|
@@ -7,6 +7,44 @@
|
|
|
#include "../DatFile.h"
|
|
|
#include "../Common/DatException.h"
|
|
|
#include "../SubfileData.h"
|
|
|
+#include <algorithm>
|
|
|
+#include <codecvt>
|
|
|
+#include <locale>
|
|
|
+
|
|
|
+std::u16string to_utf16(long long x) {
|
|
|
+ std::u16string res;
|
|
|
+ while (x > 0) {
|
|
|
+ res += u'0' + (x % 10);
|
|
|
+ x /= 10ll;
|
|
|
+ }
|
|
|
+ std::reverse(res.begin(), res.end());
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+long long from_utf16(const std::u16string &num) {
|
|
|
+ long long res = 0;
|
|
|
+ for (auto c : num) {
|
|
|
+ res = res * 10ll + (c - u'0');
|
|
|
+ }
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+std::string argumentsFromUtf16(const std::u16string &args) {
|
|
|
+ std::string res;
|
|
|
+
|
|
|
+ size_t pointer = 0;
|
|
|
+ while (pointer < args.length()) {
|
|
|
+ size_t pointer1 = args.find(u'-', pointer);
|
|
|
+ if (pointer1 == std::u16string::npos)
|
|
|
+ pointer1 = args.length();
|
|
|
+ if (!res.empty())
|
|
|
+ res += "-";
|
|
|
+ res += std::to_string(from_utf16(args.substr(pointer, pointer1 - pointer)));
|
|
|
+ pointer = pointer1 + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return res;
|
|
|
+}
|
|
|
|
|
|
namespace LOTRO_DAT {
|
|
|
TextSubfile::TextSubfile() = default;
|
|
@@ -26,8 +64,8 @@ namespace LOTRO_DAT {
|
|
|
return std::string(".txt");
|
|
|
}
|
|
|
|
|
|
- std::vector<SubfileData> TextSubfile::PrepareForExport(const BinaryData &file_data) {
|
|
|
- std::vector<SubfileData> result;
|
|
|
+ SubfileData TextSubfile::PrepareForExport(const BinaryData &file_data) {
|
|
|
+ SubfileData result;
|
|
|
if (file_size() <= 10) // File is empty, nothing to do;
|
|
|
return result;
|
|
|
|
|
@@ -53,30 +91,150 @@ namespace LOTRO_DAT {
|
|
|
text += text_pieces[j] + u"<--DO_NOT_TOUCH!-->";
|
|
|
text += text_pieces[text_pieces.size() - 1] + u"]";
|
|
|
|
|
|
- std::string arguments;
|
|
|
+ std::u16string arguments;
|
|
|
for (size_t j = 0; j + 1 < arg_references.size(); j++)
|
|
|
- arguments += std::to_string(arg_references[j]) + "-";
|
|
|
+ arguments += to_utf16(arg_references[j]) + u"-";
|
|
|
if (!arg_references.empty())
|
|
|
- arguments += std::to_string(arg_references[arg_references.size() - 1]);
|
|
|
+ arguments += to_utf16(arg_references[arg_references.size() - 1]);
|
|
|
|
|
|
- SubfileData subfile;
|
|
|
+ if (result.text_data.length() > 0)
|
|
|
+ result.text_data += u"|||";
|
|
|
|
|
|
- subfile.text_data = text;
|
|
|
- subfile.options["fid"] = file_id();
|
|
|
- subfile.options["gid"] = fragment_id;
|
|
|
- subfile.options["ext"] = Extension();
|
|
|
- if (!arg_references.empty())
|
|
|
- subfile.options["args"] = arguments;
|
|
|
-
|
|
|
- result.push_back(subfile);
|
|
|
+ result.text_data += to_utf16(fragment_id) + u":::";
|
|
|
+ result.text_data += arguments + u":::";
|
|
|
+ result.text_data += text;
|
|
|
}
|
|
|
+ result.options["fid"] = file_id();
|
|
|
+ result.options["ext"] = Extension();
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
BinaryData TextSubfile::MakeForImport(const BinaryData &old_data, const SubfileData &data) {
|
|
|
- return Subfile::MakeForImport(old_data, data);
|
|
|
+ std::unordered_map<long long, SubfileData> patch_fragments = ParsePatchFragments(data);
|
|
|
+
|
|
|
+ BinaryData new_data;
|
|
|
+
|
|
|
+ if (file_size() <= 10 + 8) // File is empty, nothing to do;
|
|
|
+ return old_data;
|
|
|
+
|
|
|
+ long long offset = 9 + 8; // first 8 bytes - file_info. After them:
|
|
|
+ // first 4 bytes - file_id, then 4 bytes - unknown, then 1 byte - unknown
|
|
|
+
|
|
|
+ long long text_fragment_num = old_data.ToNumber<1>(offset);
|
|
|
+ if ((text_fragment_num & 0x80) != 0) {
|
|
|
+ text_fragment_num = (((text_fragment_num ^ 0x80) << 8) | old_data.ToNumber<1>(offset + 1));
|
|
|
+ offset += 1;
|
|
|
+ }
|
|
|
+ offset += 1;
|
|
|
+
|
|
|
+ new_data = new_data + old_data.CutData(0, offset); // Adding file info
|
|
|
+
|
|
|
+ for (long long i = 0; i < text_fragment_num; i++) {
|
|
|
+ long long fragment_id = old_data.ToNumber<8>(offset);
|
|
|
+ offset += 8;
|
|
|
+
|
|
|
+ new_data = new_data + old_data.CutData(offset - 8, offset);
|
|
|
+
|
|
|
+ if (patch_fragments.count(fragment_id) == 0) {
|
|
|
+ std::cerr << "Omg, what..? " << file_id() << " " << fragment_id << " is not in patch? " << std::endl;
|
|
|
+ try {
|
|
|
+ // Retrieving old pieces
|
|
|
+ new_data = new_data + GetPieceData(old_data, offset);
|
|
|
+ } catch (std::exception &e) {
|
|
|
+ fprintf(stderr, "Caught %s exception.\n", e.what());
|
|
|
+ fprintf(stderr, "ERROR TextSubfile::MakeForImport() - unable to get piece data for file_id %lld and fragment_id %lld", file_id(), fragment_id);
|
|
|
+ throw DatException("Bad TextSubfile::MakeForImport()", IMPORT_EXCEPTION);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Retrieving old references
|
|
|
+ new_data = new_data + GetArgumentReferenceData(old_data, offset);
|
|
|
+ } catch (std::exception &e) {
|
|
|
+ fprintf(stderr, "Caught %s exception.\n", e.what());
|
|
|
+ fprintf(stderr, "ERROR TextSubfile::MakeForImport() - unable to get argument reference data for file_id %lld and fragment_id %lld", file_id(), fragment_id);
|
|
|
+ throw DatException("Bad TextSubfile::MakeForImport()", IMPORT_EXCEPTION);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Retrieving old ref_strings
|
|
|
+ new_data = new_data + GetArgumentStringsData(old_data, offset);
|
|
|
+ } catch (std::exception &e) {
|
|
|
+ fprintf(stderr, "Caught %s exception.\n", e.what());
|
|
|
+ fprintf(stderr, "ERROR TextSubfile::MakeForImport() - unable to get argument string for file_id %lld and fragment_id %lld", file_id(), fragment_id);
|
|
|
+ throw DatException("Bad TextSubfile::MakeForImport()", IMPORT_EXCEPTION);
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ // Making and adding new pieces
|
|
|
+ new_data = new_data + BuildPieces(old_data, patch_fragments[fragment_id], offset);
|
|
|
+ } catch (std::exception &e) {
|
|
|
+ fprintf(stderr, "Caught %s exception.\n", e.what());
|
|
|
+ fprintf(stderr, "ERROR TextSubfile::MakeForImport() - unable to build piece data for file_id %lld and fragment_id %lld", file_id(), fragment_id);
|
|
|
+ throw DatException("Bad TextSubfile::MakeForImport()", IMPORT_EXCEPTION);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Making and adding new references
|
|
|
+ new_data = new_data + BuildArgumentReferences(old_data, patch_fragments[fragment_id], offset);
|
|
|
+ } catch (std::exception &e) {
|
|
|
+ fprintf(stderr, "Caught %s exception.\n", e.what());
|
|
|
+ fprintf(stderr, "ERROR TextSubfile::MakeForImport() - unable to build argument references data for file_id %lld and fragment_id %lld", file_id(), fragment_id);
|
|
|
+ throw DatException("Bad TextSubfile::MakeForImport()", IMPORT_EXCEPTION);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Making and adding new strings
|
|
|
+ new_data = new_data + BuildArgumentStrings(old_data, patch_fragments[fragment_id], offset);
|
|
|
+ } catch (std::exception &e) {
|
|
|
+ fprintf(stderr, "Caught %s exception.\n", e.what());
|
|
|
+ fprintf(stderr, "ERROR TextSubfile::MakeForImport() - unable to build argument strings data for file_id %lld and fragment_id %lld", file_id(), fragment_id);
|
|
|
+ throw DatException("Bad TextSubfile::MakeForImport()", IMPORT_EXCEPTION);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ new_data = new_data + old_data.CutData(offset); // Adding elapsed file data
|
|
|
+ return new_data;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::unordered_map<long long, SubfileData> TextSubfile::ParsePatchFragments(const SubfileData &data) {
|
|
|
+ std::unordered_map<long long, SubfileData> res;
|
|
|
+ std::u16string text = data.text_data;
|
|
|
+
|
|
|
+ size_t pointer = 0;
|
|
|
+ while (pointer < text.length()) {
|
|
|
+ // Parsing fragment_id
|
|
|
+ size_t pointer1 = text.find(u":::", pointer);
|
|
|
+ if (pointer1 == std::u16string::npos)
|
|
|
+ throw DatException("Bad TextSubfile::ParsePatchFragments() - Unable to parse fragment id! Cannot find '...' divider");
|
|
|
+ long long fragment_id = from_utf16(text.substr(pointer, pointer1 - pointer));
|
|
|
+ pointer = pointer1 + 3;
|
|
|
+ res[fragment_id] = SubfileData();
|
|
|
+ res[fragment_id].options["gid"] = fragment_id;
|
|
|
+
|
|
|
+ // Parsing arguments
|
|
|
+ pointer1 = text.find(u":::", pointer);
|
|
|
+ if (pointer1 == std::u16string::npos)
|
|
|
+ throw DatException("Bad TextSubfile::ParsePatchFragments() - Unable to parse arguments! Cannot find '...' divider");
|
|
|
+ std::u16string arguments = text.substr(pointer, pointer1 - pointer);
|
|
|
+ pointer = pointer1 + 3;
|
|
|
+ if (arguments.length() > 0) {
|
|
|
+ res[fragment_id].options["args"] = argumentsFromUtf16(arguments);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parsing text
|
|
|
+ pointer1 = text.find(u"|||", pointer);
|
|
|
+ if (pointer1 == std::u16string::npos)
|
|
|
+ pointer1 = text.length();
|
|
|
+ std::u16string text_data = text.substr(pointer, pointer1 - pointer);
|
|
|
+ pointer = pointer1 + 3;
|
|
|
+ res[fragment_id].text_data = text_data;
|
|
|
+ }
|
|
|
+ return res;
|
|
|
}
|
|
|
|
|
|
+ // Make pieces/arguments/argument strings functions
|
|
|
+
|
|
|
std::vector<std::u16string> TextSubfile::MakePieces(const BinaryData &data, long long &offset) {
|
|
|
long long num_pieces = data.ToNumber<4>(offset);
|
|
|
offset += 4;
|
|
@@ -144,4 +302,152 @@ namespace LOTRO_DAT {
|
|
|
}
|
|
|
return arg_strings;
|
|
|
}
|
|
|
+
|
|
|
+ // Build pieces/arguments/argument strings functions from fragment SubfileData
|
|
|
+
|
|
|
+ BinaryData TextSubfile::BuildPieces(const BinaryData &data, const SubfileData &new_data, long long &offset) {
|
|
|
+ try {
|
|
|
+ // Moving &offset pointer in &data
|
|
|
+ GetPieceData(data, offset);
|
|
|
+ } catch (std::exception &e) {
|
|
|
+ fprintf(stderr, "Caught %s exception.\n", e.what());
|
|
|
+ fprintf(stderr, "ERROR TextSubfile::BuildPieces() - unable to get piece data for file_id %lld", file_id());
|
|
|
+ throw DatException("Bad TextSubfile::BuildPieces()", IMPORT_EXCEPTION);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Deleting '[' and ']' brackets
|
|
|
+ std::u16string text_data = new_data.text_data.substr(1, new_data.text_data.size() - 2);
|
|
|
+ std::vector<std::u16string> pieces;
|
|
|
+
|
|
|
+ const std::u16string DNT = u"<--DO_NOT_TOUCH!-->";
|
|
|
+ size_t prev = 0;
|
|
|
+ size_t next = text_data.find(DNT, prev);
|
|
|
+
|
|
|
+ while (next != std::string::npos) {
|
|
|
+ std::u16string piece = text_data.substr(prev, next - prev);
|
|
|
+ pieces.push_back(piece);
|
|
|
+ prev = next + DNT.length();
|
|
|
+ next = text_data.find(DNT, prev);
|
|
|
+ }
|
|
|
+ std::u16string piece = text_data.substr(prev);
|
|
|
+ pieces.push_back(piece);
|
|
|
+
|
|
|
+ // Building BinaryData from pieces
|
|
|
+ BinaryData result;
|
|
|
+ BinaryData temp_data(4);
|
|
|
+
|
|
|
+ temp_data.FromNumber<4>(pieces.size());
|
|
|
+ result = result + temp_data;
|
|
|
+
|
|
|
+ for (long long i = 0; i < pieces.size(); i++) {
|
|
|
+ long long piece_size = pieces[i].length();
|
|
|
+ if (piece_size < 128) {
|
|
|
+ temp_data.FromNumber<1>(piece_size);
|
|
|
+ } else {
|
|
|
+ temp_data.FromNumberRAW<2>((piece_size | 32768));
|
|
|
+ }
|
|
|
+ result = result + temp_data;
|
|
|
+
|
|
|
+ for (long long j = 0; j < piece_size; j++) {
|
|
|
+ temp_data.FromNumber<2>(short(pieces[i][j]));
|
|
|
+ result = result + temp_data;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ BinaryData TextSubfile::BuildArgumentReferences(const BinaryData &data, const SubfileData &new_data,
|
|
|
+ long long &offset) {
|
|
|
+ // Moving &offset pointer in &data
|
|
|
+ GetArgumentReferenceData(data, offset);
|
|
|
+
|
|
|
+ // If there are no args - making 4 null-bytes and return;
|
|
|
+ if (!new_data.options["args"]) {
|
|
|
+ BinaryData result;
|
|
|
+ result.FromNumber<4>(0);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parsing arguments from list in options["args"]
|
|
|
+ std::string args_list = new_data.options["args"].as<std::string>();
|
|
|
+ std::vector<long long> arguments;
|
|
|
+
|
|
|
+ size_t prev = 0;
|
|
|
+ size_t next = args_list.find('-', prev);
|
|
|
+ while (next != std::string::npos) {
|
|
|
+ std::string argument = args_list.substr(prev, next - prev);
|
|
|
+ arguments.push_back(std::stoll(argument));
|
|
|
+ prev = next + 1;
|
|
|
+ next = args_list.find('-', prev);
|
|
|
+ }
|
|
|
+ std::string argument = args_list.substr(prev);
|
|
|
+ arguments.push_back(std::stoll(argument));
|
|
|
+
|
|
|
+ BinaryData result;
|
|
|
+ BinaryData temp_data(4);
|
|
|
+ temp_data.FromNumber<4>(arguments.size());
|
|
|
+ result = result + temp_data;
|
|
|
+ for (auto arg_reference : arguments) {
|
|
|
+ temp_data.FromNumber<4>(arg_reference);
|
|
|
+ result = result + temp_data;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ BinaryData TextSubfile::BuildArgumentStrings(const BinaryData &data, const SubfileData &new_data,
|
|
|
+ long long &offset) {
|
|
|
+ return GetArgumentStringsData(data, offset);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get BinaryData contents of pieces/arguments/argument strings
|
|
|
+
|
|
|
+ BinaryData TextSubfile::GetPieceData(const BinaryData &data, long long &offset) const {
|
|
|
+ long long old_offset = offset;
|
|
|
+
|
|
|
+ long long num_pieces = data.ToNumber<4>(offset);
|
|
|
+ offset += 4;
|
|
|
+
|
|
|
+ for (long long j = 0; j < num_pieces; j++) {
|
|
|
+ long long piece_size = data.ToNumber<1>(offset);
|
|
|
+ if ((piece_size & 128) != 0) {
|
|
|
+ piece_size = (((piece_size ^ 128) << 8) | data.ToNumber<1>(offset + 1));
|
|
|
+ offset += 1;
|
|
|
+ }
|
|
|
+ offset += 1;
|
|
|
+ offset += piece_size * 2;
|
|
|
+ }
|
|
|
+ return data.CutData(old_offset, offset);
|
|
|
+ }
|
|
|
+
|
|
|
+ BinaryData TextSubfile::GetArgumentReferenceData(const BinaryData &data, long long &offset) const {
|
|
|
+ long long old_offset = offset;
|
|
|
+ long long num_references = data.ToNumber<4>(offset);
|
|
|
+ offset += 4;
|
|
|
+ offset += 4 * num_references;
|
|
|
+ return data.CutData(old_offset, offset);
|
|
|
+ }
|
|
|
+
|
|
|
+ BinaryData TextSubfile::GetArgumentStringsData(const BinaryData &data, long long &offset) const {
|
|
|
+ long long old_offset = offset;
|
|
|
+
|
|
|
+ long long num_arg_strings = data.ToNumber<1>(offset);
|
|
|
+ offset += 1;
|
|
|
+
|
|
|
+ for (long long j = 0; j < num_arg_strings; j++) {
|
|
|
+ long long num_args = data.ToNumber<4>(offset);
|
|
|
+ offset += 4;
|
|
|
+
|
|
|
+ for (long long k = 0; k < num_args; k++) {
|
|
|
+ long long string_size = data.ToNumber<1>(offset);
|
|
|
+ if ((string_size & 0x80) != 0) {
|
|
|
+ string_size = (((string_size ^ 0x80) << 8) | data.ToNumber<1>(offset + 1));
|
|
|
+ offset += 1;
|
|
|
+ }
|
|
|
+ offset += 1;
|
|
|
+ offset += string_size * 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return data.CutData(old_offset, offset);
|
|
|
+ }
|
|
|
+
|
|
|
};
|