reformat remaining files

This commit is contained in:
Martin Michelsen
2025-12-21 21:06:29 -08:00
parent e5a03b7e9b
commit a0a7231d67
40 changed files with 2117 additions and 3190 deletions
+75 -118
View File
@@ -22,8 +22,7 @@
using namespace std;
QuestCategoryIndex::Category::Category(uint32_t category_id, const phosg::JSON& json)
: category_id(category_id) {
QuestCategoryIndex::Category::Category(uint32_t category_id, const phosg::JSON& json) : category_id(category_id) {
this->enabled_flags = json.get_int(0);
this->directory_name = json.get_string(1);
this->name = json.get_string(2);
@@ -46,9 +45,8 @@ shared_ptr<const QuestCategoryIndex::Category> QuestCategoryIndex::at(uint32_t c
template <bool BE>
struct PSOMemCardDLQFileEncryptedHeaderT {
U32T<BE> round2_seed;
// To compute checksum, set checksum to zero, then compute the CRC32 of the
// entire data section, including this header struct (but not the unencrypted
// header struct).
// To compute checksum, set checksum to zero, then compute the CRC32 of the entire data section, including this
// header struct (but not the unencrypted header struct).
U32T<BE> checksum;
le_uint32_t decompressed_size;
le_uint32_t round3_seed;
@@ -67,15 +65,12 @@ string decrypt_download_quest_data_section(
size_t orig_size = decrypted.size();
decrypted.resize((decrypted.size() + 3) & (~3));
// Note: Other PSO save files have the round 2 seed at the end of the data,
// not at the beginning. Presumably they did this because the system,
// character, and Guild Card files are a constant size, but download quest
// files can vary in size.
// Other PSO save files have the round 2 seed at the end, not at the beginning. Presumably this is because the
// system, character, and Guild Card files are a constant size, but download quest files can vary in size.
using HeaderT = PSOMemCardDLQFileEncryptedHeaderT<BE>;
auto* header = reinterpret_cast<HeaderT*>(decrypted.data());
PSOV2Encryption round2_crypt(header->round2_seed);
round2_crypt.encrypt_t<BE>(
decrypted.data() + 4, (decrypted.size() - 4));
round2_crypt.encrypt_t<BE>(decrypted.data() + 4, (decrypted.size() - 4));
if (is_ep3_trial) {
phosg::StringReader r(decrypted);
@@ -85,9 +80,8 @@ string decrypt_download_quest_data_section(
}
r.skip(9);
// Some Ep3 trial download quests don't have a stop opcode in the PRS
// stream; it seems the client just automatically stops when the correct
// amount of data has been produced. To handle this, we allow the PRS stream
// Some Ep3 trial download quests don't have a stop opcode in the PRS stream; it seems the client just
// automatically stops when the correct amount of data has been produced. To handle this, we allow the PRS stream
// to be unterminated here.
size_t decompressed_size = prs_decompress_size(
r.getv(r.remaining(), false), r.remaining(), sizeof(Episode3::MapDefinitionTrial), true);
@@ -100,8 +94,7 @@ string decrypt_download_quest_data_section(
} else {
if (header->decompressed_size & 0xFFF00000) {
throw runtime_error(std::format(
"decompressed_size too large ({:08X})", header->decompressed_size));
throw runtime_error(std::format("decompressed_size too large ({:08X})", header->decompressed_size));
}
if (!skip_checksum) {
@@ -111,29 +104,23 @@ string decrypt_download_quest_data_section(
header->checksum = expected_crc;
if (expected_crc != actual_crc && expected_crc != phosg::bswap32(actual_crc)) {
throw runtime_error(std::format(
"incorrect decrypted data section checksum: expected {:08X}; received {:08X}",
expected_crc, actual_crc));
"incorrect decrypted data section checksum: expected {:08X}; received {:08X}", expected_crc, actual_crc));
}
}
// Unlike the above rounds, round 3 is always little-endian (it corresponds to
// the round of encryption done on the server before sending the file to the
// client in the first place)
// Unlike the above rounds, round 3 is always little-endian (it corresponds to the round of encryption done on the
// server before sending the file to the client in the first place)
PSOV2Encryption(header->round3_seed).decrypt(decrypted.data() + sizeof(HeaderT), decrypted.size() - sizeof(HeaderT));
decrypted.resize(orig_size);
// Some download quest GCI files have decompressed_size fields that are 8
// bytes smaller than the actual decompressed size of the data. They seem to
// work fine, so we accept both cases as correct.
// Some download quest GCI files have decompressed_size fields that are 8 bytes smaller than the actual
// decompressed size of the data. They seem to work fine, so we accept both cases as correct.
size_t decompressed_size = prs_decompress_size(
decrypted.data() + sizeof(HeaderT),
decrypted.size() - sizeof(HeaderT));
decrypted.data() + sizeof(HeaderT), decrypted.size() - sizeof(HeaderT));
size_t expected_decompressed_size = header->decompressed_size;
if ((decompressed_size != expected_decompressed_size) &&
(decompressed_size != expected_decompressed_size - 8)) {
if ((decompressed_size != expected_decompressed_size) && (decompressed_size != expected_decompressed_size - 8)) {
throw runtime_error(std::format(
"decompressed size ({}) does not match expected size ({})",
decompressed_size, expected_decompressed_size));
"decompressed size ({}) does not match expected size ({})", decompressed_size, expected_decompressed_size));
}
return decrypted.substr(sizeof(HeaderT));
@@ -169,8 +156,7 @@ string find_seed_and_decrypt_download_quest_data_section(
string result;
uint64_t result_seed = phosg::parallel_range_blocks<uint64_t>([&](uint64_t seed, size_t) {
try {
string ret = decrypt_download_quest_data_section<BE>(
data_section, size, seed, skip_checksum, is_ep3_trial);
string ret = decrypt_download_quest_data_section<BE>(data_section, size, seed, skip_checksum, is_ep3_trial);
lock_guard<mutex> g(result_lock);
result = std::move(ret);
return true;
@@ -300,8 +286,7 @@ string VersionedQuest::encode_qst() const {
return encode_qst_file(files, this->meta.name, this->meta.quest_number, xb_filename, this->meta.version, this->is_dlq_encoded);
}
Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
: meta(initial_version->meta), supermap(nullptr) {
Quest::Quest(shared_ptr<const VersionedQuest> initial_version) : meta(initial_version->meta), supermap(nullptr) {
this->add_version(initial_version);
}
@@ -320,10 +305,7 @@ phosg::JSON Quest::json() const {
}));
}
return phosg::JSON::dict({
{"Metadata", this->meta.json()},
{"Versions", std::move(versions_json)},
});
return phosg::JSON::dict({{"Metadata", this->meta.json()}, {"Versions", std::move(versions_json)}});
}
uint32_t Quest::versions_key(Version v, Language language) {
@@ -430,9 +412,9 @@ shared_ptr<const VersionedQuest> Quest::version(Version v, Language language) co
return it->second;
}
QuestIndex::QuestIndex(const string& directory, shared_ptr<const QuestCategoryIndex> category_index, bool raise_on_any_failure)
: directory(directory),
category_index(category_index) {
QuestIndex::QuestIndex(
const string& directory, shared_ptr<const QuestCategoryIndex> category_index, bool raise_on_any_failure)
: directory(directory), category_index(category_index) {
struct FileData {
string filename;
@@ -462,9 +444,8 @@ QuestIndex::QuestIndex(const string& directory, shared_ptr<const QuestCategoryIn
if (!files.emplace(basename, FileData{filename, data_ptr}).second) {
throw runtime_error("file " + basename + " already exists");
}
// There is a bug in the client that prevents quests from loading properly
// if any file's size is a multiple of 0x400. See the comments on the 13
// command in CommandFormats.hh for more details.
// There is a bug in the client that prevents quests from loading properly if any file's size is a multiple of
// 0x400. See the comments on the 13 command in CommandFormats.hh for more details.
if (check_chunk_size && !(data_ptr->size() & 0x3FF)) {
data_ptr->push_back(0x00);
}
@@ -587,9 +568,8 @@ QuestIndex::QuestIndex(const string& directory, shared_ptr<const QuestCategoryIn
}
}
// All quests have a bin file (even in Episode 3, though its format is
// different), so we use bin_files as the primary list of all quests that
// should be indexed
// All quests have a bin file (even in Episode 3, though its format is different), so we use bin_files as the primary
// list of all quests that should be indexed
unordered_map<const FileData*, shared_ptr<const phosg::JSON>> parsed_json_files;
for (auto& [basename, entry] : bin_files) {
try {
@@ -601,8 +581,7 @@ QuestIndex::QuestIndex(const string& directory, shared_ptr<const QuestCategoryIn
// VERS = PSO version that the quest is for (dc, pc, gc, etc.)
// LANG = client language (j, e, g, f, s)
// EXT = file type (bin, bind, bin.dlq, qst, etc.)
// EXT has already been stripped off by the time we get here, so we just
// parse the remaining fields.
// EXT has already been stripped off by the time we get here, so we just parse the remaining fields.
string quest_number_token, version_token, language_token;
{
vector<string> filename_tokens = phosg::split(basename, '-');
@@ -653,8 +632,8 @@ QuestIndex::QuestIndex(const string& directory, shared_ptr<const QuestCategoryIn
auto bin_decompressed = prs_decompress(*entry.data);
populate_quest_metadata_from_script(vq->meta, bin_decompressed.data(), bin_decompressed.size(), vq->meta.version, vq->meta.language);
// Find the corresponding dat and pvr files with the same basename as the
// bin file; if not found, look for them without the language suffix
// Find the corresponding dat and pvr files with the same basename as the bin file; if not found, look for them
// without the language suffix
const DATFileData* dat_filedata = nullptr;
const FileData* pvr_filedata = nullptr;
try {
@@ -672,8 +651,7 @@ QuestIndex::QuestIndex(const string& directory, shared_ptr<const QuestCategoryIn
try {
pvr_filedata = &pvr_files.at(quest_number_token + "-" + version_token);
} catch (const out_of_range&) {
// pvr files aren't required (and most quests do not have them), so
// don't fail if it's missing
// pvr files aren't required (and most quests do not have them), so don't fail if it's missing
}
}
vq->bin_contents = entry.data;
@@ -798,10 +776,7 @@ shared_ptr<const Quest> QuestIndex::get(const std::string& name) const {
}
vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
QuestMenuType menu_type,
Episode episode,
uint16_t version_flags,
IncludeCondition include_condition) const {
QuestMenuType menu_type, Episode episode, uint16_t version_flags, IncludeCondition include_condition) const {
vector<shared_ptr<const QuestCategoryIndex::Category>> ret;
for (const auto& cat : this->category_index->categories) {
if (cat->check_flag(menu_type) && !this->filter(episode, version_flags, cat->category_id, include_condition, 1).empty()) {
@@ -852,9 +827,8 @@ vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filt
}
string encode_download_quest_data(const string& compressed_data, size_t decompressed_size, uint32_t encryption_seed) {
// Download quest files are like normal (PRS-compressed) quest files, but they
// are encrypted with PSO V2 encryption (even on V3 / PSO GC), and a small
// header (PSODownloadQuestHeader) is prepended to the encrypted data.
// Download quest files are like normal (PRS-compressed) quest files, but they are encrypted with PSO V2 encryption
// (even on V3 / PSO GC), and a small header (PSODownloadQuestHeader) is prepended to the encrypted data.
if (encryption_seed == 0) {
encryption_seed = phosg::random_object<uint32_t>();
@@ -869,8 +843,7 @@ string encode_download_quest_data(const string& compressed_data, size_t decompre
header->encryption_seed = encryption_seed;
data += compressed_data;
// Add temporary extra bytes if necessary so encryption won't fail - the data
// size must be a multiple of 4 for PSO V2 encryption.
// Add extra bytes if necessary so encryption won't fail; the data size must be a multiple of 4 for PSO V2 encryption
size_t original_size = data.size();
data.resize((data.size() + 3) & (~3));
@@ -882,9 +855,8 @@ string encode_download_quest_data(const string& compressed_data, size_t decompre
}
shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language override_language) const {
// The download flag needs to be set in the bin header, or else the client
// will ignore it when scanning for download quests in an offline game. To set
// this flag, we need to decompress the quest's .bin file, set the flag, then
// The download flag needs to be set in the bin header, or else the client will ignore it when scanning for download
// quests in an offline game. To set this flag, we need to decompress the quest's .bin file, set the flag, then
// recompress it again.
string decompressed_bin = prs_decompress(*this->bin_contents);
@@ -934,8 +906,7 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language overri
string compressed_bin = prs_compress(decompressed_bin);
// Return a new VersionedQuest object with appropriately-processed .bin and
// .dat file contents
// Return a new VersionedQuest object with appropriately-processed .bin and .dat file contents
auto dlq = make_shared<VersionedQuest>(*this);
dlq->bin_contents = make_shared<string>(encode_download_quest_data(compressed_bin, decompressed_bin.size()));
dlq->dat_contents = make_shared<string>(encode_download_quest_data(*this->dat_contents));
@@ -944,20 +915,15 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language overri
return dlq;
}
string decode_gci_data(
const string& data,
ssize_t find_seed_num_threads,
int64_t known_seed,
bool skip_checksum) {
string decode_gci_data(const string& data, ssize_t find_seed_num_threads, int64_t known_seed, bool skip_checksum) {
phosg::StringReader r(data);
const auto& header = r.get<PSOGCIFileHeader>();
header.check();
if (header.is_ep12()) {
const auto& dlq_header = r.get<PSOGCIDLQFileEncryptedHeader>(false);
// Unencrypted GCI files appear to always have zeroes in these fields.
// Encrypted GCI files are highly unlikely to have zeroes in ALL of these
// fields, so assume it's encrypted if any of them are nonzero.
// Unencrypted GCI files appear to always have zeroes in these fields. Encrypted GCI files are highly unlikely to
// have zeroes in ALL of these fields, so assume it's encrypted if any of them are nonzero.
if (dlq_header.round2_seed || dlq_header.checksum || dlq_header.round3_seed) {
if (known_seed >= 0) {
return decrypt_download_quest_data_section<true>(
@@ -1010,14 +976,13 @@ string decode_gci_data(
}
} else {
// The first 0x10 bytes in the data segment appear to be unused. In most
// files I've seen, the last half of it (8 bytes) are duplicates of the
// first 8 bytes of the unscrambled, compressed data, though this is the
// result of an uninitialized memory bug when the client encodes the file
// and not an actual constraint on what should be in these 8 bytes.
// The first 0x10 bytes in the data segment appear to be unused. In most files I've seen, the last half of it (8
// bytes) are duplicates of the first 8 bytes of the unscrambled, compressed data, though this is the result of
// an uninitialized memory bug when the client encodes the file and not an actual constraint on what should be in
// these 8 bytes.
r.skip(16);
// The game treats this field as a 16-byte string (including the \0). The 8
// bytes after it appear to be completely unused.
// The game treats this field as a 16-byte string (including the \0). The 8 bytes after it appear to be
// completely unused.
if (r.readx(15) != "SONICTEAM,SEGA.") {
throw runtime_error("Episode 3 GCI file is not a quest");
}
@@ -1025,9 +990,8 @@ string decode_gci_data(
string decrypted = r.readx(header.data_size - 40);
// For some reason, Sega decided not to encrypt Episode 3 quest files in the
// same way as Episodes 1&2 quest files (see above). Instead, they just
// wrote a fairly trivial XOR loop over the first 0x100 bytes, leaving the
// For some reason, Sega decided not to encrypt Episode 3 quest files in the same way as Episodes 1&2 quest files
// (see above). Instead, they just wrote a fairly trivial XOR loop over the first 0x100 bytes, leaving the
// remaining bytes completely unencrypted (but still compressed).
size_t unscramble_size = min<size_t>(0x100, data.size());
decrypt_trivial_gci_data(decrypted.data(), unscramble_size, 0);
@@ -1046,11 +1010,7 @@ string decode_gci_data(
}
}
string decode_vms_data(
const string& data,
ssize_t find_seed_num_threads,
int64_t known_seed,
bool skip_checksum) {
string decode_vms_data(const string& data, ssize_t find_seed_num_threads, int64_t known_seed, bool skip_checksum) {
phosg::StringReader r(data);
const auto& header = r.get<PSOVMSFileHeader>();
if (!header.checksum_correct()) {
@@ -1065,8 +1025,7 @@ string decode_vms_data(
}
if (known_seed >= 0) {
return decrypt_download_quest_data_section<false>(
data_section, header.data_size, known_seed);
return decrypt_download_quest_data_section<false>(data_section, header.data_size, known_seed);
} else {
if (find_seed_num_threads < 0) {
@@ -1085,10 +1044,9 @@ string decode_dlq_data(const string& data) {
uint32_t decompressed_size = r.get_u32l();
uint32_t key = r.get_u32l();
// The compressed data size does not need to be a multiple of 4, but the V2
// encryption (which is used for all download quests, even in V3) requires the
// data size to be a multiple of 4. We'll just temporarily stick a few bytes
// on the end, then throw them away later if needed.
// The compressed data size does not need to be a multiple of 4, but the V2 encryption (which is used for all
// download quests, even in V3) requires the data size to be a multiple of 4. We'll just temporarily stick a few
// bytes on the end, then throw them away later if needed.
string decrypted = r.read(r.remaining());
PSOV2Encryption encr(key);
size_t original_size = data.size();
@@ -1150,9 +1108,8 @@ static unordered_map<string, string> decode_qst_data_t(const string& data) {
}
} else if (header.command == 0x13 || header.command == 0xA7) {
// We have to allow larger commands here, because it seems some tools
// encoded QST files with BB's extra 4 padding bytes included in the
// command size.
// We have to allow larger commands here, because it seems some tools encoded QST files with BB's extra 4 padding
// bytes included in the command size.
if (header.size < sizeof(HeaderT) + sizeof(S_WriteFile_13_A7)) {
throw runtime_error("qst write file command has incorrect size");
}
@@ -1195,9 +1152,8 @@ static unordered_map<string, string> decode_qst_data_t(const string& data) {
}
unordered_map<string, string> decode_qst_data(const string& data) {
// QST files start with an open file command, but the format differs depending
// on the PSO version that the qst file is for. We can detect the format from
// the first 4 bytes in the file:
// QST files start with an open file command, but the format differs depending on the PSO version that the qst file
// is for. We can detect the format from the first 4 bytes in the file:
// - BB: 58 00 44 00 or 58 00 A6 00
// - PC: 3C 00 44 ?? or 3C 00 A6 ??
// - DC/GC: 44 ?? 3C 00 or A6 ?? 3C 00
@@ -1209,10 +1165,9 @@ unordered_map<string, string> decode_qst_data(const string& data) {
} else if (((signature & 0xFFFFFF00) == 0x3C004400) || ((signature & 0xFFFFFF00) == 0x3C00A600)) {
return decode_qst_data_t<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(data);
} else if (((signature & 0xFF00FFFF) == 0x44003C00) || ((signature & 0xFF00FFFF) == 0xA6003C00)) {
// In PSO DC, the type field is only one byte, but in V3 it's two bytes and
// the filename was shifted over by one byte. To detect this, we check if
// the V3 type field has a reasonable value, and if not, we assume the file
// is for PSO DC.
// In PSO DC, the type field is only one byte, but in V3 it's two bytes and the filename was shifted over by one
// byte. To detect this, we check if the V3 type field has a reasonable value, and if not, we assume the file is
// for PSO DC.
if (r.pget_u16l(sizeof(PSOCommandHeaderDCV3) + offsetof(S_OpenFile_PC_GC_44_A6, type)) > 3) {
return decode_qst_data_t<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(data);
} else {
@@ -1249,8 +1204,7 @@ void add_open_file_command_t(
cmd.filename.encode(filename);
cmd.type = 0;
cmd.file_size = file_size;
// TODO: It'd be nice to have something like w.emplace(...) to avoid copying
// the command structs into the StringWriter.
// TODO: It'd be nice to have something like w.emplace(...) to avoid copying the structs into the StringWriter.
w.put(cmd);
}
@@ -1289,9 +1243,8 @@ void add_write_file_commands_t(
memcpy(cmd.data.data(), &data[z], chunk_size);
cmd.data_size = chunk_size;
w.put(cmd);
// On BB, the write file command size is a multiple of 4 but not a multiple
// of 8; in QST format the implicit extra 4 bytes are apparently stored in
// the file.
// On BB, the write file command size is a multiple of 4 but not a multiple of 8; in QST format the implicit extra
// 4 bytes are apparently stored in the file.
if (bb_alignment) {
w.put_u32(0);
}
@@ -1307,15 +1260,15 @@ string encode_qst_file(
bool is_dlq_encoded) {
phosg::StringWriter w;
// Some tools expect both open file commands at the beginning, hence this
// unfortunate abstraction-breaking.
// Some tools expect both open file commands at the beginning, hence this unfortunate abstraction-breaking.
switch (version) {
case Version::DC_NTE: // DC NTE doesn't support quests, but we support encoding QST files anyway
case Version::DC_11_2000:
case Version::DC_V1:
case Version::DC_V2:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(
w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
for (const auto& it : files) {
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, it.first, *it.second, is_dlq_encoded, false);
@@ -1324,7 +1277,8 @@ string encode_qst_file(
case Version::PC_NTE:
case Version::PC_V2:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(
w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
for (const auto& it : files) {
add_write_file_commands_t<PSOCommandHeaderPC>(w, it.first, *it.second, is_dlq_encoded, false);
@@ -1335,7 +1289,8 @@ string encode_qst_file(
case Version::GC_EP3_NTE:
case Version::GC_EP3:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(
w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
for (const auto& it : files) {
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, it.first, *it.second, is_dlq_encoded, false);
@@ -1343,7 +1298,8 @@ string encode_qst_file(
break;
case Version::XB_V3:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(
w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
for (const auto& it : files) {
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, it.first, *it.second, is_dlq_encoded, false);
@@ -1351,7 +1307,8 @@ string encode_qst_file(
break;
case Version::BB_V4:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(
w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
for (const auto& it : files) {
add_write_file_commands_t<PSOCommandHeaderBB>(w, it.first, *it.second, is_dlq_encoded, true);