DatLocaleManager.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. #include <DatSubsystems/DatLocaleManager.h>
  2. #include <EasyLogging++/easylogging++.h>
  3. #include <DatFile.h>
  4. #include <SubFile.h>
  5. namespace LOTRO_DAT {
  6. DatLocaleManager::DatLocaleManager(DatFile *datFilePtr) : dat(datFilePtr), current_locale_(ORIGINAL) {
  7. }
  8. /*!
  9. * \Author Gi1dor
  10. * \date 06.07.2018
  11. * Инициализация модуля. Должна происходить после инициализации модуля ввода-вывода и файловой системы.
  12. * Считывает словарь и проверяет корректность (если структура словаря нарушена и основной является локаль PATHCED,
  13. * то dat файл считается некорреткным (любое обновлене dat файла с активной локалью PATCHED нарушает структуру файла
  14. * и делает использование в игре невозможным.
  15. *
  16. * \warning Не должна вызываться вручную! Автоматически вызывается в функции Initialise класса DatFile
  17. *
  18. * Структура словарей локализации:
  19. * ======== LOCALE DICT STRUCTURE =========
  20. * 4 bytes for dict size (in bytes)
  21. * 4 bytes for locale version
  22. * 4 bytes for .dat file size (with patches)
  23. * 15 bytes for "Hi from Gi1dor"
  24. * 4 bytes for LOCALE mark ("PATC" or "ORIG")
  25. * 4 bytes for orig_dict.size()
  26. * (32 + 4) * orig_dict.size() bytes for orig_dict data
  27. * 4 bytes for patch_dict.size()
  28. * (32 + 4) * patch_dict.size() bytes for patch_dict data
  29. * 4 bytes for inactive_categories dict
  30. * 4 * inactive_categories.size() bytes for inactive_categories data
  31. * ========================================
  32. * Помимо этого:
  33. * 0x128-0x12C - Флаг, установлена ли альтернативная версия (flag)
  34. * 0x12C-0x130 - Офсет начала словаря локализации (offset)
  35. *
  36. * \warning Если flag != 0 и offset == 0 - то dat файл считается повреждённым. Проверка осуществляется функцией CheckLocaleCorrectж
  37. */
  38. DatOperationResult<> DatLocaleManager::Init() {
  39. if (!dat)
  40. return DatOperationResult<>(ERROR, "LOCALEINIT: no connection with Dat (dat is nullptr)");
  41. orig_dict_.clear();
  42. patch_dict_.clear();
  43. inactive_categories.clear();
  44. current_locale_ = ORIGINAL;
  45. LOG(INFO) << "Initialising locales...";
  46. BinaryData locale_offset_data(4);
  47. dat->GetIO().ReadData(locale_offset_data, 4, 300);
  48. long long locale_offset = locale_offset_data.ToNumber<4>(0);
  49. if (locale_offset == 0 || locale_offset + 8 >= dat->GetIO().GetActualDatSize().value) {
  50. LOG(INFO) << "Dictionary offset is empty or incorrect. Passing.";
  51. return DatOperationResult<>();
  52. }
  53. BinaryData locale_info(12);
  54. dat->GetIO().ReadData(locale_info, 12, locale_offset);
  55. long long dict_size = locale_info.ToNumber<4>(0);
  56. long long dict_version = locale_info.ToNumber<4>(4);
  57. if (dict_version != 101) {
  58. dat->GetIO().WriteData(BinaryData::FromNumber<4>(0), 4, 300);
  59. if (CheckLocaleCorrect())
  60. return DatOperationResult<>(SUCCESS);
  61. else
  62. return DatOperationResult<>(ERROR, "Version of locales' dictionary is incorrect, through patched mark is standing. Dat file may be corrupted");
  63. }
  64. BinaryData dicts_data = BinaryData((unsigned)dict_size);
  65. dat->GetIO().ReadData(dicts_data, dict_size - 12, locale_offset + 12);
  66. if (dicts_data.size() < 15) {
  67. dat->GetIO().WriteData(BinaryData::FromNumber<4>(0), 4, 300);
  68. return DatOperationResult<>(ERROR, "INITLOCALE: Data in locales' dictionary is incorrect.");
  69. }
  70. BinaryData hi_data = dicts_data.CutData(0, 15) + BinaryData("\0", 1);
  71. std::string hi = std::string((char *) (hi_data.data()));
  72. if (hi != "Hi from Gi1dor!")
  73. return DatOperationResult<>(ERROR, "INITLOCALE: Data in locales' dictionary is incorrect (couldn't receive 'Hello').");
  74. int offset = 15;
  75. BinaryData current_locale_data = dicts_data.CutData(offset, offset + 4) + BinaryData("\0", 1);
  76. std::string locale((char *) (current_locale_data.data()));
  77. offset += 4;
  78. if (locale != "PATC" && locale != "ORIG")
  79. return DatOperationResult<>(ERROR, "INITLOCALE: Data in locales' dictionary is incorrect (current locale mark is invalid).");
  80. current_locale_ = (locale == "PATC" ? PATCHED : ORIGINAL);
  81. size_t orig_dict_size = size_t(dicts_data.CutData(offset, offset + 4).ToNumber<4>(0));
  82. offset += 4;
  83. for (size_t i = 0; i < orig_dict_size; i++) {
  84. auto file = SubFile(*dat, dicts_data.CutData(offset, offset + 32));
  85. file.category = dicts_data.ToNumber<4>(offset);
  86. orig_dict_[file.file_id()] = file;
  87. offset += 36;
  88. }
  89. size_t patch_dict_size = size_t(dicts_data.CutData(offset, offset + 4).ToNumber<4>(0));
  90. offset += 4;
  91. for (size_t i = 0; i < patch_dict_size; i++) {
  92. auto file = SubFile(*dat, dicts_data.CutData(offset, offset + 32));
  93. file.category = dicts_data.ToNumber<4>(offset);
  94. patch_dict_[file.file_id()] = file;
  95. offset += 36;
  96. }
  97. LOG(INFO) << "There are " << patch_dict_.size() << " files in patch locale dictionary";
  98. LOG(INFO) << "There are " << orig_dict_.size() << " files in original locale dictionary";
  99. LOG(INFO) << "Finished initialising locales";
  100. if (CheckLocaleCorrect()) {
  101. dat->GetIO().file_size = locale_info.ToNumber<4>(8);
  102. LOG(INFO) << "Locales initialisation success. Dictionary size is " << dict_size << ". Version is " << dict_version << ". Localed .dat size = " << dat->GetIO().file_size;
  103. return DatOperationResult<>(SUCCESS);
  104. } else
  105. return DatOperationResult<>(ERROR, "Locale dict is incorrect, through patched mark is standing. Dat file may be corrupted");
  106. }
  107. /*!
  108. * \Author Gi1dor
  109. * \date 06.07.2018
  110. * Смена активной локали. Меняет информацию о файлах в файловой системе на соответствующую информацию в локали.
  111. * \param[in] locale Устанавливаемая локаль
  112. * \warning Изменения в файловую систему вносятся локально! В dat файл изменения будут записаны во время деинициализации модуля файловой системы
  113. */
  114. DatOperationResult<> DatLocaleManager::SetLocale(DatLocaleManager::LOCALE locale) {
  115. LOG(INFO) << "Setting locale to " << (locale == PATCHED ? " PATCHED" : " ORIGINAL");
  116. if (!dat)
  117. return DatOperationResult<>(ERROR, "SETLOCALE: no connection with Dat (dat is nullptr)");
  118. if (current_locale_ == locale) {
  119. LOG(INFO) << "Locale is already " << locale << ", nothing to do.";
  120. return DatOperationResult<>(SUCCESS);
  121. }
  122. std::map<long long, SubFile>& dict = GetLocaleDictReference(locale);
  123. for (const auto &file : dict) {
  124. long long file_id = file.first;
  125. auto dict_file_result = dat->GetFileSystem().GetFile(file_id);
  126. if (dict_file_result.result != SUCCESS) {
  127. LOG(WARNING) << "Unable to get file with id = " << file_id << "from datFileSystem!";
  128. dict.erase(file_id);
  129. continue;
  130. }
  131. std::shared_ptr<SubFile> dict_file = dict_file_result.value;
  132. if (dict_file->MakeHeaderData().CutData(8, 16) == file.second.MakeHeaderData().CutData(8, 16))
  133. continue;
  134. dat->GetFileSystem().UpdateFileInfo(file.second);
  135. }
  136. current_locale_ = locale;
  137. return DatOperationResult<>(SUCCESS);
  138. }
  139. /*!
  140. * \Author Gi1dor
  141. * \date 06.07.2018
  142. * Получение текущей активной локали. Если локали не существуют, понимается, что используется оригинальная (ORIGINAL)
  143. */
  144. DatLocaleManager::LOCALE DatLocaleManager::GetCurrentLocale() {
  145. return current_locale_;
  146. }
  147. /*!
  148. * \Author Gi1dor
  149. * \date 06.07.2018
  150. * Деинициализация модуля. Должна происходить перед деинициализацией модулей ввода-вывода и файловой системы.
  151. * Записывает словарь. Если ранее словарь не существовал - новый будет записан в конец файла.
  152. * Для словаря выделяется блок не менее 20мб
  153. *
  154. * \warning Не должна вызываться вручную! Автоматически вызывается в функции Deinitialise класса DatFile
  155. *
  156. * Структура словарей локализации:
  157. * ======== LOCALE DICT STRUCTURE =========
  158. * 4 bytes for dict size (in bytes)
  159. * 4 bytes for locale version
  160. * 4 bytes for .dat file size (with patches)
  161. * 15 bytes for "Hi from Gi1dor"
  162. * 4 bytes for LOCALE
  163. * 4 bytes for orig_dict.size()
  164. * (32 + 4) * orig_dict.size() bytes for orig_dict data
  165. * 4 bytes for patch_dict.size()
  166. * (32 + 4) * patch_dict.size() bytes for patch_dict data
  167. * 4 bytes for inactive_categories dict
  168. * 4 * inactive_categories.size() bytes for inactive_categories data
  169. * ========================================
  170. * Помимо этого:
  171. * 0x128-0x12C - Флаг, является ли активной альтернативная версия (PATCHED)
  172. * 0x12C-0x130 - Офсет начала словаря локализации
  173. */
  174. DatOperationResult<> DatLocaleManager::DeInit() {
  175. LOG(INFO) << "Committing locales...";
  176. if (!dat)
  177. return DatOperationResult<>(ERROR, "LOCALEDEINIT: no connection with Dat (dat is nullptr)");
  178. if (patch_dict_.empty()) {
  179. dat->GetIO().WriteData(BinaryData::FromNumber<4>(0), 4, 300);
  180. return DatOperationResult<>(SUCCESS);
  181. }
  182. BinaryData binary_data = BinaryData(4 + 4 + 4 + 14 + 15 + 4
  183. + 4 + (32 + 4) * orig_dict_.size()
  184. + 4 + (32 + 4) * patch_dict_.size()
  185. + 4 + 4 * inactive_categories.size());
  186. // First 12 bytes will be filled just before writing data to file
  187. size_t current_size = 12;
  188. binary_data.Append(BinaryData("Hi from Gi1dor!", 15), current_size);
  189. current_size += 15;
  190. binary_data.Append(BinaryData((current_locale_ == ORIGINAL ? "ORIG" : "PATC"), 4), current_size);
  191. current_size += 4;
  192. binary_data.Append(BinaryData::FromNumber<4>(orig_dict_.size()), current_size);
  193. current_size += 4;
  194. for (const auto &file : orig_dict_) {
  195. binary_data.Append(file.second.MakeHeaderData(), current_size);
  196. current_size += 32;
  197. binary_data.Append(BinaryData::FromNumber<4>(file.second.category), current_size);
  198. current_size += 4;
  199. }
  200. binary_data.Append(BinaryData::FromNumber<4>(patch_dict_.size()), current_size);
  201. current_size += 4;
  202. for (const auto &file : patch_dict_) {
  203. binary_data.Append(file.second.MakeHeaderData(), current_size);
  204. current_size += 32;
  205. binary_data.Append(BinaryData::FromNumber<4>(file.second.category), current_size);
  206. current_size += 4;
  207. }
  208. binary_data.Append(BinaryData::FromNumber<4>(inactive_categories.size()), current_size);
  209. current_size += 4;
  210. for (auto patch_id : inactive_categories) {
  211. binary_data.Append(BinaryData::FromNumber<4>(patch_id), current_size);
  212. current_size += 4;
  213. }
  214. BinaryData dicts_data(4);
  215. dat->GetIO().ReadData(dicts_data, 4, 300);
  216. long long dict_offset = dicts_data.ToNumber<4>(0);
  217. dat->GetIO().ReadData(dicts_data, 4, dict_offset);
  218. long long dict_size = dicts_data.ToNumber<4>(0);
  219. if (binary_data.size() > dict_size || dict_offset == 0) {
  220. long long new_dict_offset = dat->GetIO().file_size + 12;
  221. // Updating first 12 bytes
  222. binary_data.Append(BinaryData::FromNumber<4>(std::max(binary_data.size() + 4, 20u * 1024u * 1024u)), 0);
  223. binary_data.Append(BinaryData::FromNumber<4>(101), 4);
  224. binary_data.Append(BinaryData::FromNumber<4>(dat->GetIO().file_size + binary_data.size() + 20 * 1024 * 1024), 8);
  225. auto operation = dat->GetIO().WriteData(binary_data, binary_data.size(), new_dict_offset);
  226. if (operation.result != SUCCESS)
  227. return DatOperationResult<>(ERROR, "LOCALEDEINIT: Cannot write locales");
  228. dat->GetIO().WriteData(BinaryData::FromNumber<4>(new_dict_offset), 4, 300);
  229. dat->GetIO().WriteData(BinaryData::FromNumber<4>(current_locale_), 4, 296);
  230. dat->GetIO().file_size += binary_data.size();
  231. // Adding space for 20 megabytes locales file in total.
  232. BinaryData nulls(unsigned(20 * 1024 * 1024));
  233. dat->GetIO().WriteData(nulls, nulls.size(), dat->GetIO().file_size);
  234. dat->GetIO().file_size += nulls.size();
  235. } else {
  236. binary_data.Append(BinaryData::FromNumber<4>(std::max(binary_data.size() + 4, 20u * 1024u * 1024u)), 0);
  237. binary_data.Append(BinaryData::FromNumber<4>(101), 4);
  238. binary_data.Append(BinaryData::FromNumber<4>(dat->GetIO().file_size), 8);
  239. dat->GetIO().WriteData(BinaryData::FromNumber<4>(current_locale_), 4, 296, 0);
  240. auto operation = dat->GetIO().WriteData(binary_data, binary_data.size(), dict_offset);
  241. if (operation.result != SUCCESS)
  242. return DatOperationResult<>(ERROR, "LOCALEDEINIT: Cannot write locales. ERRMSG: " + operation.msg);
  243. }
  244. LOG(INFO) << "Locales commited successfully";
  245. return DatOperationResult<>(SUCCESS);
  246. }
  247. /*!
  248. * \Author Gi1dor
  249. * \date 06.07.2018
  250. * Обновление данных файла в локали
  251. * \param[in] locale Локаль, для которой файл данных будет обновлён
  252. * \param[in] file Новые данные файла. Старые данные будут перезаписаны
  253. */
  254. void DatLocaleManager::UpdateLocaleFile(DatLocaleManager::LOCALE locale, const SubFile &file) {
  255. std::map<long long, SubFile>& dict = GetLocaleDictReference(locale);
  256. dict[file.file_id()] = file;
  257. }
  258. /*!
  259. * \Author Gi1dor
  260. * \date 06.07.2018
  261. * Получение данных файла в локали. Вернёт DatOperationResult.result = ERROR, если файла с таким id не существует.
  262. * \param[in] file_id id файла, для которого необходимо получить данных
  263. * \param[in] locale Локаль, данные которой будут получены
  264. */
  265. DatOperationResult<SubFile> DatLocaleManager::GetLocaleFile(long long file_id, DatLocaleManager::LOCALE locale) {
  266. std::map<long long, SubFile>& dict = GetLocaleDictReference(locale);
  267. if (dict.count(file_id) == 0)
  268. return DatOperationResult<SubFile>(SubFile(), ERROR, "GETLOCFILE: cannot get file with id = " + std::to_string(file_id) + " from dict " + std::to_string(locale));
  269. return DatOperationResult<SubFile>(dict[file_id], SUCCESS);
  270. }
  271. /*!
  272. * \Author Gi1dor
  273. * \date 06.07.2018
  274. * Получение словаря файлов локали
  275. * \param[in] locale Локаль, данные которой будут получены
  276. */
  277. std::map<long long, SubFile> &DatLocaleManager::GetLocaleDictReference(DatLocaleManager::LOCALE locale) {
  278. return locale == PATCHED ? patch_dict_ : orig_dict_;
  279. }
  280. /*!
  281. * \Author Gi1dor
  282. * \date 06.07.2018
  283. * Выводит информацию о состоянии модуля в файл
  284. * \param[in] file указатель на объект файла, в конец которого будет напечатана информация
  285. * \warning Файл file должен быть открыт для записи. После завершения работы функции файл остаётся открытым
  286. */
  287. void DatLocaleManager::PrintInformaion(FILE *file) {
  288. fprintf(file, "========= Locales info ========\n");
  289. BinaryData locale_offset_data(4);
  290. dat->GetIO().ReadData(locale_offset_data, 4, 300);
  291. long long locale_offset = locale_offset_data.ToNumber<4>(0);
  292. fprintf(file, "Locales' dictionary offset = %lld\n", locale_offset);
  293. BinaryData locale_status_data(4);
  294. dat->GetIO().ReadData(locale_status_data, 4, 296);
  295. long long locale_status = locale_offset_data.ToNumber<4>(0);
  296. fprintf(file, "Locale status = %lld\n", locale_status);
  297. if (locale_offset != 0) {
  298. BinaryData locale_info(12);
  299. dat->GetIO().ReadData(locale_info, 12, locale_offset);
  300. long long dict_size = locale_info.ToNumber<4>(0);
  301. long long dict_version = locale_info.ToNumber<4>(4);
  302. fprintf(file, "Locales' dictionary size = %lld, version = %lld\n", dict_size, dict_version);
  303. dat->GetIO().file_size = locale_info.ToNumber<4>(8);
  304. }
  305. fprintf(file, "Current locale id = %d\n", current_locale_);
  306. fprintf(file, "Patch dictionary size = %d\n", patch_dict_.size());
  307. fprintf(file, "Original dictionary size = %d\n", orig_dict_.size());
  308. }
  309. /*!
  310. * \Author Gi1dor
  311. * \date 06.07.2018
  312. * Осуществляет проверку корректности dat файла с позиции локалей
  313. * Файл считается некорректным, если активной является альтернативная локаль, причём нет возможности вернуть оригинальную (блок локалей неверный/повреждён)
  314. * Байты 0x128-0x12C показывают активную локаль на момент закрытия dat файла.
  315. */
  316. bool DatLocaleManager::CheckLocaleCorrect() {
  317. BinaryData locale_info(4);
  318. dat->GetIO().ReadData(locale_info, 4, 296);
  319. long long locale_status = locale_info.ToNumber<4>(0);
  320. if (locale_status == ORIGINAL)
  321. return true;
  322. BinaryData locale_offset_data(4);
  323. dat->GetIO().ReadData(locale_offset_data, 4, 300);
  324. long long locale_offset = locale_offset_data.ToNumber<4>(0);
  325. if (locale_offset == 0 || locale_offset + 8 >= dat->GetIO().GetActualDatSize().value)
  326. return locale_status == ORIGINAL;
  327. BinaryData dicts_data = BinaryData(4);
  328. auto operation = dat->GetIO().ReadData(dicts_data, 4, locale_offset + 12 + 15);
  329. if (operation.result == ERROR)
  330. return locale_status == ORIGINAL;
  331. BinaryData locale_data = dicts_data + BinaryData("\0", 1);
  332. std::string locale((char *)(locale_data.data()));
  333. LOCALE dat_locale = (locale == "PATC" ? PATCHED : ORIGINAL);
  334. return locale_status == dat_locale;
  335. }
  336. /*!
  337. * \Author Gi1dor
  338. * \date 11.07.2018
  339. * Проверка активности категории
  340. * \returns true, если категория активна и false - если нет
  341. */
  342. bool DatLocaleManager::CategoryIsInactive(long long category) {
  343. return inactive_categories.count(category) > 0;
  344. }
  345. /*!
  346. * \Author Gi1dor
  347. * \date 11.07.2018
  348. * Обновляет категорию у файлов в словарях локалей
  349. */
  350. void DatLocaleManager::UpdateCategory(long long file_id, long long category) {
  351. if (orig_dict_.count(file_id))
  352. orig_dict_[file_id].category = category;
  353. if (patch_dict_.count(file_id))
  354. patch_dict_[file_id].category = category;
  355. }
  356. }