add decoder/encoder for AdEnding.rel

This commit is contained in:
Martin Michelsen
2026-02-01 17:29:31 -08:00
parent ef2d9fae03
commit 2429c4d341
3 changed files with 110 additions and 2 deletions
+31 -2
View File
@@ -1628,7 +1628,7 @@ Action a_disassemble_quest_script(
QEdit. If you intend to reassemble the script, after editing it, use the\n\ QEdit. If you intend to reassemble the script, after editing it, use the\n\
--reassembly option to add explicit label numbers and remove offsets and\n\ --reassembly option to add explicit label numbers and remove offsets and\n\
data in code sections. To include script references from the map, use the\n\ data in code sections. To include script references from the map, use the\n\
--map-file=FILENAME option.", --map-file=FILENAME option.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
string data = read_input_data(args); string data = read_input_data(args);
auto version = get_cli_version(args); auto version = get_cli_version(args);
@@ -1720,7 +1720,7 @@ Action a_assemble_quest_script(
Assemble the input quest script (.txt file) into a compressed .bin file\n\ Assemble the input quest script (.txt file) into a compressed .bin file\n\
usable as an online quest script. If --decompressed is given, produces an\n\ usable as an online quest script. If --decompressed is given, produces an\n\
uncompressed .bind file instead. If --disable-strict is given, allows some\n\ uncompressed .bind file instead. If --disable-strict is given, allows some\n\
invalid behaviors (e.g. calling an undefined label by number).", invalid behaviors (e.g. calling an undefined label by number).\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
string text = read_input_data(args); string text = read_input_data(args);
@@ -2035,6 +2035,35 @@ Action a_encode_unicode_text_set(
write_output_data(args, encoded.data(), encoded.size(), "prs"); write_output_data(args, encoded.data(), encoded.size(), "prs");
}); });
Action a_decode_credits_text_archive(
"decode-credits-text-archive", "\
decode-credits-text-archive [OPTIONS] [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
Decode a credits text archive (AdEnding.rel) to JSON. Use the --big-endian\n\
option if the file is for PSO GC.\n",
+[](phosg::Arguments& args) {
auto ret = decode_credits_text_set(read_input_data(args), args.get<bool>("big-endian"));
auto json = phosg::JSON::list();
for (const auto& it : ret) {
json.emplace_back(it);
}
string out_data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY | phosg::JSON::SerializeOption::EXPAND_LEAF_CONTAINERS);
write_output_data(args, out_data.data(), out_data.size(), "json");
});
Action a_encode_credits_text_archive(
"encode-credits-text-archive", "\
encode-credits-text-archive [OPTIONS] [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
Encode a credits text archive (AdEnding.rel) from JSON. Use the\n\
--big-endian option if the file is for PSO GC.\n",
+[](phosg::Arguments& args) {
auto json = phosg::JSON::parse(read_input_data(args));
std::vector<std::string> data;
for (const auto& it : json.as_list()) {
data.emplace_back(it->as_string());
}
auto ret = encode_credits_text_set(data, args.get<bool>("big-endian"));
write_output_data(args, ret.data(), ret.size(), "rel");
});
Action a_decode_word_select_set( Action a_decode_word_select_set(
"decode-word-select-set", "\ "decode-word-select-set", "\
decode-word-select-set [INPUT-FILENAME]\n\ decode-word-select-set [INPUT-FILENAME]\n\
+76
View File
@@ -551,3 +551,79 @@ std::shared_ptr<const TextSet> TextIndex::get(Version version, Language language
uint32_t TextIndex::key_for_set(Version version, Language language) { uint32_t TextIndex::key_for_set(Version version, Language language) {
return (static_cast<uint32_t>(version) << 8) | static_cast<size_t>(language); return (static_cast<uint32_t>(version) << 8) | static_cast<size_t>(language);
} }
template <bool BE>
std::vector<std::string> decode_credits_text_set_t(const std::string& data) {
std::vector<std::string> ret;
phosg::StringReader r(data);
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
r.go(footer.root_offset);
for (;;) {
ret.emplace_back(tt_sega_sjis_to_utf8(r.pget_cstr(r.get<U32T<BE>>())));
if (!ret.back().empty() && (ret.back()[0] == '*')) {
break;
}
}
return ret;
}
std::vector<std::string> decode_credits_text_set(const std::string& data, bool big_endian) {
if (big_endian) {
return decode_credits_text_set_t<true>(data);
} else {
return decode_credits_text_set_t<false>(data);
}
}
template <bool BE>
std::string encode_credits_text_set_t(const std::vector<std::string>& data) {
if (data.empty() || (data.back() != "*")) {
throw std::runtime_error("the last string in a credits text set must be \"*\"");
}
phosg::StringWriter strings_w;
phosg::StringWriter offsets_w;
std::unordered_map<std::string, uint32_t> existing_offsets;
for (const auto& str : data) {
try {
offsets_w.put<U32T<BE>>(existing_offsets.at(str));
} catch (const std::out_of_range&) {
existing_offsets.emplace(str, strings_w.size());
offsets_w.put<U32T<BE>>(strings_w.size());
strings_w.write(tt_utf8_to_sega_sjis(str));
strings_w.put_u8(0);
while (strings_w.size() & 3) {
strings_w.put_u8(0);
}
}
}
phosg::StringWriter w;
RELFileFooterT<BE> footer;
w.write(strings_w.str());
footer.root_offset = w.size();
w.write(offsets_w.str());
while (w.size() & 0x1F) {
w.put_u8(0);
}
footer.relocations_offset = w.size();
footer.num_relocations = data.size();
w.put<U16T<BE>>(strings_w.size() / 4);
for (size_t z = 1; z < data.size(); z++) {
w.put<U16T<BE>>(1);
}
while (w.size() & 0x1F) {
w.put_u8(0);
}
w.put(footer);
return std::move(w.str());
}
std::string encode_credits_text_set(const std::vector<std::string>& data, bool big_endian) {
if (big_endian) {
return encode_credits_text_set_t<true>(data);
} else {
return encode_credits_text_set_t<false>(data);
}
}
+3
View File
@@ -109,3 +109,6 @@ protected:
phosg::PrefixedLogger log; phosg::PrefixedLogger log;
std::unordered_map<uint32_t, std::shared_ptr<const TextSet>> sets; std::unordered_map<uint32_t, std::shared_ptr<const TextSet>> sets;
}; };
std::vector<std::string> decode_credits_text_set(const std::string& data, bool big_endian);
std::string encode_credits_text_set(const std::vector<std::string>& data, bool big_endian);