rewrite ItemNameIndex and index all game text
This commit is contained in:
+3
-3
@@ -295,7 +295,7 @@ static void server_command_quest(shared_ptr<Client> c, const std::string& args)
|
||||
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
auto q = s->quest_index_for_version(effective_version)->get(stoul(args));
|
||||
auto q = s->quest_index(effective_version)->get(stoul(args));
|
||||
if (!q) {
|
||||
send_text_message(c, "$C6Quest not found");
|
||||
} else {
|
||||
@@ -1611,7 +1611,7 @@ static void server_command_item(shared_ptr<Client> c, const std::string& args) {
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(l, c);
|
||||
|
||||
ItemData item = s->item_name_index->parse_item_description(c->version(), args);
|
||||
ItemData item = s->parse_item_description(c->version(), args);
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
|
||||
if ((l->drop_mode == Lobby::DropMode::SERVER_PRIVATE) || (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE)) {
|
||||
@@ -1644,7 +1644,7 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
|
||||
bool set_drop = (!args.empty() && (args[0] == '!'));
|
||||
|
||||
ItemData item = s->item_name_index->parse_item_description(ses->version(), (set_drop ? args.substr(1) : args));
|
||||
ItemData item = s->parse_item_description(ses->version(), (set_drop ? args.substr(1) : args));
|
||||
item.id = random_object<uint32_t>() | 0x80000000;
|
||||
|
||||
if (set_drop) {
|
||||
|
||||
@@ -1021,3 +1021,46 @@ void Client::use_character_bank(int8_t index) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::print_inventory(FILE* stream) const {
|
||||
auto p = this->character();
|
||||
shared_ptr<const ItemNameIndex> name_index;
|
||||
try {
|
||||
name_index = this->require_server_state()->item_name_index(this->version());
|
||||
} catch (const runtime_error&) {
|
||||
}
|
||||
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", p->disp.stats.meseta.load());
|
||||
fprintf(stream, "[PlayerInventory] %hhu items\n", p->inventory.num_items);
|
||||
for (size_t x = 0; x < p->inventory.num_items; x++) {
|
||||
const auto& item = p->inventory.items[x];
|
||||
auto hex = item.data.hex();
|
||||
if (name_index) {
|
||||
auto name = name_index->describe_item(item.data);
|
||||
fprintf(stream, "[PlayerInventory] %2zu: [+%08" PRIX32 "] %s (%s)\n", x, item.flags.load(), hex.c_str(), name.c_str());
|
||||
} else {
|
||||
fprintf(stream, "[PlayerInventory] %2zu: [+%08" PRIX32 "] %s\n", x, item.flags.load(), hex.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::print_bank(FILE* stream) const {
|
||||
auto p = this->character();
|
||||
shared_ptr<const ItemNameIndex> name_index;
|
||||
try {
|
||||
name_index = this->require_server_state()->item_name_index(this->version());
|
||||
} catch (const runtime_error&) {
|
||||
}
|
||||
fprintf(stream, "[PlayerBank] Meseta: %" PRIu32 "\n", p->bank.meseta.load());
|
||||
fprintf(stream, "[PlayerBank] %" PRIu32 " items\n", p->bank.num_items.load());
|
||||
for (size_t x = 0; x < p->bank.num_items; x++) {
|
||||
const auto& item = p->bank.items[x];
|
||||
const char* present_token = item.present ? "" : " (missing present flag)";
|
||||
auto hex = item.data.hex();
|
||||
if (name_index) {
|
||||
auto name = name_index->describe_item(item.data);
|
||||
fprintf(stream, "[PlayerBank] %3zu: %s (%s) (x%hu)%s\n", x, hex.c_str(), name.c_str(), item.amount.load(), present_token);
|
||||
} else {
|
||||
fprintf(stream, "[PlayerBank] %3zu: %s (x%hu)%s\n", x, hex.c_str(), item.amount.load(), present_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +349,9 @@ public:
|
||||
void use_character_bank(int8_t bb_character_index);
|
||||
void use_default_bank();
|
||||
|
||||
void print_inventory(FILE* stream) const;
|
||||
void print_bank(FILE* stream) const;
|
||||
|
||||
private:
|
||||
// The overlay character data is used in battle and challenge modes, when
|
||||
// character data is temporarily replaced in-game. In other play modes and in
|
||||
|
||||
@@ -764,7 +764,7 @@ string CardDefinition::Effect::str_for_arg(const string& arg) {
|
||||
}
|
||||
}
|
||||
|
||||
string CardDefinition::Effect::str(const char* separator, const TextArchive* text_archive) const {
|
||||
string CardDefinition::Effect::str(const char* separator, const TextSet* text_archive) const {
|
||||
vector<string> tokens;
|
||||
tokens.emplace_back(string_printf("%hhu:", this->effect_num));
|
||||
{
|
||||
@@ -802,7 +802,7 @@ string CardDefinition::Effect::str(const char* separator, const TextArchive* tex
|
||||
const char* name = nullptr;
|
||||
if (this->name_index && text_archive) {
|
||||
try {
|
||||
name = text_archive->get_string(45, this->name_index).c_str();
|
||||
name = text_archive->get(45, this->name_index).c_str();
|
||||
} catch (const exception&) {
|
||||
}
|
||||
}
|
||||
@@ -1061,7 +1061,7 @@ static const char* name_for_assist_ai_param_target(uint8_t target) {
|
||||
}
|
||||
}
|
||||
|
||||
string CardDefinition::str(bool single_line, const TextArchive* text_archive) const {
|
||||
string CardDefinition::str(bool single_line, const TextSet* text_archive) const {
|
||||
string type_str;
|
||||
try {
|
||||
type_str = name_for_card_type(this->type);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#include "../PlayerSubordinates.hh"
|
||||
#include "../Text.hh"
|
||||
#include "../TextArchive.hh"
|
||||
#include "../TextIndex.hh"
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
@@ -514,7 +514,7 @@ struct CardDefinition {
|
||||
|
||||
bool is_empty() const;
|
||||
static std::string str_for_arg(const std::string& arg);
|
||||
std::string str(const char* separator = ", ", const TextArchive* text_archive = nullptr) const;
|
||||
std::string str(const char* separator = ", ", const TextSet* text_archive = nullptr) const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* 0000 */ be_uint32_t card_id;
|
||||
@@ -780,7 +780,7 @@ struct CardDefinition {
|
||||
CardClass card_class() const;
|
||||
|
||||
void decode_range();
|
||||
std::string str(bool single_line = true, const TextArchive* text_archive = nullptr) const;
|
||||
std::string str(bool single_line = true, const TextSet* text_archive = nullptr) const;
|
||||
} __attribute__((packed)); // 0x128 bytes in total
|
||||
|
||||
struct CardDefinitionsFooter {
|
||||
|
||||
+74
-65
@@ -4,37 +4,67 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
ItemNameIndex::ItemNameIndex(JSON&& v2_names, JSON&& v3_names, JSON&& v4_names) {
|
||||
auto get_or_create_meta = [&](uint32_t primary_identifier) {
|
||||
shared_ptr<ItemMetadata> meta;
|
||||
try {
|
||||
return this->primary_identifier_index.at(primary_identifier);
|
||||
} catch (const out_of_range&) {
|
||||
auto meta = make_shared<ItemMetadata>();
|
||||
meta->primary_identifier = primary_identifier;
|
||||
this->primary_identifier_index.emplace(primary_identifier, meta);
|
||||
return meta;
|
||||
// class ItemNameIndex {
|
||||
// public:
|
||||
// ItemNameIndex(std::shared_ptr<const ItemParameterTable> pmt, const std::vector<std::string>& name_coll);
|
||||
// std::string describe_item(const ItemData& item, bool include_color_escapes = false) const;
|
||||
// ItemData parse_item_description(const std::string& description) const;
|
||||
// private:
|
||||
// ItemData parse_item_description_phase(const std::string& description, bool skip_special) const;
|
||||
// std::shared_ptr<const ItemParameterTable> item_parameter_table;
|
||||
// struct ItemMetadata {
|
||||
// uint32_t primary_identifier;
|
||||
// std::string name;
|
||||
// };
|
||||
// std::unordered_map<uint32_t, std::shared_ptr<ItemMetadata>> primary_identifier_indexes;
|
||||
// std::map<std::string, std::shared_ptr<ItemMetadata>> name_indexes;
|
||||
// };
|
||||
|
||||
ItemNameIndex::ItemNameIndex(
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
const std::vector<std::string>& name_coll)
|
||||
: item_parameter_table(item_parameter_table) {
|
||||
|
||||
auto find_items_1d = [&](uint64_t data1, size_t position) -> size_t {
|
||||
ItemData item(data1, 0);
|
||||
for (size_t x = 0; x < 0x100; x++) {
|
||||
item.data1[position] = x;
|
||||
uint32_t id;
|
||||
try {
|
||||
id = this->item_parameter_table->get_item_id(item);
|
||||
} catch (const out_of_range&) {
|
||||
return x;
|
||||
}
|
||||
const string* name = nullptr;
|
||||
try {
|
||||
name = &name_coll.at(id);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
if (name) {
|
||||
auto meta = make_shared<ItemMetadata>();
|
||||
meta->primary_identifier = item.primary_identifier();
|
||||
meta->name = *name;
|
||||
this->primary_identifier_index.emplace(meta->primary_identifier, meta);
|
||||
this->name_index.emplace(tolower(meta->name), meta);
|
||||
}
|
||||
}
|
||||
return 0x100;
|
||||
};
|
||||
auto find_items_2d = [&](uint64_t data1) {
|
||||
for (size_t x = 0; x < 0x100; x++) {
|
||||
if (find_items_1d(data1 | (static_cast<uint64_t>(x) << 48), 2) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& it : v2_names.as_dict()) {
|
||||
uint32_t primary_identifier = stoul(it.first, nullptr, 16);
|
||||
auto meta = get_or_create_meta(primary_identifier);
|
||||
meta->v2_name = std::move(it.second->as_string());
|
||||
this->v2_name_index.emplace(tolower(meta->v2_name), meta);
|
||||
}
|
||||
for (const auto& it : v3_names.as_dict()) {
|
||||
uint32_t primary_identifier = stoul(it.first, nullptr, 16);
|
||||
auto meta = get_or_create_meta(primary_identifier);
|
||||
meta->v3_name = std::move(it.second->as_string());
|
||||
this->v3_name_index.emplace(tolower(meta->v3_name), meta);
|
||||
}
|
||||
for (const auto& it : v4_names.as_dict()) {
|
||||
uint32_t primary_identifier = stoul(it.first, nullptr, 16);
|
||||
auto meta = get_or_create_meta(primary_identifier);
|
||||
meta->v4_name = std::move(it.second->as_string());
|
||||
this->v4_name_index.emplace(tolower(meta->v4_name), meta);
|
||||
}
|
||||
find_items_2d(0x0000000000000000);
|
||||
find_items_1d(0x0101000000000000, 2);
|
||||
find_items_1d(0x0102000000000000, 2);
|
||||
find_items_1d(0x0103000000000000, 2);
|
||||
find_items_1d(0x0200000000000000, 1);
|
||||
find_items_2d(0x0300000000000000);
|
||||
}
|
||||
|
||||
static const char* s_rank_name_characters = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
|
||||
@@ -106,11 +136,10 @@ const array<const char*, 0x11> name_for_s_rank_special = {
|
||||
};
|
||||
|
||||
std::string ItemNameIndex::describe_item(
|
||||
Version version,
|
||||
const ItemData& item,
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table) const {
|
||||
bool include_color_escapes) const {
|
||||
if (item.data1[0] == 0x04) {
|
||||
return string_printf("%s%" PRIu32 " Meseta", item_parameter_table ? "$C7" : "", item.data2d.load());
|
||||
return string_printf("%s%" PRIu32 " Meseta", include_color_escapes ? "$C7" : "", item.data2d.load());
|
||||
}
|
||||
|
||||
vector<string> ret_tokens;
|
||||
@@ -172,18 +201,7 @@ std::string ItemNameIndex::describe_item(
|
||||
} else {
|
||||
try {
|
||||
auto meta = this->primary_identifier_index.at(primary_identifier);
|
||||
const string* name;
|
||||
if (is_v4(version)) {
|
||||
name = &meta->v4_name;
|
||||
} else if (is_v3(version)) {
|
||||
name = &meta->v3_name;
|
||||
} else {
|
||||
name = &meta->v2_name;
|
||||
}
|
||||
if (name->empty()) {
|
||||
throw out_of_range("item does not exist");
|
||||
}
|
||||
ret_tokens.emplace_back(*name);
|
||||
ret_tokens.emplace_back(meta->name);
|
||||
|
||||
} catch (const out_of_range&) {
|
||||
ret_tokens.emplace_back(string_printf("!ID:%06" PRIX32, primary_identifier));
|
||||
@@ -347,10 +365,10 @@ std::string ItemNameIndex::describe_item(
|
||||
}
|
||||
|
||||
string ret = join(ret_tokens, " ");
|
||||
if (item_parameter_table) {
|
||||
if (include_color_escapes) {
|
||||
if (item.is_s_rank_weapon()) {
|
||||
return "$C4" + ret;
|
||||
} else if (item_parameter_table->is_item_rare(item)) {
|
||||
} else if (this->item_parameter_table->is_item_rare(item)) {
|
||||
return "$C6" + ret;
|
||||
} else if (item.has_bonuses()) {
|
||||
return "$C2" + ret;
|
||||
@@ -362,23 +380,23 @@ std::string ItemNameIndex::describe_item(
|
||||
}
|
||||
}
|
||||
|
||||
ItemData ItemNameIndex::parse_item_description(Version version, const std::string& desc) const {
|
||||
ItemData ItemNameIndex::parse_item_description(const std::string& desc) const {
|
||||
ItemData ret;
|
||||
try {
|
||||
ret = this->parse_item_description_phase(version, desc, false);
|
||||
ret = this->parse_item_description_phase(desc, false);
|
||||
} catch (const exception& e1) {
|
||||
try {
|
||||
ret = this->parse_item_description_phase(version, desc, true);
|
||||
ret = this->parse_item_description_phase(desc, true);
|
||||
} catch (const exception& e2) {
|
||||
try {
|
||||
ret = ItemData::from_data(parse_data_string(desc));
|
||||
} catch (const exception& ed) {
|
||||
if (strcmp(e1.what(), e2.what())) {
|
||||
throw runtime_error(string_printf("cannot parse item description \"%s\" in %s (as text 1: %s) (as text 2: %s) (as data: %s)",
|
||||
desc.c_str(), name_for_enum(version), e1.what(), e2.what(), ed.what()));
|
||||
throw runtime_error(string_printf("cannot parse item description \"%s\" (as text 1: %s) (as text 2: %s) (as data: %s)",
|
||||
desc.c_str(), e1.what(), e2.what(), ed.what()));
|
||||
} else {
|
||||
throw runtime_error(string_printf("cannot parse item description \"%s\" in %s (as text: %s) (as data: %s)",
|
||||
desc.c_str(), name_for_enum(version), e1.what(), ed.what()));
|
||||
throw runtime_error(string_printf("cannot parse item description \"%s\" (as text: %s) (as data: %s)",
|
||||
desc.c_str(), e1.what(), ed.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,7 +405,7 @@ ItemData ItemNameIndex::parse_item_description(Version version, const std::strin
|
||||
return ret;
|
||||
}
|
||||
|
||||
ItemData ItemNameIndex::parse_item_description_phase(Version version, const std::string& description, bool skip_special) const {
|
||||
ItemData ItemNameIndex::parse_item_description_phase(const std::string& description, bool skip_special) const {
|
||||
ItemData ret;
|
||||
ret.data1d.clear(0);
|
||||
ret.id = 0xFFFFFFFF;
|
||||
@@ -448,24 +466,15 @@ ItemData ItemNameIndex::parse_item_description_phase(Version version, const std:
|
||||
}
|
||||
}
|
||||
|
||||
const map<string, shared_ptr<ItemMetadata>>* name_index;
|
||||
if (is_v4(version)) {
|
||||
name_index = &this->v4_name_index;
|
||||
} else if (is_v3(version)) {
|
||||
name_index = &this->v3_name_index;
|
||||
} else {
|
||||
name_index = &this->v2_name_index;
|
||||
}
|
||||
|
||||
auto name_it = name_index->lower_bound(desc);
|
||||
auto name_it = this->name_index.lower_bound(desc);
|
||||
// Look up to 3 places before the lower bound. We have to do this to catch
|
||||
// cases like Sange vs. Sange & Yasha - if the input is like "Sange 0/...",
|
||||
// then we'll see Sange & Yasha first, which we should skip.
|
||||
size_t lookback = 0;
|
||||
while (lookback < 4) {
|
||||
if (name_it != name_index->end() && desc.starts_with(name_it->first)) {
|
||||
if (name_it != this->name_index.end() && desc.starts_with(name_it->first)) {
|
||||
break;
|
||||
} else if (name_it == name_index->begin()) {
|
||||
} else if (name_it == this->name_index.begin()) {
|
||||
throw runtime_error("no such item");
|
||||
} else {
|
||||
name_it--;
|
||||
|
||||
+24
-18
@@ -14,26 +14,32 @@
|
||||
|
||||
class ItemNameIndex {
|
||||
public:
|
||||
ItemNameIndex(JSON&& v2_names, JSON&& v3_names, JSON&& v4_names);
|
||||
|
||||
std::string describe_item(
|
||||
Version version,
|
||||
const ItemData& item,
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table = nullptr) const;
|
||||
ItemData parse_item_description(Version version, const std::string& description) const;
|
||||
|
||||
private:
|
||||
ItemData parse_item_description_phase(Version version, const std::string& description, bool skip_special) const;
|
||||
|
||||
struct ItemMetadata {
|
||||
uint32_t primary_identifier;
|
||||
std::string v2_name;
|
||||
std::string v3_name;
|
||||
std::string v4_name;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, std::shared_ptr<ItemMetadata>> primary_identifier_index;
|
||||
std::map<std::string, std::shared_ptr<ItemMetadata>> v2_name_index;
|
||||
std::map<std::string, std::shared_ptr<ItemMetadata>> v3_name_index;
|
||||
std::map<std::string, std::shared_ptr<ItemMetadata>> v4_name_index;
|
||||
ItemNameIndex(std::shared_ptr<const ItemParameterTable> pmt, const std::vector<std::string>& name_coll);
|
||||
|
||||
inline size_t entry_count() const {
|
||||
return this->primary_identifier_index.size();
|
||||
}
|
||||
|
||||
inline const std::unordered_map<uint32_t, std::shared_ptr<const ItemMetadata>>& all_by_primary_identifier() const {
|
||||
return this->primary_identifier_index;
|
||||
}
|
||||
inline const std::map<std::string, std::shared_ptr<const ItemMetadata>>& all_by_name() const {
|
||||
return this->name_index;
|
||||
}
|
||||
|
||||
std::string describe_item(const ItemData& item, bool include_color_escapes = false) const;
|
||||
ItemData parse_item_description(const std::string& description) const;
|
||||
|
||||
private:
|
||||
ItemData parse_item_description_phase(const std::string& description, bool skip_special) const;
|
||||
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table;
|
||||
|
||||
std::unordered_map<uint32_t, std::shared_ptr<const ItemMetadata>> primary_identifier_index;
|
||||
std::map<std::string, std::shared_ptr<const ItemMetadata>> name_index;
|
||||
};
|
||||
|
||||
+46
-43
@@ -52,13 +52,13 @@ ItemParameterTable::ItemParameterTable(shared_ptr<const string> data, Version ve
|
||||
|
||||
const ItemParameterTable::WeaponV4& ItemParameterTable::get_weapon(uint8_t data1_1, uint8_t data1_2) const {
|
||||
if (data1_1 >= this->num_weapon_classes) {
|
||||
throw runtime_error("weapon ID out of range");
|
||||
throw out_of_range("weapon ID out of range");
|
||||
}
|
||||
|
||||
if (this->offsets_v4) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->weapon_table + sizeof(ArrayRefLE) * data1_1);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("weapon ID out of range");
|
||||
throw out_of_range("weapon ID out of range");
|
||||
}
|
||||
return this->r.pget<WeaponV4>(co.offset + sizeof(WeaponV4) * data1_2);
|
||||
}
|
||||
@@ -67,12 +67,12 @@ const ItemParameterTable::WeaponV4& ItemParameterTable::get_weapon(uint8_t data1
|
||||
try {
|
||||
return this->parsed_weapons.at(key);
|
||||
} catch (const std::out_of_range&) {
|
||||
auto& def_v4 = this->parsed_weapons.emplace(key, WeaponV4{}).first->second;
|
||||
WeaponV4 def_v4;
|
||||
|
||||
if (this->offsets_v2) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->weapon_table + sizeof(ArrayRefLE) * data1_1);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("weapon ID out of range");
|
||||
throw out_of_range("weapon ID out of range");
|
||||
}
|
||||
const auto& def_v2 = this->r.pget<WeaponV2>(co.offset + sizeof(WeaponV2) * data1_2);
|
||||
def_v4.base.id = def_v2.base.id;
|
||||
@@ -90,7 +90,7 @@ const ItemParameterTable::WeaponV4& ItemParameterTable::get_weapon(uint8_t data1
|
||||
} else if (this->offsets_v3) {
|
||||
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->weapon_table + sizeof(ArrayRefBE) * data1_1);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("weapon ID out of range");
|
||||
throw out_of_range("weapon ID out of range");
|
||||
}
|
||||
const auto& def_v3 = this->r.pget<WeaponV3<true>>(co.offset + sizeof(WeaponV3<true>) * data1_2);
|
||||
def_v4.base.id = def_v3.base.id.load();
|
||||
@@ -126,19 +126,19 @@ const ItemParameterTable::WeaponV4& ItemParameterTable::get_weapon(uint8_t data1
|
||||
throw logic_error("table is not v2, v3, or v4");
|
||||
}
|
||||
|
||||
return def_v4;
|
||||
return this->parsed_weapons.emplace(key, def_v4).first->second;
|
||||
}
|
||||
}
|
||||
|
||||
const ItemParameterTable::ArmorOrShieldV4& ItemParameterTable::get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const {
|
||||
if ((data1_1 < 1) || (data1_1 > 2)) {
|
||||
throw runtime_error("armor/shield class ID out of range");
|
||||
throw out_of_range("armor/shield class ID out of range");
|
||||
}
|
||||
|
||||
if (this->offsets_v4) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->armor_table + sizeof(ArrayRefLE) * (data1_1 - 1));
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("armor/shield ID out of range");
|
||||
throw out_of_range("armor/shield ID out of range");
|
||||
}
|
||||
return this->r.pget<ArmorOrShieldV4>(co.offset + sizeof(ArmorOrShieldV4) * data1_2);
|
||||
}
|
||||
@@ -151,15 +151,12 @@ const ItemParameterTable::ArmorOrShieldV4& ItemParameterTable::get_armor_or_shie
|
||||
}
|
||||
return ret;
|
||||
} catch (const std::out_of_range&) {
|
||||
if (data1_2 >= parsed_vec.size()) {
|
||||
parsed_vec.resize(data1_2 + 1);
|
||||
}
|
||||
auto& def_v4 = parsed_vec[data1_2];
|
||||
ArmorOrShieldV4 def_v4;
|
||||
|
||||
if (this->offsets_v2) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->armor_table + sizeof(ArrayRefLE) * (data1_1 - 1));
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("armor/shield ID out of range");
|
||||
throw out_of_range("armor/shield ID out of range");
|
||||
}
|
||||
const auto& def_v2 = this->r.pget<ArmorOrShieldV2>(co.offset + sizeof(ArmorOrShieldV2) * data1_2);
|
||||
def_v4.base.id = def_v2.base.id;
|
||||
@@ -183,7 +180,7 @@ const ItemParameterTable::ArmorOrShieldV4& ItemParameterTable::get_armor_or_shie
|
||||
} else if (this->offsets_v3) {
|
||||
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->armor_table + sizeof(ArrayRefBE) * (data1_1 - 1));
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("armor/shield ID out of range");
|
||||
throw out_of_range("armor/shield ID out of range");
|
||||
}
|
||||
const auto& def_v3 = this->r.pget<ArmorOrShieldV3>(co.offset + sizeof(ArmorOrShieldV3) * data1_2);
|
||||
def_v4.base.id = def_v3.base.id.load();
|
||||
@@ -210,7 +207,11 @@ const ItemParameterTable::ArmorOrShieldV4& ItemParameterTable::get_armor_or_shie
|
||||
throw logic_error("table is not v2, v3, or v4");
|
||||
}
|
||||
|
||||
return def_v4;
|
||||
if (data1_2 >= parsed_vec.size()) {
|
||||
parsed_vec.resize(data1_2 + 1);
|
||||
}
|
||||
parsed_vec[data1_2] = def_v4;
|
||||
return parsed_vec[data1_2];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +219,7 @@ const ItemParameterTable::UnitV4& ItemParameterTable::get_unit(uint8_t data1_2)
|
||||
if (this->offsets_v4) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->unit_table);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("unit ID out of range");
|
||||
throw out_of_range("unit ID out of range");
|
||||
}
|
||||
return this->r.pget<UnitV4>(co.offset + sizeof(UnitV4) * data1_2);
|
||||
}
|
||||
@@ -230,15 +231,12 @@ const ItemParameterTable::UnitV4& ItemParameterTable::get_unit(uint8_t data1_2)
|
||||
}
|
||||
return ret;
|
||||
} catch (const std::out_of_range&) {
|
||||
if (data1_2 >= this->parsed_units.size()) {
|
||||
this->parsed_units.resize(data1_2 + 1);
|
||||
}
|
||||
auto& def_v4 = this->parsed_units[data1_2];
|
||||
UnitV4 def_v4;
|
||||
|
||||
if (this->offsets_v2) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->unit_table);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("unit ID out of range");
|
||||
throw out_of_range("unit ID out of range");
|
||||
}
|
||||
const auto& def_v2 = this->r.pget<UnitV2>(co.offset + sizeof(UnitV2) * data1_2);
|
||||
def_v4.base.id = def_v2.base.id;
|
||||
@@ -249,7 +247,7 @@ const ItemParameterTable::UnitV4& ItemParameterTable::get_unit(uint8_t data1_2)
|
||||
} else if (this->offsets_v3) {
|
||||
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->unit_table);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("unit ID out of range");
|
||||
throw out_of_range("unit ID out of range");
|
||||
}
|
||||
const auto& def_v3 = this->r.pget<UnitV3>(co.offset + sizeof(UnitV3) * data1_2);
|
||||
def_v4.base.id = def_v3.base.id.load();
|
||||
@@ -263,7 +261,11 @@ const ItemParameterTable::UnitV4& ItemParameterTable::get_unit(uint8_t data1_2)
|
||||
throw logic_error("table is not v2, v3, or v4");
|
||||
}
|
||||
|
||||
return def_v4;
|
||||
if (data1_2 >= this->parsed_units.size()) {
|
||||
this->parsed_units.resize(data1_2 + 1);
|
||||
}
|
||||
this->parsed_units[data1_2] = def_v4;
|
||||
return this->parsed_units[data1_2];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +273,7 @@ const ItemParameterTable::MagV4& ItemParameterTable::get_mag(uint8_t data1_1) co
|
||||
if (this->offsets_v4) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->mag_table);
|
||||
if (data1_1 >= co.count) {
|
||||
throw runtime_error("mag ID out of range");
|
||||
throw out_of_range("mag ID out of range");
|
||||
}
|
||||
return this->r.pget<MagV4>(co.offset + sizeof(MagV4) * data1_1);
|
||||
}
|
||||
@@ -283,15 +285,12 @@ const ItemParameterTable::MagV4& ItemParameterTable::get_mag(uint8_t data1_1) co
|
||||
}
|
||||
return ret;
|
||||
} catch (const std::out_of_range&) {
|
||||
if (data1_1 >= this->parsed_mags.size()) {
|
||||
this->parsed_mags.resize(data1_1 + 1);
|
||||
}
|
||||
auto& def_v4 = this->parsed_mags[data1_1];
|
||||
MagV4 def_v4;
|
||||
|
||||
if (this->offsets_v2) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->mag_table);
|
||||
if (data1_1 >= co.count) {
|
||||
throw runtime_error("mag ID out of range");
|
||||
throw out_of_range("mag ID out of range");
|
||||
}
|
||||
const auto& def_v2 = this->r.pget<MagV2>(co.offset + sizeof(MagV2) * data1_1);
|
||||
def_v4.base.id = def_v2.base.id;
|
||||
@@ -311,7 +310,7 @@ const ItemParameterTable::MagV4& ItemParameterTable::get_mag(uint8_t data1_1) co
|
||||
} else if (this->offsets_v3) {
|
||||
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->mag_table);
|
||||
if (data1_1 >= co.count) {
|
||||
throw runtime_error("mag ID out of range");
|
||||
throw out_of_range("mag ID out of range");
|
||||
}
|
||||
const auto& def_v3 = this->r.pget<MagV3>(co.offset + sizeof(MagV3) * data1_1);
|
||||
def_v4.base.id = def_v3.base.id.load();
|
||||
@@ -334,19 +333,23 @@ const ItemParameterTable::MagV4& ItemParameterTable::get_mag(uint8_t data1_1) co
|
||||
throw logic_error("table is not v2, v3, or v4");
|
||||
}
|
||||
|
||||
return def_v4;
|
||||
if (data1_1 >= this->parsed_mags.size()) {
|
||||
this->parsed_mags.resize(data1_1 + 1);
|
||||
}
|
||||
this->parsed_mags[data1_1] = def_v4;
|
||||
return this->parsed_mags[data1_1];
|
||||
}
|
||||
}
|
||||
|
||||
const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1, uint8_t data1_2) const {
|
||||
if (data1_1 >= this->num_tool_classes) {
|
||||
throw runtime_error("tool class ID out of range");
|
||||
throw out_of_range("tool class ID out of range");
|
||||
}
|
||||
|
||||
if (this->offsets_v4) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->tool_table + sizeof(ArrayRefLE) * data1_1);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("tool ID out of range");
|
||||
throw out_of_range("tool ID out of range");
|
||||
}
|
||||
return this->r.pget<ToolV4>(co.offset + sizeof(ToolV4) * data1_2);
|
||||
}
|
||||
@@ -355,12 +358,12 @@ const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1,
|
||||
try {
|
||||
return this->parsed_tools.at(key);
|
||||
} catch (const std::out_of_range&) {
|
||||
auto& def_v4 = this->parsed_tools.emplace(key, ToolV4{}).first->second;
|
||||
ToolV4 def_v4;
|
||||
|
||||
if (this->offsets_v2) {
|
||||
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->tool_table + sizeof(ArrayRefLE) * data1_1);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("tool ID out of range");
|
||||
throw out_of_range("tool ID out of range");
|
||||
}
|
||||
const auto& def_v2 = this->r.pget<ToolV2>(co.offset + sizeof(ToolV2) * data1_2);
|
||||
def_v4.base.id = def_v2.base.id;
|
||||
@@ -372,7 +375,7 @@ const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1,
|
||||
} else if (this->offsets_v3) {
|
||||
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->tool_table + sizeof(ArrayRefBE) * data1_1);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("tool ID out of range");
|
||||
throw out_of_range("tool ID out of range");
|
||||
}
|
||||
const auto& def_v3 = this->r.pget<ToolV3>(co.offset + sizeof(ToolV3) * data1_2);
|
||||
def_v4.base.id = def_v3.base.id.load();
|
||||
@@ -387,7 +390,7 @@ const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1,
|
||||
throw logic_error("table is not v2, v3, or v4");
|
||||
}
|
||||
|
||||
return def_v4;
|
||||
return this->parsed_tools.emplace(key, def_v4).first->second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,10 +475,10 @@ float ItemParameterTable::get_sale_divisor(uint8_t data1_0, uint8_t data1_1) con
|
||||
const ItemParameterTable::MagFeedResult& ItemParameterTable::get_mag_feed_result(
|
||||
uint8_t table_index, uint8_t item_index) const {
|
||||
if (table_index >= 8) {
|
||||
throw runtime_error("invalid mag feed table index");
|
||||
throw out_of_range("invalid mag feed table index");
|
||||
}
|
||||
if (item_index >= 11) {
|
||||
throw runtime_error("invalid mag feed item index");
|
||||
throw out_of_range("invalid mag feed item index");
|
||||
}
|
||||
|
||||
uint32_t offset;
|
||||
@@ -521,7 +524,7 @@ uint8_t ItemParameterTable::get_special_stars(uint8_t det) const {
|
||||
const ItemParameterTable::Special<false>& ItemParameterTable::get_special(uint8_t special) const {
|
||||
special &= 0x3F;
|
||||
if (special >= this->num_specials) {
|
||||
throw runtime_error("invalid special index");
|
||||
throw out_of_range("invalid special index");
|
||||
}
|
||||
|
||||
if (this->offsets_v2) {
|
||||
@@ -545,10 +548,10 @@ const ItemParameterTable::Special<false>& ItemParameterTable::get_special(uint8_
|
||||
|
||||
uint8_t ItemParameterTable::get_max_tech_level(uint8_t char_class, uint8_t tech_num) const {
|
||||
if (char_class >= 12) {
|
||||
throw runtime_error("invalid character class");
|
||||
throw out_of_range("invalid character class");
|
||||
}
|
||||
if (tech_num >= 19) {
|
||||
throw runtime_error("invalid technique number");
|
||||
throw out_of_range("invalid technique number");
|
||||
}
|
||||
|
||||
if (this->offsets_v2) {
|
||||
@@ -762,7 +765,7 @@ std::pair<const ItemParameterTable::EventItem*, size_t> ItemParameterTable::get_
|
||||
uint32_t base_offset, uint8_t event_number) const {
|
||||
const auto& co = this->r.pget<ArrayRef<IsBigEndian>>(base_offset);
|
||||
if (event_number >= co.count) {
|
||||
throw runtime_error("invalid event number");
|
||||
throw out_of_range("invalid event number");
|
||||
}
|
||||
const auto& event_co = this->r.pget<ArrayRef<IsBigEndian>>(co.offset + sizeof(ArrayRef<IsBigEndian>) * event_number);
|
||||
const auto* defs = &this->r.pget<EventItem>(event_co.offset, event_co.count * sizeof(EventItem));
|
||||
|
||||
@@ -369,6 +369,9 @@ private:
|
||||
/* 40 / 5704 */ le_uint32_t shield_effect_table; // -> [8-byte structs]
|
||||
} __attribute__((packed));
|
||||
|
||||
// TODO: The GC NTE ItemPMT format is intermediate between V2 and V3 - the
|
||||
// Offsets struct is 0x50 bytes. Figure it out and add support here.
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct TableOffsetsV3V4 {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
+5
-5
@@ -23,7 +23,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
||||
// Nothing to do (it should be deleted)
|
||||
|
||||
} else if (item_identifier == 0x030200) { // Technique disk
|
||||
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.char_class, item.data.data1[4]);
|
||||
if (item.data.data1[2] > max_level) {
|
||||
throw runtime_error("technique level too high");
|
||||
@@ -43,7 +43,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
||||
// get an accurate picture of what's actually in the player's inventory, so
|
||||
// there's no way to know if we would be enforcing the correct grind limit.
|
||||
if (is_v3_or_later) {
|
||||
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
|
||||
if (weapon.data.data1[3] >= weapon_def.max_grind) {
|
||||
throw runtime_error("weapon already at maximum grind");
|
||||
@@ -168,7 +168,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
||||
|
||||
} else if ((item_identifier & 0xFFFF00) == 0x031500) {
|
||||
// Christmas Present, etc. - use unwrap_table + probabilities therein
|
||||
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
auto table = item_parameter_table->get_event_items(item.data.data1[2]);
|
||||
size_t sum = 0;
|
||||
for (size_t z = 0; z < table.second; z++) {
|
||||
@@ -207,7 +207,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
const auto& combo = item_parameter_table->get_item_combination(item.data, inv_item.data);
|
||||
if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.char_class) {
|
||||
throw runtime_error("item combination requires specific char_class");
|
||||
@@ -497,7 +497,7 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
||||
apply_mag_feed_result(
|
||||
player->inventory.items[mag_item_index].data,
|
||||
player->inventory.items[fed_item_index].data,
|
||||
s->item_parameter_table_for_version(c->version()),
|
||||
s->item_parameter_table(c->version()),
|
||||
s->mag_evolution_table,
|
||||
player->disp.visual.char_class,
|
||||
player->disp.visual.section_id,
|
||||
|
||||
+2
-2
@@ -251,7 +251,7 @@ void Lobby::create_item_creator() {
|
||||
s->tool_random_set,
|
||||
s->weapon_random_sets.at(this->difficulty),
|
||||
s->tekker_adjustment_set,
|
||||
s->item_parameter_table_for_version(this->base_version),
|
||||
s->item_parameter_table(this->base_version),
|
||||
this->base_version,
|
||||
this->episode,
|
||||
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
|
||||
@@ -351,7 +351,7 @@ void Lobby::load_maps() {
|
||||
|
||||
this->log.info("Generated objects list (%zu entries):", this->map->objects.size());
|
||||
for (size_t z = 0; z < this->map->objects.size(); z++) {
|
||||
string o_str = this->map->objects[z].str(s->item_name_index);
|
||||
string o_str = this->map->objects[z].str();
|
||||
this->log.info("(K-%zX) %s", z, o_str.c_str());
|
||||
}
|
||||
this->log.info("Generated enemies list (%zu entries):", this->map->enemies.size());
|
||||
|
||||
+90
-71
@@ -43,8 +43,7 @@
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "TextArchive.hh"
|
||||
#include "UnicodeTextSet.hh"
|
||||
#include "TextIndex.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -955,7 +954,7 @@ Action a_decode_gci(
|
||||
auto decoded = decode_gci_data(read_input_data(args), num_threads, dec_seed, skip_checksum);
|
||||
save_file(input_filename + ".dec", decoded);
|
||||
});
|
||||
Action a_decode_vmg(
|
||||
Action a_decode_vms(
|
||||
"decode-vms", nullptr, +[](Arguments& args) {
|
||||
string input_filename = args.get<string>(1, false);
|
||||
if (input_filename.empty() || (input_filename == "-")) {
|
||||
@@ -1171,25 +1170,35 @@ Action a_decode_sjis(
|
||||
});
|
||||
|
||||
Action a_decode_text_archive(
|
||||
"decode-text-archive", nullptr, +[](Arguments& args) {
|
||||
"decode-text-archive", "\
|
||||
decode-text-archive [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
|
||||
Decode a text archive to JSON. --collections=NUM_COLLECTIONS is given,\n\
|
||||
expects a fixed number of collections in the input. If --has-pr3 is given,\n\
|
||||
expects the input not to have a REL footer.\n",
|
||||
+[](Arguments& args) {
|
||||
string data = read_input_data(args);
|
||||
TextArchive a(data, args.get<bool>("big-endian"));
|
||||
JSON j = a.json();
|
||||
|
||||
unique_ptr<TextSet> ts;
|
||||
size_t collection_count = args.get<size_t>("collections", 0);
|
||||
if (collection_count) {
|
||||
ts = make_unique<BinaryTextSet>(data, collection_count, !args.get<bool>("has-pr3"));
|
||||
} else {
|
||||
ts = make_unique<BinaryTextAndKeyboardsSet>(data, args.get<bool>("big-endian"));
|
||||
}
|
||||
JSON j = ts->json();
|
||||
string out_data = j.serialize(JSON::SerializeOption::FORMAT);
|
||||
write_output_data(args, out_data.data(), out_data.size(), "json");
|
||||
});
|
||||
Action a_encode_text_archive(
|
||||
"encode-text-archive", "\
|
||||
decode-text-archive [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
|
||||
encode-text-archive [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
|
||||
Decode a text archive (e.g. TextEnglish.pr2) to JSON for easy editing, or\n\
|
||||
encode a JSON file to a text archive.\n",
|
||||
Encode a text archive. Currently only supports GC and Xbox format.\n",
|
||||
+[](Arguments& args) {
|
||||
const string& input_filename = args.get<string>(1, false);
|
||||
const string& output_filename = args.get<string>(2, false);
|
||||
|
||||
auto json = JSON::parse(read_input_data(args));
|
||||
TextArchive a(json);
|
||||
BinaryTextAndKeyboardsSet a(json);
|
||||
auto result = a.serialize(args.get<bool>("big-endian"));
|
||||
if (output_filename.empty()) {
|
||||
if (input_filename.empty() || (input_filename == "-")) {
|
||||
@@ -1214,14 +1223,8 @@ Action a_encode_text_archive(
|
||||
|
||||
Action a_decode_unicode_text_set(
|
||||
"decode-unicode-text-set", nullptr, +[](Arguments& args) {
|
||||
auto collections = parse_unicode_text_set(read_input_data(args));
|
||||
JSON j = JSON::list();
|
||||
for (const auto& collection : collections) {
|
||||
JSON& coll_j = j.emplace_back(JSON::list());
|
||||
for (const auto& s : collection) {
|
||||
coll_j.emplace_back(s);
|
||||
}
|
||||
}
|
||||
UnicodeTextSet uts(read_input_data(args));
|
||||
JSON j = uts.json();
|
||||
string out_data = j.serialize(JSON::SerializeOption::FORMAT);
|
||||
write_output_data(args, out_data.data(), out_data.size(), "json");
|
||||
});
|
||||
@@ -1232,15 +1235,8 @@ Action a_encode_unicode_text_set(
|
||||
Decode a Unicode text set (e.g. unitxt_e.prs) to JSON for easy editing, or\n\
|
||||
encode a JSON file to a Unicode text set.\n",
|
||||
+[](Arguments& args) {
|
||||
auto json = JSON::parse(read_input_data(args));
|
||||
vector<vector<string>> collections;
|
||||
for (const auto& coll_json : json.as_list()) {
|
||||
auto& collection = collections.emplace_back();
|
||||
for (const auto& s_json : coll_json->as_list()) {
|
||||
collection.emplace_back(std::move(s_json->as_string()));
|
||||
}
|
||||
}
|
||||
string encoded = serialize_unicode_text_set(collections);
|
||||
UnicodeTextSet uts(JSON::parse(read_input_data(args)));
|
||||
string encoded = uts.serialize();
|
||||
write_output_data(args, encoded.data(), encoded.size(), "prs");
|
||||
});
|
||||
|
||||
@@ -1256,30 +1252,22 @@ Action a_decode_word_select_set(
|
||||
auto version = get_cli_version(args);
|
||||
|
||||
string unitxt_filename = args.get<string>("unitxt");
|
||||
vector<string> unitxt_collection;
|
||||
const vector<string>* unitxt_collection;
|
||||
if (!unitxt_filename.empty()) {
|
||||
vector<vector<string>> unitxt_data;
|
||||
unique_ptr<UnicodeTextSet> uts;
|
||||
if (ends_with(unitxt_filename, ".prs")) {
|
||||
unitxt_data = parse_unicode_text_set(load_file(unitxt_filename));
|
||||
uts = make_unique<UnicodeTextSet>(load_file(unitxt_filename));
|
||||
} else if (ends_with(unitxt_filename, ".json")) {
|
||||
auto json = JSON::parse(load_file(unitxt_filename));
|
||||
for (const auto& coll_it : json.as_list()) {
|
||||
auto& coll = unitxt_data.emplace_back();
|
||||
for (const auto& str_it : coll_it->as_list()) {
|
||||
coll.emplace_back(str_it->as_string());
|
||||
}
|
||||
}
|
||||
uts = make_unique<UnicodeTextSet>(JSON::parse(load_file(unitxt_filename)));
|
||||
} else {
|
||||
throw runtime_error("unitxt filename must end in .prs or .json");
|
||||
}
|
||||
if (version == Version::BB_V4) {
|
||||
unitxt_collection = std::move(unitxt_data.at(0));
|
||||
} else {
|
||||
unitxt_collection = std::move(unitxt_data.at(35));
|
||||
}
|
||||
unitxt_collection = &uts->get((version == Version::BB_V4) ? 0 : 35);
|
||||
} else {
|
||||
unitxt_collection = nullptr;
|
||||
}
|
||||
|
||||
WordSelectSet ws(read_input_data(args), version, &unitxt_collection, args.get<bool>("japanese"));
|
||||
WordSelectSet ws(read_input_data(args), version, unitxt_collection, args.get<bool>("japanese"));
|
||||
ws.print(stdout);
|
||||
});
|
||||
Action a_print_word_select_table(
|
||||
@@ -1289,17 +1277,18 @@ Action a_print_word_select_table(
|
||||
given, prints the table sorted by token ID for that version. If no version\n\
|
||||
option is given, prints the token table sorted by canonical name.\n",
|
||||
+[](Arguments& args) {
|
||||
auto table = ServerState::load_word_select_table_from_system();
|
||||
Version v = Version::UNKNOWN;
|
||||
ServerState s;
|
||||
s.load_objects("word_select_table");
|
||||
Version v;
|
||||
try {
|
||||
v = get_cli_version(args);
|
||||
} catch (const runtime_error&) {
|
||||
v = Version::UNKNOWN;
|
||||
}
|
||||
|
||||
if (v != Version::UNKNOWN) {
|
||||
table->print_index(stdout, v);
|
||||
s.word_select_table->print_index(stdout, v);
|
||||
} else {
|
||||
table->print(stdout);
|
||||
s.word_select_table->print(stdout);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1343,21 +1332,20 @@ Action a_convert_rare_item_set(
|
||||
.afs (PSO V2 little-endian AFS archive)\n\
|
||||
.rel (Schtserv rare table; cannot be used in output filename)\n",
|
||||
+[](Arguments& args) {
|
||||
auto name_index = make_shared<ItemNameIndex>(
|
||||
JSON::parse(load_file("system/item-tables/names-v2.json")),
|
||||
JSON::parse(load_file("system/item-tables/names-v3.json")),
|
||||
JSON::parse(load_file("system/item-tables/names-v4.json")));
|
||||
auto version = get_cli_version(args);
|
||||
|
||||
ServerState s;
|
||||
s.load_objects("item_name_indexes");
|
||||
|
||||
string input_filename = args.get<string>(1, false);
|
||||
if (input_filename.empty() || (input_filename == "-")) {
|
||||
throw runtime_error("input filename must be given");
|
||||
}
|
||||
auto version = get_cli_version(args);
|
||||
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
shared_ptr<RareItemSet> rs;
|
||||
if (ends_with(input_filename, ".json")) {
|
||||
rs = make_shared<RareItemSet>(JSON::parse(*data), version, name_index);
|
||||
rs = make_shared<RareItemSet>(JSON::parse(*data), s.item_name_index(version));
|
||||
} else if (ends_with(input_filename, ".gsl")) {
|
||||
rs = make_shared<RareItemSet>(GSLArchive(data, false), false);
|
||||
} else if (ends_with(input_filename, ".gslb")) {
|
||||
@@ -1372,9 +1360,9 @@ Action a_convert_rare_item_set(
|
||||
|
||||
string output_filename = args.get<string>(2, false);
|
||||
if (output_filename.empty() || (output_filename == "-")) {
|
||||
rs->print_all_collections(stdout, version, name_index);
|
||||
rs->print_all_collections(stdout, s.item_name_index(version));
|
||||
} else if (ends_with(output_filename, ".json")) {
|
||||
string data = rs->serialize_json(version, name_index);
|
||||
string data = rs->serialize_json(s.item_name_index(version));
|
||||
write_output_data(args, data.data(), data.size(), nullptr);
|
||||
} else if (ends_with(output_filename, ".gsl")) {
|
||||
string data = rs->serialize_gsl(args.get<bool>("big-endian"));
|
||||
@@ -1401,20 +1389,13 @@ Action a_describe_item(
|
||||
string description = args.get<string>(1);
|
||||
auto version = get_cli_version(args);
|
||||
|
||||
auto name_index = make_shared<ItemNameIndex>(
|
||||
JSON::parse(load_file("system/item-tables/names-v2.json")),
|
||||
JSON::parse(load_file("system/item-tables/names-v3.json")),
|
||||
JSON::parse(load_file("system/item-tables/names-v4.json")));
|
||||
auto pmt_data_v2 = make_shared<string>(prs_decompress(load_file("system/item-tables/ItemPMT-v2.prs")));
|
||||
auto pmt_v2 = make_shared<ItemParameterTable>(pmt_data_v2, ItemParameterTable::Version::V2);
|
||||
auto pmt_data_v3 = make_shared<string>(prs_decompress(load_file("system/item-tables/ItemPMT-gc.prs")));
|
||||
auto pmt_v3 = make_shared<ItemParameterTable>(pmt_data_v3, ItemParameterTable::Version::V3);
|
||||
auto pmt_data_v4 = make_shared<string>(prs_decompress(load_file("system/item-tables/ItemPMT-bb.prs")));
|
||||
auto pmt_v4 = make_shared<ItemParameterTable>(pmt_data_v4, ItemParameterTable::Version::V4);
|
||||
ServerState s;
|
||||
s.load_objects("item_name_indexes");
|
||||
auto name_index = s.item_name_index(version);
|
||||
|
||||
ItemData item = name_index->parse_item_description(version, description);
|
||||
ItemData item = name_index->parse_item_description(description);
|
||||
|
||||
string desc = name_index->describe_item(version, item);
|
||||
string desc = name_index->describe_item(item);
|
||||
log_info("Data (decoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX",
|
||||
item.data1[0], item.data1[1], item.data1[2], item.data1[3],
|
||||
item.data1[4], item.data1[5], item.data1[6], item.data1[7],
|
||||
@@ -1422,7 +1403,7 @@ Action a_describe_item(
|
||||
item.data2[0], item.data2[1], item.data2[2], item.data2[3]);
|
||||
|
||||
ItemData item_v1 = item;
|
||||
item_v1.encode_for_version(Version::PC_V2, pmt_v2);
|
||||
item_v1.encode_for_version(Version::PC_V2, s.item_parameter_table(Version::PC_V2));
|
||||
ItemData item_v1_decoded = item_v1;
|
||||
item_v1_decoded.decode_for_version(Version::PC_V2);
|
||||
|
||||
@@ -1441,7 +1422,7 @@ Action a_describe_item(
|
||||
}
|
||||
|
||||
ItemData item_gc = item;
|
||||
item_gc.encode_for_version(Version::GC_V3, pmt_v3);
|
||||
item_gc.encode_for_version(Version::GC_V3, s.item_parameter_table(Version::GC_V3));
|
||||
ItemData item_gc_decoded = item_gc;
|
||||
item_gc_decoded.decode_for_version(Version::GC_V3);
|
||||
|
||||
@@ -1461,11 +1442,49 @@ Action a_describe_item(
|
||||
|
||||
log_info("Description: %s", desc.c_str());
|
||||
|
||||
size_t purchase_price = pmt_v4->price_for_item(item);
|
||||
size_t purchase_price = s.item_parameter_table(Version::BB_V4)->price_for_item(item);
|
||||
size_t sale_price = purchase_price >> 3;
|
||||
log_info("Purchase price: %zu; sale price: %zu", purchase_price, sale_price);
|
||||
});
|
||||
|
||||
Action a_name_all_items(
|
||||
"name-all-items", nullptr, +[](Arguments&) {
|
||||
ServerState s;
|
||||
s.load_objects("item_name_indexes");
|
||||
|
||||
set<uint32_t> all_primary_identifiers;
|
||||
for (const auto& index : s.item_name_indexes) {
|
||||
if (index) {
|
||||
for (const auto& it : index->all_by_primary_identifier()) {
|
||||
all_primary_identifiers.emplace(it.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "IDENT :");
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
Version version = static_cast<Version>(v_s);
|
||||
const auto& index = s.item_name_indexes.at(v_s);
|
||||
if (index) {
|
||||
fprintf(stderr, " %30s", name_for_enum(version));
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
|
||||
for (uint32_t primary_identifier : all_primary_identifiers) {
|
||||
fprintf(stderr, "%06" PRIX32 ":", primary_identifier);
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
const auto& index = s.item_name_indexes.at(v_s);
|
||||
if (index) {
|
||||
ItemData item(static_cast<uint64_t>(primary_identifier) << 40);
|
||||
string name = index->describe_item(item);
|
||||
fprintf(stderr, " %30s", name.c_str());
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
});
|
||||
|
||||
Action a_show_ep3_cards(
|
||||
"show-ep3-cards", "\
|
||||
show-ep3-cards\n\
|
||||
|
||||
+3
-10
@@ -113,17 +113,10 @@ string Map::Enemy::str() const {
|
||||
this->source_index, name_for_enum(this->type), this->floor, this->state_flags, this->last_hit_by_client_id);
|
||||
}
|
||||
|
||||
string Map::Object::str(shared_ptr<const ItemNameIndex> name_index) const {
|
||||
string Map::Object::str() const {
|
||||
if (this->param1 <= 0.0f) {
|
||||
string item_name;
|
||||
try {
|
||||
auto item = ItemCreator::base_item_for_specialized_box(this->param4, this->param5, this->param6);
|
||||
item_name = name_index ? name_index->describe_item(Version::BB_V4, item) : item.hex();
|
||||
} catch (const exception& e) {
|
||||
item_name = string_printf("(failed: %s)", e.what());
|
||||
}
|
||||
return string_printf("[Map::Object source %zX %04hX @%04hX p1=%g (specialized: %s) floor=%02hhX item_drop_checked=%s]",
|
||||
this->source_index, this->base_type, this->section, this->param1, item_name.c_str(), this->floor, this->item_drop_checked ? "true" : "false");
|
||||
return string_printf("[Map::Object source %zX %04hX @%04hX p1=%g (specialized: %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ") floor=%02hhX item_drop_checked=%s]",
|
||||
this->source_index, this->base_type, this->section, this->param1, this->param4, this->param5, this->param6, this->floor, this->item_drop_checked ? "true" : "false");
|
||||
} else {
|
||||
return string_printf("[Map::Object source %zX %04hX @%04hX p1=%g (generic) p456=[%08" PRIX32 " %08" PRIX32 " %08" PRIX32 "] floor=%02hhX item_drop_checked=%s]",
|
||||
this->source_index, this->base_type, this->section, this->param1, this->param4, this->param5, this->param6,
|
||||
|
||||
+1
-2
@@ -10,7 +10,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "BattleParamsIndex.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
@@ -217,7 +216,7 @@ struct Map {
|
||||
uint8_t floor;
|
||||
bool item_drop_checked;
|
||||
|
||||
std::string str(std::shared_ptr<const ItemNameIndex> name_index) const;
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
struct Enemy {
|
||||
|
||||
@@ -678,7 +678,7 @@ void PlayerInventory::encode_for_client(shared_ptr<Client> c) {
|
||||
}
|
||||
}
|
||||
|
||||
auto item_parameter_table = c->require_server_state()->item_parameter_table_for_version(c->version());
|
||||
auto item_parameter_table = c->require_server_state()->item_parameter_table(c->version());
|
||||
for (size_t z = 0; z < this->items.size(); z++) {
|
||||
this->items[z].data.encode_for_version(c->version(), item_parameter_table);
|
||||
}
|
||||
|
||||
+11
-13
@@ -17,7 +17,7 @@ string RareItemSet::ExpandedDrop::str() const {
|
||||
this->probability, frac.first, frac.second, this->item_code[0], this->item_code[1], this->item_code[2]);
|
||||
}
|
||||
|
||||
string RareItemSet::ExpandedDrop::str(Version version, shared_ptr<const ItemNameIndex> name_index) const {
|
||||
string RareItemSet::ExpandedDrop::str(shared_ptr<const ItemNameIndex> name_index) const {
|
||||
ItemData item;
|
||||
item.data1[0] = this->item_code[0];
|
||||
item.data1[1] = this->item_code[1];
|
||||
@@ -25,7 +25,7 @@ string RareItemSet::ExpandedDrop::str(Version version, shared_ptr<const ItemName
|
||||
|
||||
string ret = this->str();
|
||||
ret += " (";
|
||||
ret += name_index->describe_item(version, item);
|
||||
ret += name_index->describe_item(item);
|
||||
ret += ")";
|
||||
return ret;
|
||||
}
|
||||
@@ -303,7 +303,7 @@ RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) {
|
||||
}
|
||||
}
|
||||
|
||||
RareItemSet::RareItemSet(const JSON& json, Version version, shared_ptr<const ItemNameIndex> name_index) {
|
||||
RareItemSet::RareItemSet(const JSON& json, shared_ptr<const ItemNameIndex> name_index) {
|
||||
for (const auto& mode_it : json.as_dict()) {
|
||||
static const unordered_map<string, GameMode> mode_keys(
|
||||
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
|
||||
@@ -369,7 +369,7 @@ RareItemSet::RareItemSet(const JSON& json, Version version, shared_ptr<const Ite
|
||||
if (!name_index) {
|
||||
throw runtime_error("item name index is not available");
|
||||
}
|
||||
ItemData data = name_index->parse_item_description(version, item_desc.as_string());
|
||||
ItemData data = name_index->parse_item_description(item_desc.as_string());
|
||||
d.item_code[0] = data.data1[0];
|
||||
d.item_code[1] = data.data1[1];
|
||||
d.item_code[2] = data.data1[2];
|
||||
@@ -427,7 +427,7 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
|
||||
return GSLArchive::generate(files, big_endian);
|
||||
}
|
||||
|
||||
std::string RareItemSet::serialize_json(Version version, shared_ptr<const ItemNameIndex> name_index) const {
|
||||
std::string RareItemSet::serialize_json(shared_ptr<const ItemNameIndex> name_index) const {
|
||||
auto modes_dict = JSON::dict();
|
||||
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (const auto& mode : modes) {
|
||||
@@ -458,7 +458,7 @@ std::string RareItemSet::serialize_json(Version version, shared_ptr<const ItemNa
|
||||
data.data1[0] = spec.item_code[0];
|
||||
data.data1[1] = spec.item_code[1];
|
||||
data.data1[2] = spec.item_code[2];
|
||||
spec_json.emplace_back(name_index->describe_item(version, data));
|
||||
spec_json.emplace_back(name_index->describe_item(data));
|
||||
}
|
||||
for (const auto& enemy_type : enemy_types) {
|
||||
if (enemy_type_valid_for_episode(episode, enemy_type)) {
|
||||
@@ -485,7 +485,7 @@ std::string RareItemSet::serialize_json(Version version, shared_ptr<const ItemNa
|
||||
data.data1[0] = spec.item_code[0];
|
||||
data.data1[1] = spec.item_code[1];
|
||||
data.data1[2] = spec.item_code[2];
|
||||
area_list.back().emplace_back(name_index->describe_item(version, data));
|
||||
area_list.back().emplace_back(name_index->describe_item(data));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +512,6 @@ std::string RareItemSet::serialize_json(Version version, shared_ptr<const ItemNa
|
||||
|
||||
void RareItemSet::print_collection(
|
||||
FILE* stream,
|
||||
Version version,
|
||||
GameMode mode,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
@@ -544,7 +543,7 @@ void RareItemSet::print_collection(
|
||||
}
|
||||
|
||||
for (const auto& spec : collection->rt_index_to_specs[z]) {
|
||||
string s = name_index ? spec.str(version, name_index) : spec.str();
|
||||
string s = name_index ? spec.str(name_index) : spec.str();
|
||||
fprintf(stream, " %02zX: %s (%s)\n", z, s.c_str(), enemy_types_str.c_str());
|
||||
}
|
||||
}
|
||||
@@ -552,14 +551,13 @@ void RareItemSet::print_collection(
|
||||
fprintf(stream, " Box rares:\n");
|
||||
for (size_t area = 0; area < collection->box_area_to_specs.size(); area++) {
|
||||
for (const auto& spec : collection->box_area_to_specs[area]) {
|
||||
string s = name_index ? spec.str(version, name_index) : spec.str();
|
||||
string s = name_index ? spec.str(name_index) : spec.str();
|
||||
fprintf(stream, " (area %02zX) %s\n", area, s.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RareItemSet::print_all_collections(
|
||||
FILE* stream, Version version, std::shared_ptr<const ItemNameIndex> name_index) const {
|
||||
void RareItemSet::print_all_collections(FILE* stream, std::shared_ptr<const ItemNameIndex> name_index) const {
|
||||
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (GameMode mode : modes) {
|
||||
@@ -567,7 +565,7 @@ void RareItemSet::print_all_collections(
|
||||
for (uint8_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
this->print_collection(stream, version, mode, episode, difficulty, section_id, name_index);
|
||||
this->print_collection(stream, mode, episode, difficulty, section_id, name_index);
|
||||
} catch (const out_of_range& e) {
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -22,14 +22,14 @@ public:
|
||||
parray<uint8_t, 3> item_code;
|
||||
|
||||
std::string str() const;
|
||||
std::string str(Version version, std::shared_ptr<const ItemNameIndex> name_index) const;
|
||||
std::string str(std::shared_ptr<const ItemNameIndex> name_index) const;
|
||||
};
|
||||
|
||||
RareItemSet();
|
||||
RareItemSet(const AFSArchive& afs, bool is_v1);
|
||||
RareItemSet(const GSLArchive& gsl, bool is_big_endian);
|
||||
RareItemSet(const std::string& rel, bool is_big_endian);
|
||||
RareItemSet(const JSON& json, Version version, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
|
||||
RareItemSet(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
|
||||
~RareItemSet() = default;
|
||||
|
||||
std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
|
||||
@@ -37,17 +37,16 @@ public:
|
||||
|
||||
std::string serialize_afs(bool is_v1) const;
|
||||
std::string serialize_gsl(bool big_endian) const;
|
||||
std::string serialize_json(Version version, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
std::string serialize_json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
|
||||
void print_collection(
|
||||
FILE* stream,
|
||||
Version version,
|
||||
GameMode mode,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t section_id,
|
||||
std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
void print_all_collections(FILE* stream, Version version, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
void print_all_collections(FILE* stream, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
|
||||
protected:
|
||||
struct SpecCollection {
|
||||
|
||||
+11
-11
@@ -132,7 +132,7 @@ void send_client_to_proxy_server(shared_ptr<Client> c) {
|
||||
|
||||
static void send_proxy_destinations_menu(shared_ptr<Client> c) {
|
||||
auto s = c->require_server_state();
|
||||
send_menu(c, s->proxy_destinations_menu_for_version(c->version()));
|
||||
send_menu(c, s->proxy_destinations_menu(c->version()));
|
||||
}
|
||||
|
||||
static bool send_enable_send_function_call_if_applicable(shared_ptr<Client> c) {
|
||||
@@ -1762,7 +1762,7 @@ static void on_D6_V3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
check_size_v(data.size(), 0);
|
||||
if (c->config.check_flag(Client::Flag::IN_INFORMATION_MENU)) {
|
||||
auto s = c->require_server_state();
|
||||
send_menu(c, s->information_menu_for_version(c->version()));
|
||||
send_menu(c, s->information_menu(c->version()));
|
||||
} else if (c->config.check_flag(Client::Flag::AT_WELCOME_MESSAGE)) {
|
||||
c->config.clear_flag(Client::Flag::AT_WELCOME_MESSAGE);
|
||||
send_enable_send_function_call_if_applicable(c);
|
||||
@@ -1784,7 +1784,7 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
case MenuID::QUEST_EP1:
|
||||
case MenuID::QUEST_EP2: {
|
||||
bool is_download_quest = !c->lobby.lock();
|
||||
auto quest_index = s->quest_index_for_version(c->version());
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
if (!quest_index) {
|
||||
send_quest_info(c, "$C6Quests are not available.", is_download_quest);
|
||||
} else {
|
||||
@@ -2178,7 +2178,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
|
||||
case MainMenuItemID::INFORMATION: {
|
||||
auto s = c->require_server_state();
|
||||
send_menu(c, s->information_menu_for_version(c->version()));
|
||||
send_menu(c, s->information_menu(c->version()));
|
||||
c->config.set_flag(Client::Flag::IN_INFORMATION_MENU);
|
||||
break;
|
||||
}
|
||||
@@ -2199,7 +2199,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
// always the download quest menu. (Episode 3 does actually have
|
||||
// online quests, but they're served via a server data request
|
||||
// instead of the file download paradigm that other versions use.)
|
||||
auto quest_index = s->quest_index_for_version(c->version());
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
const auto& categories = quest_index->categories(menu_type, Episode::EP3, c->version());
|
||||
if (categories.size() == 1) {
|
||||
auto quests = quest_index->filter(menu_type, Episode::EP3, c->version(), categories[0]->category_id);
|
||||
@@ -2208,7 +2208,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
}
|
||||
}
|
||||
|
||||
send_quest_categories_menu(c, s->quest_index_for_version(c->version()), menu_type, Episode::NONE);
|
||||
send_quest_categories_menu(c, s->quest_index(c->version()), menu_type, Episode::NONE);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2346,7 +2346,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
auto s = c->require_server_state();
|
||||
const pair<string, uint16_t>* dest = nullptr;
|
||||
try {
|
||||
dest = &s->proxy_destinations_for_version(c->version()).at(item_id);
|
||||
dest = &s->proxy_destinations(c->version()).at(item_id);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
@@ -2453,7 +2453,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
|
||||
case MenuID::QUEST_CATEGORIES: {
|
||||
auto s = c->require_server_state();
|
||||
auto quest_index = s->quest_index_for_version(c->version());
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
if (!quest_index) {
|
||||
send_lobby_message_box(c, "$C6Quests are not available.");
|
||||
break;
|
||||
@@ -2494,7 +2494,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
case MenuID::QUEST_EP1:
|
||||
case MenuID::QUEST_EP2: {
|
||||
auto s = c->require_server_state();
|
||||
auto quest_index = s->quest_index_for_version(c->version());
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
if (!quest_index) {
|
||||
send_lobby_message_box(c, "$C6Quests are not\navailable.");
|
||||
break;
|
||||
@@ -2707,7 +2707,7 @@ static void on_08_E6(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
|
||||
static void on_1F(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
check_size_v(data.size(), 0);
|
||||
auto s = c->require_server_state();
|
||||
send_menu(c, s->information_menu_for_version(c->version()), true);
|
||||
send_menu(c, s->information_menu(c->version()), true);
|
||||
}
|
||||
|
||||
static void on_A0(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
|
||||
@@ -2842,7 +2842,7 @@ static void on_A2(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data) {
|
||||
throw logic_error("invalid game mode");
|
||||
}
|
||||
}
|
||||
send_quest_categories_menu(c, s->quest_index_for_version(c->version()), menu_type, l->episode);
|
||||
send_quest_categories_menu(c, s->quest_index(c->version()), menu_type, l->episode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+30
-30
@@ -832,7 +832,7 @@ static void on_sync_joining_player_disp_and_inventory(
|
||||
throw logic_error("6x70 command from unknown game version");
|
||||
}
|
||||
|
||||
parsed->transcode_inventory_items(c->version(), target->version(), s->item_parameter_table_for_version(target->version()));
|
||||
parsed->transcode_inventory_items(c->version(), target->version(), s->item_parameter_table(target->version()));
|
||||
parsed->visual.enforce_lobby_join_limits_for_version(target->version());
|
||||
|
||||
switch (target->version()) {
|
||||
@@ -1349,7 +1349,7 @@ static void on_player_drop_item(shared_ptr<Client> c, uint8_t command, uint8_t f
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hu dropped item %08" PRIX32 " (%s) at %hu:(%g, %g)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.floor.load(), cmd.x.load(), cmd.z.load());
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
@@ -1374,7 +1374,7 @@ void forward_subcommand_with_item_transcode_t(shared_ptr<Client> c, uint8_t comm
|
||||
out_cmd.header.subcommand = translate_subcommand_number(lc->version(), c->version(), out_cmd.header.subcommand);
|
||||
if (out_cmd.header.subcommand) {
|
||||
out_cmd.item_data.decode_for_version(c->version());
|
||||
out_cmd.item_data.encode_for_version(lc->version(), s->item_parameter_table_for_version(lc->version()));
|
||||
out_cmd.item_data.encode_for_version(lc->version(), s->item_parameter_table(lc->version()));
|
||||
send_command_t(lc, command, flag, out_cmd);
|
||||
} else {
|
||||
lc->log.info("Subcommand cannot be translated to client\'s version");
|
||||
@@ -1409,7 +1409,7 @@ static void on_create_inventory_item_t(shared_ptr<Client> c, uint8_t command, ui
|
||||
auto s = c->require_server_state();
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hu created inventory item %08" PRIX32 " (%s)", c->lobby_client_id, item.id.load(), name.c_str());
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand_with_item_transcode_t(c, command, flag, cmd);
|
||||
@@ -1450,7 +1450,7 @@ static void on_drop_partial_stack_t(shared_ptr<Client> c, uint8_t command, uint8
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hu split stack to create floor item %08" PRIX32 " (%s) at %hu:(%g, %g)",
|
||||
cmd.header.client_id.load(), item.id.load(), name.c_str(), cmd.floor.load(), cmd.x.load(), cmd.z.load());
|
||||
c->character()->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand_with_item_transcode_t(c, command, flag, cmd);
|
||||
@@ -1498,7 +1498,7 @@ static void on_drop_partial_stack_bb(shared_ptr<Client> c, uint8_t command, uint
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hu split stack %08" PRIX32 " (removed: %s) at %hu:(%g, %g)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.floor.load(), cmd.x.load(), cmd.z.load());
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -1525,14 +1525,14 @@ static void on_buy_shop_item(shared_ptr<Client> c, uint8_t command, uint8_t flag
|
||||
l->on_item_id_generated_externally(item.id);
|
||||
p->add_item(item);
|
||||
|
||||
size_t price = s->item_parameter_table_for_version(c->version())->price_for_item(item);
|
||||
size_t price = s->item_parameter_table(c->version())->price_for_item(item);
|
||||
p->remove_meseta(price, c->version() != Version::BB_V4);
|
||||
|
||||
if (l->log.should_log(LogLevel::INFO)) {
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hu bought item %08" PRIX32 " (%s) from shop (%zu Meseta)",
|
||||
cmd.header.client_id.load(), item.id.load(), name.c_str(), price);
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand_with_item_transcode_t(c, command, flag, cmd);
|
||||
@@ -1575,7 +1575,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, uint8_t command, u
|
||||
out_cmd.header.subcommand = translate_subcommand_number(lc->version(), c->version(), out_cmd.header.subcommand);
|
||||
if (out_cmd.header.subcommand) {
|
||||
out_cmd.item.item.decode_for_version(c->version());
|
||||
out_cmd.item.item.encode_for_version(lc->version(), s->item_parameter_table_for_version(lc->version()));
|
||||
out_cmd.item.item.encode_for_version(lc->version(), s->item_parameter_table(lc->version()));
|
||||
send_command_t(lc, command, flag, out_cmd);
|
||||
} else {
|
||||
lc->log.info("Subcommand cannot be translated to client\'s version");
|
||||
@@ -1639,7 +1639,7 @@ static void on_pick_up_item_generic(
|
||||
auto s = c->require_server_state();
|
||||
auto name = s->describe_item(c->version(), fi->data, false);
|
||||
l->log.info("Player %hu picked up %08" PRIX32 " (%s)", client_id, item_id, name.c_str());
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
auto s = c->require_server_state();
|
||||
@@ -1725,7 +1725,7 @@ static void on_use_item(
|
||||
if (l->log.should_log(LogLevel::INFO)) {
|
||||
l->log.info("Player %hhu used item %hu:%08" PRIX32 " (%s)",
|
||||
c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
@@ -1771,7 +1771,7 @@ static void on_feed_mag(
|
||||
l->log.info("Player %hhu fed item %hu:%08" PRIX32 " (%s) to mag %hu:%08" PRIX32 " (%s)",
|
||||
c->lobby_client_id, cmd.header.client_id.load(), cmd.fed_item_id.load(), fed_name.c_str(),
|
||||
cmd.header.client_id.load(), cmd.mag_item_id.load(), mag_name.c_str());
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
@@ -1806,7 +1806,7 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<Client> c, uint8_t com
|
||||
}
|
||||
for (auto& item : c->bb_shop_contents[cmd.shop_type]) {
|
||||
item.id = 0xFFFFFFFF;
|
||||
item.data2d = s->item_parameter_table_for_version(c->version())->price_for_item(item);
|
||||
item.data2d = s->item_parameter_table(c->version())->price_for_item(item);
|
||||
}
|
||||
|
||||
send_shop(c, cmd.shop_type);
|
||||
@@ -1863,10 +1863,10 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
|
||||
send_destroy_item_to_lobby(c, cmd.item_id, cmd.item_amount, true);
|
||||
|
||||
if (l->log.should_log(LogLevel::INFO)) {
|
||||
string name = s->item_name_index->describe_item(Version::BB_V4, item);
|
||||
string name = s->describe_item(Version::BB_V4, item, false);
|
||||
l->log.info("Player %hu deposited item %08" PRIX32 " (x%hhu) (%s) in the bank",
|
||||
c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str());
|
||||
c->character()->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1892,10 +1892,10 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
|
||||
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
|
||||
|
||||
if (l->log.should_log(LogLevel::INFO)) {
|
||||
string name = s->item_name_index->describe_item(Version::BB_V4, item);
|
||||
string name = s->describe_item(Version::BB_V4, item, false);
|
||||
l->log.info("Player %hu withdrew item %08" PRIX32 " (x%hhu) (%s) from the bank",
|
||||
c->lobby_client_id, item.id.load(), cmd.item_amount, name.c_str());
|
||||
c->character()->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2133,7 +2133,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
if (item.empty()) {
|
||||
l->log.info("No item was created");
|
||||
} else {
|
||||
string name = s->item_name_index->describe_item(l->base_version, item);
|
||||
string name = s->describe_item(l->base_version, item, false);
|
||||
l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
if (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE) {
|
||||
for (const auto& lc : l->clients) {
|
||||
@@ -2163,7 +2163,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
if (item.empty()) {
|
||||
l->log.info("No item was created for %s", lc->channel.name.c_str());
|
||||
} else {
|
||||
string name = s->item_name_index->describe_item(l->base_version, item);
|
||||
string name = s->describe_item(l->base_version, item, false);
|
||||
l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
item.id = l->generate_item_id(0xFF);
|
||||
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
|
||||
@@ -2455,7 +2455,7 @@ static void on_steal_exp_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* data,
|
||||
const auto& inventory = p->inventory;
|
||||
const auto& weapon = inventory.items[inventory.find_equipped_item(EquipSlot::WEAPON)];
|
||||
|
||||
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
|
||||
uint8_t special_id = 0;
|
||||
if (((weapon.data.data1[1] < 0x0A) && (weapon.data.data1[2] < 0x05)) ||
|
||||
@@ -2574,7 +2574,7 @@ static void on_enemy_exp_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
|
||||
for (size_t z = 0; z < inventory.num_items; z++) {
|
||||
auto& item = inventory.items[z];
|
||||
if ((item.flags & 0x08) &&
|
||||
s->item_parameter_table_for_version(c->version())->is_unsealable_item(item.data)) {
|
||||
s->item_parameter_table(c->version())->is_unsealable_item(item.data)) {
|
||||
item.data.set_sealed_item_kill_count(item.data.get_sealed_item_kill_count() + 1);
|
||||
}
|
||||
}
|
||||
@@ -2644,7 +2644,7 @@ void on_transfer_item_via_mail_message_bb(shared_ptr<Client> c, uint8_t command,
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hhu sent inventory item %hu:%08" PRIX32 " (%s) x%" PRIu32 " to player %08" PRIX32,
|
||||
c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.amount.load(), cmd.target_guild_card_number.load());
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
// To receive an item, the player must be online, using BB, have a character
|
||||
@@ -2704,7 +2704,7 @@ void on_exchange_item_for_team_points_bb(shared_ptr<Client> c, uint8_t command,
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hhu exchanged inventory item %hu:%08" PRIX32 " (%s) for %zu team points",
|
||||
c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), points);
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
@@ -2729,7 +2729,7 @@ static void on_destroy_inventory_item(shared_ptr<Client> c, uint8_t command, uin
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)",
|
||||
c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
@@ -2833,14 +2833,14 @@ static void on_sell_item_at_shop_bb(shared_ptr<Client> c, uint8_t command, uint8
|
||||
auto s = c->require_server_state();
|
||||
auto p = c->character();
|
||||
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
||||
size_t price = (s->item_parameter_table_for_version(c->version())->price_for_item(item) >> 3) * cmd.amount;
|
||||
size_t price = (s->item_parameter_table(c->version())->price_for_item(item) >> 3) * cmd.amount;
|
||||
p->add_meseta(price);
|
||||
|
||||
if (l->log.should_log(LogLevel::INFO)) {
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hhu sold inventory item %08" PRIX32 " (%s) for %zu Meseta",
|
||||
c->lobby_client_id, cmd.item_id.load(), name.c_str(), price);
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
@@ -2875,7 +2875,7 @@ static void on_buy_shop_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* da
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hhu purchased item %08" PRIX32 " (%s) for %zu meseta",
|
||||
c->lobby_client_id, item.id.load(), name.c_str(), price);
|
||||
p->print_inventory(stderr, c->version(), s->item_name_index);
|
||||
c->print_inventory(stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3246,7 +3246,7 @@ static void on_quest_F960_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
|
||||
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
// If it's a weapon, make it unidentified
|
||||
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
if ((item.data1[0] == 0x00) && (item_parameter_table->is_item_rare(item) || (item.data1[4] != 0))) {
|
||||
item.data1[4] |= 0x80;
|
||||
}
|
||||
@@ -3260,12 +3260,12 @@ static void on_quest_F960_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
|
||||
p->add_item(item);
|
||||
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
|
||||
if (c->log.should_log(LogLevel::INFO)) {
|
||||
string name = s->item_name_index->describe_item(c->version(), item);
|
||||
string name = s->describe_item(c->version(), item, false);
|
||||
c->log.info("Awarded item %s", name.c_str());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
if (c->log.should_log(LogLevel::INFO)) {
|
||||
string name = s->item_name_index->describe_item(c->version(), item);
|
||||
string name = s->describe_item(c->version(), item, false);
|
||||
c->log.info("Attempted to award item %s, but inventory was full", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,29 +578,6 @@ void PSOBBCharacterFile::clear_all_material_usage() {
|
||||
}
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::print_inventory(FILE* stream, Version version, shared_ptr<const ItemNameIndex> name_index) const {
|
||||
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.stats.meseta.load());
|
||||
fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items);
|
||||
for (size_t x = 0; x < this->inventory.num_items; x++) {
|
||||
const auto& item = this->inventory.items[x];
|
||||
auto name = name_index->describe_item(version, item.data);
|
||||
auto hex = item.data.hex();
|
||||
fprintf(stream, "[PlayerInventory] %2zu: [+%08" PRIX32 "] %s (%s)\n", x, item.flags.load(), hex.c_str(), name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::print_bank(FILE* stream, Version version, shared_ptr<const ItemNameIndex> name_index) const {
|
||||
fprintf(stream, "[PlayerBank] Meseta: %" PRIu32 "\n", this->bank.meseta.load());
|
||||
fprintf(stream, "[PlayerBank] %" PRIu32 " items\n", this->bank.num_items.load());
|
||||
for (size_t x = 0; x < this->bank.num_items; x++) {
|
||||
const auto& item = this->bank.items[x];
|
||||
const char* present_token = item.present ? "" : " (missing present flag)";
|
||||
auto name = name_index->describe_item(version, item.data);
|
||||
auto hex = item.data.hex();
|
||||
fprintf(stream, "[PlayerBank] %3zu: %s (%s) (x%hu) %s\n", x, hex.c_str(), name.c_str(), item.amount.load(), present_token);
|
||||
}
|
||||
}
|
||||
|
||||
const array<PSOBBCharacterFile::DefaultSymbolChatEntry, 6> PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS = {
|
||||
DefaultSymbolChatEntry{"\tEHello", 0x28, {0xFFFF, 0x000D, 0xFFFF, 0xFFFF}, {SymbolChat::FacePart{0x05, 0x18, 0x1D, 0x00}, {0x05, 0x28, 0x1D, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||
DefaultSymbolChatEntry{"\tEGood-bye", 0x74, {0x0476, 0x000C, 0xFFFF, 0xFFFF}, {SymbolChat::FacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||
|
||||
@@ -252,9 +252,6 @@ struct PSOBBCharacterFile {
|
||||
uint8_t get_material_usage(MaterialType which) const;
|
||||
void set_material_usage(MaterialType which, uint8_t usage);
|
||||
void clear_all_material_usage();
|
||||
|
||||
void print_inventory(FILE* stream, Version version, std::shared_ptr<const ItemNameIndex> name_index) const;
|
||||
void print_bank(FILE* stream, Version version, std::shared_ptr<const ItemNameIndex> name_index) const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PSOBBGuildCardFile {
|
||||
|
||||
+4
-4
@@ -2239,7 +2239,7 @@ void send_execute_item_trade(shared_ptr<Client> c, const vector<ItemData>& items
|
||||
cmd.item_count = items.size();
|
||||
for (size_t x = 0; x < items.size(); x++) {
|
||||
cmd.item_datas[x] = items[x];
|
||||
cmd.item_datas[x].encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
|
||||
cmd.item_datas[x].encode_for_version(c->version(), s->item_parameter_table(c->version()));
|
||||
}
|
||||
send_command_t(c, 0xD3, 0x00, cmd);
|
||||
}
|
||||
@@ -2431,7 +2431,7 @@ void send_game_item_state(shared_ptr<Client> c) {
|
||||
fi.unknown_a2 = 0;
|
||||
fi.drop_number = (floor == 0) ? 0xFFFF : (decompressed_header.next_drop_number_per_floor.at(floor - 1)++);
|
||||
fi.item = item->data;
|
||||
fi.item.encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
|
||||
fi.item.encode_for_version(c->version(), s->item_parameter_table(c->version()));
|
||||
floor_items_w.put(fi);
|
||||
|
||||
decompressed_header.floor_item_count_per_floor.at(floor)++;
|
||||
@@ -2509,7 +2509,7 @@ void send_drop_item_to_channel(shared_ptr<ServerState> s, Channel& ch, const Ite
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x51, 0x58, 0x5F);
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{{subcommand, 0x0B, 0x0000}, {floor, from_enemy, entity_id, x, z, 0, 0, item}}, 0};
|
||||
cmd.item.item.encode_for_version(ch.version, s->item_parameter_table_for_version(ch.version));
|
||||
cmd.item.item.encode_for_version(ch.version, s->item_parameter_table(ch.version));
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
@@ -2528,7 +2528,7 @@ void send_drop_stacked_item_to_channel(
|
||||
shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z) {
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x4F, 0x56, 0x5D);
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, x, z, item}, 0};
|
||||
cmd.item_data.encode_for_version(ch.version, s->item_parameter_table_for_version(ch.version));
|
||||
cmd.item_data.encode_for_version(ch.version, s->item_parameter_table(ch.version));
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -806,7 +806,7 @@ Proxy session commands:\n\
|
||||
}
|
||||
|
||||
auto s = ses->require_server_state();
|
||||
ItemData item = s->item_name_index->parse_item_description(ses->version(), command_args);
|
||||
ItemData item = s->parse_item_description(ses->version(), command_args);
|
||||
item.id = random_object<uint32_t>() | 0x80000000;
|
||||
|
||||
if (command_name == "set-next-item") {
|
||||
|
||||
+133
-38
@@ -14,7 +14,7 @@
|
||||
#include "NetworkAddresses.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "Text.hh"
|
||||
#include "UnicodeTextSet.hh"
|
||||
#include "TextIndex.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -379,7 +379,7 @@ uint32_t ServerState::connect_address_for_client(shared_ptr<Client> c) const {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const Menu> ServerState::information_menu_for_version(Version version) const {
|
||||
shared_ptr<const Menu> ServerState::information_menu(Version version) const {
|
||||
if (is_v1_or_v2(version)) {
|
||||
return this->information_menu_v2;
|
||||
} else if (is_v3(version)) {
|
||||
@@ -388,7 +388,7 @@ shared_ptr<const Menu> ServerState::information_menu_for_version(Version version
|
||||
throw out_of_range("no information menu exists for this version");
|
||||
}
|
||||
|
||||
shared_ptr<const Menu> ServerState::proxy_destinations_menu_for_version(Version version) const {
|
||||
shared_ptr<const Menu> ServerState::proxy_destinations_menu(Version version) const {
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
@@ -410,7 +410,7 @@ shared_ptr<const Menu> ServerState::proxy_destinations_menu_for_version(Version
|
||||
}
|
||||
}
|
||||
|
||||
const vector<pair<string, uint16_t>>& ServerState::proxy_destinations_for_version(Version version) const {
|
||||
const vector<pair<string, uint16_t>>& ServerState::proxy_destinations(Version version) const {
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
@@ -432,7 +432,19 @@ const vector<pair<string, uint16_t>>& ServerState::proxy_destinations_for_versio
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const ItemParameterTable> ServerState::item_parameter_table_for_version(Version version) const {
|
||||
shared_ptr<const vector<string>> ServerState::information_contents_for_client(shared_ptr<const Client> c) const {
|
||||
return is_v1_or_v2(c->version()) ? this->information_contents_v2 : this->information_contents_v3;
|
||||
}
|
||||
|
||||
shared_ptr<const QuestIndex> ServerState::quest_index(Version version) const {
|
||||
return is_ep3(version) ? this->ep3_download_quest_index : this->default_quest_index;
|
||||
}
|
||||
|
||||
void ServerState::dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<ServerState*>(ctx)->lobbies_to_destroy.clear();
|
||||
}
|
||||
|
||||
shared_ptr<const ItemParameterTable> ServerState::item_parameter_table(Version version) const {
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
@@ -454,11 +466,24 @@ shared_ptr<const ItemParameterTable> ServerState::item_parameter_table_for_versi
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const ItemNameIndex> ServerState::item_name_index(Version version) const {
|
||||
auto ret = this->item_name_indexes.at(static_cast<size_t>(version));
|
||||
if (ret == nullptr) {
|
||||
throw runtime_error("no item name index exists for this version");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ServerState::set_item_name_index(Version version, shared_ptr<const ItemNameIndex> new_index) {
|
||||
this->item_name_indexes.at(static_cast<size_t>(version)) = new_index;
|
||||
}
|
||||
|
||||
string ServerState::describe_item(Version version, const ItemData& item, bool include_color_codes) const {
|
||||
return this->item_name_index->describe_item(
|
||||
version,
|
||||
item,
|
||||
include_color_codes ? this->item_parameter_table_for_version(version) : nullptr);
|
||||
return this->item_name_index(version)->describe_item(item, include_color_codes);
|
||||
}
|
||||
|
||||
ItemData ServerState::parse_item_description(Version version, const string& description) const {
|
||||
return this->item_name_index(version)->parse_item_description(description);
|
||||
}
|
||||
|
||||
void ServerState::set_port_configuration(
|
||||
@@ -829,7 +854,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
|
||||
for (const auto& difficulty_it : type_it->as_list()) {
|
||||
auto& difficulty_res = type_res.emplace_back();
|
||||
for (const auto& item_it : difficulty_it->as_list()) {
|
||||
difficulty_res.emplace_back(this->item_name_index->parse_item_description(Version::BB_V4, item_it->as_string()));
|
||||
difficulty_res.emplace_back(this->parse_item_description(Version::BB_V4, item_it->as_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -840,22 +865,22 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
|
||||
for (const auto& it : json.get_list("QuestF95FResultItems")) {
|
||||
auto& list = it->as_list();
|
||||
size_t price = list.at(0)->as_int();
|
||||
this->quest_F95F_results.emplace_back(make_pair(price, this->item_name_index->parse_item_description(Version::BB_V4, list.at(1)->as_string())));
|
||||
this->quest_F95F_results.emplace_back(make_pair(price, this->parse_item_description(Version::BB_V4, list.at(1)->as_string())));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->quest_F960_success_results.clear();
|
||||
this->quest_F960_failure_results = QuestF960Result(json.at("QuestF960FailureResultItems"), this->item_name_index);
|
||||
this->quest_F960_failure_results = QuestF960Result(json.at("QuestF960FailureResultItems"), this->item_name_index(Version::BB_V4));
|
||||
for (const auto& it : json.get_list("QuestF960SuccessResultItems")) {
|
||||
this->quest_F960_success_results.emplace_back(*it, this->item_name_index);
|
||||
this->quest_F960_success_results.emplace_back(*it, this->item_name_index(Version::BB_V4));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->secret_lottery_results.clear();
|
||||
for (const auto& it : json.get_list("SecretLotteryResultItems")) {
|
||||
this->secret_lottery_results.emplace_back(this->item_name_index->parse_item_description(Version::BB_V4, it->as_string()));
|
||||
this->secret_lottery_results.emplace_back(this->parse_item_description(Version::BB_V4, it->as_string()));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
@@ -1109,7 +1134,25 @@ void ServerState::load_level_table() {
|
||||
this->level_table = make_shared<LevelTableV4>(*this->load_bb_file("PlyLevelTbl.prs"), true);
|
||||
}
|
||||
|
||||
shared_ptr<WordSelectTable> ServerState::load_word_select_table_from_system() {
|
||||
void ServerState::load_text_index() {
|
||||
this->text_index = make_shared<TextIndex>("system/text-sets", [&](Version version, const string& filename) -> shared_ptr<const string> {
|
||||
try {
|
||||
if (version == Version::BB_V4) {
|
||||
return this->load_bb_file(filename);
|
||||
} else {
|
||||
return this->pc_patch_file_index->get("Media/PSO/" + filename)->load_data();
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
} catch (const cannot_open_file&) {
|
||||
return nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ServerState::load_word_select_table() {
|
||||
config_log.info("Loading Word Select table");
|
||||
|
||||
vector<vector<string>> name_alias_lists;
|
||||
auto json = JSON::parse(load_file("system/word-select/name-alias-lists.json"));
|
||||
for (const auto& coll_it : json.as_list()) {
|
||||
@@ -1119,12 +1162,20 @@ shared_ptr<WordSelectTable> ServerState::load_word_select_table_from_system() {
|
||||
}
|
||||
}
|
||||
|
||||
config_log.info("(Word select) Loading pc_unitxt.prs");
|
||||
vector<vector<string>> pc_unitxt_data = parse_unicode_text_set(load_file("system/word-select/pc_unitxt.prs"));
|
||||
const vector<string>* pc_unitxt_collection = nullptr;
|
||||
const vector<string>* bb_unitxt_collection = nullptr;
|
||||
unique_ptr<UnicodeTextSet> pc_unitxt_data;
|
||||
if (this->text_index) {
|
||||
config_log.info("(Word select) Using PC_V2 unitxt_e.prs from text index");
|
||||
pc_unitxt_collection = &this->text_index->get(Version::PC_V2, 1, 35);
|
||||
} else {
|
||||
config_log.info("(Word select) Loading PC_V2 unitxt_e.prs");
|
||||
pc_unitxt_data = make_unique<UnicodeTextSet>(load_file("system/word-select/pc_unitxt.prs"));
|
||||
pc_unitxt_collection = &pc_unitxt_data->get(35);
|
||||
}
|
||||
config_log.info("(Word select) Loading bb_unitxt_ws.prs");
|
||||
vector<vector<string>> bb_unitxt_data = parse_unicode_text_set(load_file("system/word-select/bb_unitxt_ws.prs"));
|
||||
vector<string> pc_unitxt_collection = std::move(pc_unitxt_data.at(35));
|
||||
vector<string> bb_unitxt_collection = std::move(bb_unitxt_data.at(0));
|
||||
auto bb_unitxt_data = make_unique<UnicodeTextSet>(load_file("system/word-select/bb_unitxt_ws.prs"));
|
||||
bb_unitxt_collection = &bb_unitxt_data->get(0);
|
||||
|
||||
config_log.info("(Word select) Loading DC_NTE data");
|
||||
WordSelectSet dc_nte_ws(load_file("system/word-select/dc_nte_ws_data.bin"), Version::DC_NTE, nullptr, true);
|
||||
@@ -1135,9 +1186,9 @@ shared_ptr<WordSelectTable> ServerState::load_word_select_table_from_system() {
|
||||
config_log.info("(Word select) Loading DC_V2 data");
|
||||
WordSelectSet dc_v2_ws(load_file("system/word-select/dcv2_ws_data.bin"), Version::DC_V2, nullptr, false);
|
||||
config_log.info("(Word select) Loading PC_NTE data");
|
||||
WordSelectSet pc_nte_ws(load_file("system/word-select/pc_nte_ws_data.bin"), Version::PC_NTE, &pc_unitxt_collection, false);
|
||||
WordSelectSet pc_nte_ws(load_file("system/word-select/pc_nte_ws_data.bin"), Version::PC_NTE, pc_unitxt_collection, false);
|
||||
config_log.info("(Word select) Loading PC_V2 data");
|
||||
WordSelectSet pc_v2_ws(load_file("system/word-select/pc_ws_data.bin"), Version::PC_V2, &pc_unitxt_collection, false);
|
||||
WordSelectSet pc_v2_ws(load_file("system/word-select/pc_ws_data.bin"), Version::PC_V2, pc_unitxt_collection, false);
|
||||
config_log.info("(Word select) Loading GC_NTE data");
|
||||
WordSelectSet gc_nte_ws(load_file("system/word-select/gc_nte_ws_data.bin"), Version::GC_NTE, nullptr, false);
|
||||
config_log.info("(Word select) Loading GC_V3 data");
|
||||
@@ -1149,30 +1200,72 @@ shared_ptr<WordSelectTable> ServerState::load_word_select_table_from_system() {
|
||||
config_log.info("(Word select) Loading XB_V3 data");
|
||||
WordSelectSet xb_v3_ws(load_file("system/word-select/xb_ws_data.bin"), Version::XB_V3, nullptr, false);
|
||||
config_log.info("(Word select) Loading BB_V4 data");
|
||||
WordSelectSet bb_v4_ws(load_file("system/word-select/bb_ws_data.bin"), Version::BB_V4, &bb_unitxt_collection, false);
|
||||
WordSelectSet bb_v4_ws(load_file("system/word-select/bb_ws_data.bin"), Version::BB_V4, bb_unitxt_collection, false);
|
||||
|
||||
config_log.info("(Word select) Generating table");
|
||||
return make_shared<WordSelectTable>(
|
||||
this->word_select_table = make_shared<WordSelectTable>(
|
||||
dc_nte_ws, dc_112000_ws, dc_v1_ws, dc_v2_ws,
|
||||
pc_nte_ws, pc_v2_ws, gc_nte_ws, gc_v3_ws,
|
||||
gc_ep3_nte_ws, gc_ep3_ws, xb_v3_ws, bb_v4_ws,
|
||||
name_alias_lists);
|
||||
}
|
||||
|
||||
void ServerState::load_word_select_table() {
|
||||
config_log.info("Loading Word Select table");
|
||||
this->word_select_table = this->load_word_select_table_from_system();
|
||||
shared_ptr<ItemNameIndex> ServerState::create_item_name_index_for_version(
|
||||
Version version, shared_ptr<const ItemParameterTable> pmt, shared_ptr<const TextIndex> text_index) {
|
||||
switch (version) {
|
||||
case Version::DC_NTE:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::DC_NTE, 0, 2));
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::DC_V1_11_2000_PROTOTYPE, 1, 2));
|
||||
case Version::DC_V1:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::DC_V1, 1, 2));
|
||||
case Version::DC_V2:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::DC_V2, 1, 3));
|
||||
case Version::PC_NTE:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::PC_NTE, 1, 3));
|
||||
case Version::PC_V2:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::PC_V2, 1, 3));
|
||||
case Version::GC_NTE:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::GC_NTE, 1, 0));
|
||||
case Version::GC_V3:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::GC_V3, 1, 0));
|
||||
case Version::XB_V3:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::XB_V3, 1, 0));
|
||||
case Version::BB_V4:
|
||||
return make_shared<ItemNameIndex>(pmt, text_index->get(Version::BB_V4, 1, 1));
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerState::load_item_name_index() {
|
||||
config_log.info("Loading item name index");
|
||||
this->item_name_index = make_shared<ItemNameIndex>(
|
||||
JSON::parse(load_file("system/item-tables/names-v2.json")),
|
||||
JSON::parse(load_file("system/item-tables/names-v3.json")),
|
||||
JSON::parse(load_file("system/item-tables/names-v4.json")));
|
||||
void ServerState::load_item_name_indexes() {
|
||||
config_log.info("Generating item name indexes");
|
||||
// TODO: Get ItemPMT files for the versions for which we don't have them
|
||||
// (especially DC_V1) and add support for them. Currently we only have three
|
||||
// ItemPMTs (PC, GC, and BB), so we can't use them to generate all the name
|
||||
// indexes.
|
||||
|
||||
auto pc_v2_index = create_item_name_index_for_version(
|
||||
Version::PC_V2, this->item_parameter_table(Version::PC_V2), this->text_index);
|
||||
this->set_item_name_index(Version::DC_NTE, pc_v2_index);
|
||||
this->set_item_name_index(Version::DC_V1_11_2000_PROTOTYPE, pc_v2_index);
|
||||
this->set_item_name_index(Version::DC_V1, pc_v2_index);
|
||||
this->set_item_name_index(Version::DC_V2, pc_v2_index);
|
||||
this->set_item_name_index(Version::PC_NTE, pc_v2_index);
|
||||
this->set_item_name_index(Version::PC_V2, pc_v2_index);
|
||||
|
||||
auto gc_v3_index = create_item_name_index_for_version(
|
||||
Version::GC_V3, this->item_parameter_table(Version::GC_V3), this->text_index);
|
||||
this->set_item_name_index(Version::GC_NTE, gc_v3_index);
|
||||
this->set_item_name_index(Version::GC_V3, gc_v3_index);
|
||||
this->set_item_name_index(Version::XB_V3, gc_v3_index);
|
||||
|
||||
auto bb_v4_index = create_item_name_index_for_version(
|
||||
Version::BB_V4, this->item_parameter_table(Version::BB_V4), this->text_index);
|
||||
this->set_item_name_index(Version::BB_V4, bb_v4_index);
|
||||
}
|
||||
|
||||
void ServerState::load_item_tables() {
|
||||
void ServerState::load_drop_tables() {
|
||||
config_log.info("Loading rare item sets");
|
||||
unordered_map<string, shared_ptr<const RareItemSet>> new_rare_item_sets;
|
||||
for (const auto& filename : list_directory_sorted("system/item-tables")) {
|
||||
@@ -1186,16 +1279,16 @@ void ServerState::load_item_tables() {
|
||||
|
||||
if (ends_with(filename, "-v1.json")) {
|
||||
config_log.info("Loading v1 JSON rare item table %s", filename.c_str());
|
||||
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(JSON::parse(load_file(path)), Version::DC_V1, this->item_name_index));
|
||||
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(JSON::parse(load_file(path)), this->item_name_index(Version::DC_V1)));
|
||||
} else if (ends_with(filename, "-v2.json")) {
|
||||
config_log.info("Loading v2 JSON rare item table %s", filename.c_str());
|
||||
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(JSON::parse(load_file(path)), Version::PC_V2, this->item_name_index));
|
||||
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(JSON::parse(load_file(path)), this->item_name_index(Version::PC_V2)));
|
||||
} else if (ends_with(filename, "-v3.json")) {
|
||||
config_log.info("Loading v3 JSON rare item table %s", filename.c_str());
|
||||
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(JSON::parse(load_file(path)), Version::GC_V3, this->item_name_index));
|
||||
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(JSON::parse(load_file(path)), this->item_name_index(Version::GC_V3)));
|
||||
} else if (ends_with(filename, "-v4.json")) {
|
||||
config_log.info("Loading v4 JSON rare item table %s", filename.c_str());
|
||||
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(JSON::parse(load_file(path)), Version::BB_V4, this->item_name_index));
|
||||
new_rare_item_sets.emplace(basename, make_shared<RareItemSet>(JSON::parse(load_file(path)), this->item_name_index(Version::BB_V4)));
|
||||
|
||||
} else if (ends_with(filename, ".afs")) {
|
||||
config_log.info("Loading AFS rare item table %s", filename.c_str());
|
||||
@@ -1255,7 +1348,9 @@ void ServerState::load_item_tables() {
|
||||
config_log.info("Loading tekker adjustment table");
|
||||
auto tekker_data = make_shared<string>(load_file("system/item-tables/JudgeItem-gc.rel"));
|
||||
this->tekker_adjustment_set = make_shared<TekkerAdjustmentSet>(tekker_data);
|
||||
}
|
||||
|
||||
void ServerState::load_item_definitions() {
|
||||
config_log.info("Loading item definition tables");
|
||||
auto pmt_data_v2 = make_shared<string>(prs_decompress(load_file("system/item-tables/ItemPMT-v2.prs")));
|
||||
this->item_parameter_table_v2 = make_shared<ItemParameterTable>(pmt_data_v2, ItemParameterTable::Version::V2);
|
||||
|
||||
+10
-6
@@ -138,7 +138,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table_v3;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table_v4;
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
|
||||
std::shared_ptr<const ItemNameIndex> item_name_index;
|
||||
std::shared_ptr<const TextIndex> text_index;
|
||||
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
|
||||
std::shared_ptr<const WordSelectTable> word_select_table;
|
||||
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
@@ -257,15 +258,18 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
|
||||
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
|
||||
|
||||
std::shared_ptr<const Menu> information_menu_for_version(Version version) const;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu_for_version(Version version) const;
|
||||
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(Version version) const;
|
||||
std::shared_ptr<const Menu> information_menu(Version version) const;
|
||||
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
|
||||
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
|
||||
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_version(Version version) const;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
|
||||
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const;
|
||||
void set_item_name_index(Version version, std::shared_ptr<const ItemNameIndex>);
|
||||
std::string describe_item(Version version, const ItemData& item, bool include_color_codes) const;
|
||||
ItemData parse_item_description(Version version, const std::string& description) const;
|
||||
|
||||
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
|
||||
std::shared_ptr<const QuestIndex> quest_index_for_version(Version version) const;
|
||||
std::shared_ptr<const QuestIndex> quest_index(Version version) const;
|
||||
|
||||
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
|
||||
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
#include "TextArchive.hh"
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Compression.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TextArchive::TextArchive(const string& pr2_data, bool big_endian) {
|
||||
if (big_endian) {
|
||||
this->load_t<true>(pr2_data);
|
||||
} else {
|
||||
this->load_t<false>(pr2_data);
|
||||
}
|
||||
}
|
||||
|
||||
TextArchive::TextArchive(const JSON& json) {
|
||||
for (const auto& collection_json : json.at("collections").as_list()) {
|
||||
auto& collection = this->collections.emplace_back();
|
||||
for (const auto& string_json : collection_json->as_list()) {
|
||||
collection.emplace_back(string_json->as_string());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& keyboard_json : json.at("keyboards").as_list()) {
|
||||
auto& keyboard = this->keyboards.emplace_back(make_unique<Keyboard>());
|
||||
for (size_t y = 0; y < keyboard->size(); y++) {
|
||||
auto& row = keyboard->at(y);
|
||||
const auto& row_json = keyboard_json->at(y);
|
||||
for (size_t x = 0; x < row.size(); x++) {
|
||||
row[x] = row_json.at(x).as_int();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->keyboard_selector_width = json.at("keyboard_selector_width").as_int();
|
||||
}
|
||||
|
||||
JSON TextArchive::json() const {
|
||||
auto collections_json = JSON::list();
|
||||
for (const auto& collection : this->collections) {
|
||||
auto collection_json = JSON::list();
|
||||
for (const auto& s : collection) {
|
||||
collection_json.emplace_back(s);
|
||||
}
|
||||
collections_json.emplace_back(std::move(collection_json));
|
||||
}
|
||||
auto keyboards_json = JSON::list();
|
||||
for (const auto& kb : this->keyboards) {
|
||||
JSON keyboard_json = JSON::list();
|
||||
for (size_t y = 0; y < kb->size(); y++) {
|
||||
const auto& row = kb->at(y);
|
||||
JSON row_json = JSON::list();
|
||||
for (size_t x = 0; x < row.size(); x++) {
|
||||
row_json.emplace_back(row[x]);
|
||||
}
|
||||
keyboard_json.emplace_back(std::move(row_json));
|
||||
}
|
||||
keyboards_json.emplace_back(std::move(keyboard_json));
|
||||
}
|
||||
return JSON::dict({
|
||||
{"collections", std::move(collections_json)},
|
||||
{"keyboards", std::move(keyboards_json)},
|
||||
{"keyboard_selector_width", this->keyboard_selector_width},
|
||||
});
|
||||
}
|
||||
|
||||
const string& TextArchive::get_string(size_t collection_index, size_t index) const {
|
||||
return this->collections.at(collection_index).at(index);
|
||||
}
|
||||
|
||||
void TextArchive::set_string(size_t collection_index, size_t index, const string& data) {
|
||||
if (collection_index >= this->collections.size()) {
|
||||
this->collections.resize(collection_index + 1);
|
||||
}
|
||||
auto& coll = this->collections[collection_index];
|
||||
if (index >= coll.size()) {
|
||||
coll.resize(index + 1);
|
||||
}
|
||||
coll[index] = data;
|
||||
}
|
||||
|
||||
void TextArchive::set_string(size_t collection_index, size_t index, string&& data) {
|
||||
if (collection_index >= this->collections.size()) {
|
||||
this->collections.resize(collection_index + 1);
|
||||
}
|
||||
auto& coll = this->collections[collection_index];
|
||||
if (index >= coll.size()) {
|
||||
coll.resize(index + 1);
|
||||
}
|
||||
coll[index] = std::move(data);
|
||||
}
|
||||
|
||||
void TextArchive::resize_collection(size_t collection_index, size_t size) {
|
||||
if (collection_index >= this->collections.size()) {
|
||||
this->collections.resize(collection_index + 1);
|
||||
}
|
||||
this->collections[collection_index].resize(size);
|
||||
}
|
||||
|
||||
void TextArchive::resize_collection(size_t num_collections) {
|
||||
this->collections.resize(num_collections);
|
||||
}
|
||||
|
||||
TextArchive::Keyboard TextArchive::get_keyboard(size_t kb_index) const {
|
||||
return *this->keyboards.at(kb_index);
|
||||
}
|
||||
|
||||
void TextArchive::set_keyboard(size_t kb_index, const Keyboard& kb) {
|
||||
if (kb_index >= this->keyboards.size()) {
|
||||
this->keyboards.resize(kb_index + 1);
|
||||
}
|
||||
this->keyboards[kb_index] = make_unique<Keyboard>(kb);
|
||||
}
|
||||
|
||||
void TextArchive::resize_keyboards(size_t num_keyboards) {
|
||||
this->keyboards.resize(num_keyboards);
|
||||
}
|
||||
|
||||
pair<string, string> TextArchive::serialize(bool big_endian) const {
|
||||
if (big_endian) {
|
||||
return this->serialize_t<true>();
|
||||
} else {
|
||||
return this->serialize_t<false>();
|
||||
}
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
void TextArchive::load_t(const string& pr2_data) {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
using U16T = std::conditional_t<IsBigEndian, be_uint16_t, le_uint16_t>;
|
||||
|
||||
// The structure is as follows:
|
||||
// Footer:
|
||||
// U32T keyboard_index_offset ->:
|
||||
// U8 num_keyboards
|
||||
// U8 keyboard_selector_width
|
||||
// U8 unused[2]
|
||||
// U32T keyboards_offset ->:
|
||||
// U32T keyboard_offset[num_keyboards] ->:
|
||||
// U16T key_defs[7][16]
|
||||
// U32T collections_offset ->:
|
||||
// U32T[...] strings_offset ->:
|
||||
// U32T[...] string_offset ->:
|
||||
// char string[...\0]
|
||||
// <EOF>
|
||||
|
||||
auto pr2_decrypted = decrypt_pr2_data<IsBigEndian>(pr2_data);
|
||||
auto decompressed = prs_decompress(pr2_decrypted.compressed_data);
|
||||
StringReader r(decompressed);
|
||||
|
||||
// Annoyingly, there doesn't appear to be any bounds-checking on the language
|
||||
// functions, so there are no counts of strings in each collection. We have to
|
||||
// figure out where each collection ends by collecting all the relevant
|
||||
// offsets in the file instead.
|
||||
set<uint32_t> used_offsets;
|
||||
used_offsets.emplace(r.size() - 8);
|
||||
|
||||
uint32_t keyboard_index_offset = r.pget<U32T>(r.size() - 8);
|
||||
used_offsets.emplace(keyboard_index_offset);
|
||||
size_t num_keyboards = r.pget_u8(keyboard_index_offset);
|
||||
this->keyboard_selector_width = r.pget_u8(keyboard_index_offset + 1);
|
||||
uint32_t keyboards_offset = r.pget<U32T>(keyboard_index_offset + 4);
|
||||
used_offsets.emplace(keyboards_offset);
|
||||
while (this->keyboards.size() < num_keyboards) {
|
||||
uint32_t keyboard_offset = r.pget<U32T>(keyboards_offset + 4 * this->keyboards.size());
|
||||
used_offsets.emplace(keyboard_offset);
|
||||
auto& kb = this->keyboards.emplace_back(make_unique<Keyboard>());
|
||||
auto key_r = r.sub(keyboard_offset, sizeof(Keyboard));
|
||||
for (size_t y = 0; y < kb->size(); y++) {
|
||||
auto& row = kb->at(y);
|
||||
for (size_t x = 0; x < row.size(); x++) {
|
||||
row[x] = key_r.get<U16T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t collections_offset = r.pget<U32T>(r.size() - 4);
|
||||
for (uint32_t offset = collections_offset; !used_offsets.count(offset); offset += 4) {
|
||||
used_offsets.emplace(r.pget<U32T>(offset));
|
||||
}
|
||||
used_offsets.emplace(collections_offset);
|
||||
|
||||
for (uint32_t offset = collections_offset; (offset == collections_offset) || !used_offsets.count(offset); offset += 4) {
|
||||
auto& collection = this->collections.emplace_back();
|
||||
uint32_t first_string_offset_offset = r.pget<U32T>(offset);
|
||||
for (uint32_t string_offset_offset = first_string_offset_offset;
|
||||
(string_offset_offset == first_string_offset_offset) || !used_offsets.count(string_offset_offset);
|
||||
string_offset_offset += 4) {
|
||||
collection.emplace_back(r.pget_cstr(r.pget<U32T>(string_offset_offset)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
pair<string, string> TextArchive::serialize_t() const {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
using U16T = std::conditional_t<IsBigEndian, be_uint16_t, le_uint16_t>;
|
||||
|
||||
StringWriter w;
|
||||
set<size_t> relocation_offsets;
|
||||
auto put_offset_u32 = [&](uint32_t v) {
|
||||
relocation_offsets.emplace(w.size());
|
||||
w.put<U32T>(v);
|
||||
};
|
||||
|
||||
uint32_t collections_offset;
|
||||
{
|
||||
unordered_map<string, uint32_t> string_to_offset;
|
||||
for (const auto& collection : this->collections) {
|
||||
for (const auto& s : collection) {
|
||||
if (string_to_offset.emplace(s, w.size()).second) {
|
||||
w.write(s);
|
||||
w.put_u8(0);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<uint32_t> collection_offsets;
|
||||
for (const auto& collection : this->collections) {
|
||||
collection_offsets.emplace_back(w.size());
|
||||
for (const auto& s : collection) {
|
||||
put_offset_u32(string_to_offset.at(s));
|
||||
}
|
||||
}
|
||||
|
||||
collections_offset = w.size();
|
||||
for (uint32_t collection_offset : collection_offsets) {
|
||||
put_offset_u32(collection_offset);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t keyboard_index_offset;
|
||||
{
|
||||
vector<uint32_t> keyboard_offsets;
|
||||
for (const auto& keyboard : this->keyboards) {
|
||||
keyboard_offsets.emplace_back(w.size());
|
||||
for (size_t y = 0; y < keyboard->size(); y++) {
|
||||
const auto& row = keyboard->at(y);
|
||||
for (size_t x = 0; x < row.size(); x++) {
|
||||
w.put<U16T>(row[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t keyboards_offset = w.size();
|
||||
for (uint32_t keyboard_offset : keyboard_offsets) {
|
||||
put_offset_u32(keyboard_offset);
|
||||
}
|
||||
|
||||
keyboard_index_offset = w.size();
|
||||
w.put_u8(keyboard_offsets.size());
|
||||
w.put_u8(this->keyboard_selector_width);
|
||||
w.put_u16(0);
|
||||
put_offset_u32(keyboards_offset);
|
||||
}
|
||||
|
||||
put_offset_u32(keyboard_index_offset);
|
||||
put_offset_u32(collections_offset);
|
||||
|
||||
StringWriter reloc_w;
|
||||
reloc_w.put_u32(0);
|
||||
reloc_w.put<U32T>(relocation_offsets.size());
|
||||
reloc_w.put_u64(0);
|
||||
reloc_w.put<U32T>(w.size() - 8);
|
||||
reloc_w.put_u32(0);
|
||||
reloc_w.put_u64(0);
|
||||
{
|
||||
size_t offset = 0;
|
||||
for (size_t reloc_offset : relocation_offsets) {
|
||||
if (reloc_offset & 3) {
|
||||
throw logic_error("misaligned relocation");
|
||||
}
|
||||
size_t num_words = (reloc_offset - offset) >> 2;
|
||||
if (num_words > 0xFFFF) {
|
||||
throw runtime_error("relocation offset too far away");
|
||||
}
|
||||
reloc_w.put<U16T>(num_words);
|
||||
offset = reloc_offset;
|
||||
}
|
||||
}
|
||||
|
||||
const string& pr2_data = w.str();
|
||||
const string& pr3_data = reloc_w.str();
|
||||
print_data(stderr, pr2_data);
|
||||
string pr2_compressed = prs_compress_optimal(pr2_data.data(), pr2_data.size());
|
||||
string pr3_compressed = prs_compress_optimal(pr3_data.data(), pr3_data.size());
|
||||
print_data(stderr, pr2_compressed);
|
||||
string pr2_ret = encrypt_pr2_data<IsBigEndian>(pr2_compressed, pr2_data.size(), random_object<uint32_t>());
|
||||
string pr3_ret = encrypt_pr2_data<IsBigEndian>(pr3_compressed, pr3_data.size(), random_object<uint32_t>());
|
||||
print_data(stderr, pr2_ret);
|
||||
return make_pair(std::move(pr2_ret), std::move(pr3_ret));
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <phosg/JSON.hh>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
// This class implements loading and saving of text archives, commonly found in
|
||||
// PSO games with filenames like TextEnglish.pr2 and TextEnglish.pr3. The game
|
||||
// requires both files, but newserv needs only the pr2 file to load a text
|
||||
// archive. When saving (serializing), both pr2 and pr3 files are generated.
|
||||
class TextArchive {
|
||||
public:
|
||||
using Keyboard = parray<parray<uint16_t, 0x10>, 7>;
|
||||
|
||||
explicit TextArchive(const JSON& json);
|
||||
TextArchive(const std::string& pr2_data, bool big_endian);
|
||||
~TextArchive() = default;
|
||||
|
||||
JSON json() const;
|
||||
|
||||
const std::string& get_string(size_t collection_index, size_t index) const;
|
||||
void set_string(size_t collection_index, size_t index, const std::string& data);
|
||||
void set_string(size_t collection_index, size_t index, std::string&& data);
|
||||
void resize_collection(size_t collection_index, size_t size);
|
||||
void resize_collection(size_t num_collections);
|
||||
|
||||
Keyboard get_keyboard(size_t kb_index) const;
|
||||
void set_keyboard(size_t kb_index, const Keyboard& kb);
|
||||
void resize_keyboards(size_t num_keyboards);
|
||||
|
||||
uint8_t get_keyboard_selector_width() const;
|
||||
void set_keyboard_selector_width(uint8_t width);
|
||||
|
||||
// Returns (pr2_data, pr3_data)
|
||||
std::pair<std::string, std::string> serialize(bool big_endian) const;
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
void load_t(const std::string& pr2_data);
|
||||
template <bool IsBigEndian>
|
||||
std::pair<std::string, std::string> serialize_t() const;
|
||||
|
||||
std::vector<std::vector<std::string>> collections;
|
||||
std::vector<std::unique_ptr<Keyboard>> keyboards;
|
||||
uint8_t keyboard_selector_width;
|
||||
};
|
||||
@@ -0,0 +1,557 @@
|
||||
#include "TextIndex.hh"
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TextSet::TextSet(const JSON& json) {
|
||||
for (const auto& coll_json : json.as_list()) {
|
||||
auto& collection = this->collections.emplace_back();
|
||||
for (const auto& s_json : coll_json->as_list()) {
|
||||
collection.emplace_back(s_json->as_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextSet::TextSet(JSON&& json) {
|
||||
for (const auto& coll_json : json.as_list()) {
|
||||
auto& collection = this->collections.emplace_back();
|
||||
for (const auto& s_json : coll_json->as_list()) {
|
||||
collection.emplace_back(std::move(s_json->as_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSON TextSet::json() const {
|
||||
JSON j = JSON::list();
|
||||
for (const auto& collection : this->collections) {
|
||||
JSON& coll_j = j.emplace_back(JSON::list());
|
||||
for (const auto& s : collection) {
|
||||
coll_j.emplace_back(s);
|
||||
}
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
size_t TextSet::count(size_t collection_index) const {
|
||||
return this->collections.at(collection_index).size();
|
||||
}
|
||||
|
||||
size_t TextSet::count() const {
|
||||
return this->collections.size();
|
||||
}
|
||||
|
||||
const std::string& TextSet::get(size_t collection_index, size_t string_index) const {
|
||||
return this->get(collection_index).at(string_index);
|
||||
}
|
||||
|
||||
const vector<string>& TextSet::get(size_t collection_index) const {
|
||||
return this->collections.at(collection_index);
|
||||
}
|
||||
|
||||
void TextSet::set(size_t collection_index, size_t string_index, const std::string& data) {
|
||||
this->ensure_slot_exists(collection_index, string_index);
|
||||
this->collections[collection_index][string_index] = data;
|
||||
}
|
||||
|
||||
void TextSet::set(size_t collection_index, size_t string_index, std::string&& data) {
|
||||
this->ensure_slot_exists(collection_index, string_index);
|
||||
this->collections[collection_index][string_index] = std::move(data);
|
||||
}
|
||||
|
||||
void TextSet::set(size_t collection_index, const std::vector<std::string>& coll) {
|
||||
this->ensure_collection_exists(collection_index);
|
||||
this->collections[collection_index] = coll;
|
||||
}
|
||||
|
||||
void TextSet::set(size_t collection_index, std::vector<std::string>&& coll) {
|
||||
this->ensure_collection_exists(collection_index);
|
||||
this->collections[collection_index] = std::move(coll);
|
||||
}
|
||||
|
||||
void TextSet::truncate_collection(size_t collection_index, size_t new_entry_count) {
|
||||
if (collection_index >= this->collections.size()) {
|
||||
this->collections.resize(collection_index + 1);
|
||||
}
|
||||
this->collections[collection_index].resize(new_entry_count);
|
||||
}
|
||||
|
||||
void TextSet::truncate(size_t new_collection_count) {
|
||||
this->collections.resize(new_collection_count);
|
||||
}
|
||||
|
||||
void TextSet::ensure_slot_exists(size_t collection_index, size_t string_index) {
|
||||
this->ensure_collection_exists(collection_index);
|
||||
auto& coll = this->collections[collection_index];
|
||||
if (string_index >= coll.size()) {
|
||||
coll.resize(string_index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void TextSet::ensure_collection_exists(size_t collection_index) {
|
||||
if (collection_index >= this->collections.size()) {
|
||||
this->collections.resize(collection_index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeTextSet::UnicodeTextSet(const string& prs_data) {
|
||||
string data = prs_decompress(prs_data);
|
||||
StringReader r(data);
|
||||
|
||||
uint32_t num_collections = r.get_u32l();
|
||||
deque<uint32_t> collection_sizes;
|
||||
while (collection_sizes.size() < num_collections) {
|
||||
collection_sizes.emplace_back(r.get_u32l());
|
||||
}
|
||||
|
||||
this->collections.reserve(collection_sizes.size());
|
||||
while (!collection_sizes.empty()) {
|
||||
uint32_t num_strings = collection_sizes.front();
|
||||
collection_sizes.pop_front();
|
||||
|
||||
auto& strings = this->collections.emplace_back();
|
||||
strings.reserve(num_strings);
|
||||
while (strings.size() < num_strings) {
|
||||
StringReader sub_r = r.sub(r.get_u32l());
|
||||
StringWriter w;
|
||||
for (uint16_t ch = sub_r.get_u16l(); ch != 0; ch = sub_r.get_u16l()) {
|
||||
w.put_u16l(ch);
|
||||
}
|
||||
strings.emplace_back(tt_utf16_to_utf8(w.str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string UnicodeTextSet::serialize() const {
|
||||
StringWriter header_w;
|
||||
StringWriter data_w;
|
||||
|
||||
size_t total_num_strings = 0;
|
||||
header_w.put_u32l(this->collections.size());
|
||||
for (const auto& collection : this->collections) {
|
||||
header_w.put_u32l(collection.size());
|
||||
total_num_strings += collection.size();
|
||||
}
|
||||
|
||||
unordered_map<string, uint32_t> encoded;
|
||||
|
||||
size_t data_base_offset = (total_num_strings * 4) + header_w.size();
|
||||
for (const auto& collection : this->collections) {
|
||||
for (const auto& s : collection) {
|
||||
auto encoded_it = encoded.find(s);
|
||||
if (encoded_it == encoded.end()) {
|
||||
uint32_t offset = data_base_offset + data_w.size();
|
||||
encoded_it = encoded.emplace(s, offset).first;
|
||||
string s_utf16 = tt_utf8_to_utf16(s);
|
||||
data_w.write(s_utf16.data(), s_utf16.size());
|
||||
data_w.put_u16(0);
|
||||
while (data_w.size() & 3) {
|
||||
data_w.put_u8(0);
|
||||
}
|
||||
}
|
||||
header_w.put_u32l(encoded_it->second);
|
||||
}
|
||||
}
|
||||
|
||||
header_w.write(data_w.str());
|
||||
return prs_compress_optimal(header_w.str());
|
||||
}
|
||||
|
||||
BinaryTextSet::BinaryTextSet(const std::string& pr2_data, size_t collection_count, bool has_rel_footer) {
|
||||
auto pr2_decrypted = decrypt_pr2_data<false>(pr2_data);
|
||||
auto decompressed = prs_decompress(pr2_decrypted.compressed_data);
|
||||
StringReader r(decompressed);
|
||||
|
||||
// Annoyingly, there doesn't appear to be any bounds-checking on the language
|
||||
// functions, so there are no counts of strings in each collection. We have to
|
||||
// figure out where each collection ends by collecting all the relevant
|
||||
// offsets in the file instead.
|
||||
::set<uint32_t> used_offsets;
|
||||
size_t root_offset = has_rel_footer
|
||||
? r.pget_u32l(r.size() - 0x10)
|
||||
: (r.size() - collection_count * sizeof(le_uint32_t));
|
||||
|
||||
StringReader collection_offsets_r = r.sub(root_offset, collection_count * sizeof(le_uint32_t));
|
||||
while (!collection_offsets_r.eof()) {
|
||||
used_offsets.emplace(collection_offsets_r.get_u32l());
|
||||
}
|
||||
used_offsets.emplace(root_offset);
|
||||
|
||||
collection_offsets_r.go(0);
|
||||
while (!collection_offsets_r.eof()) {
|
||||
auto& collection = this->collections.emplace_back();
|
||||
uint32_t first_string_offset_offset = collection_offsets_r.get_u32l();
|
||||
// TODO: Apparently the early formats do actually include keyboards, but
|
||||
// they're just in the middle of the collections list. Sigh...
|
||||
try {
|
||||
for (uint32_t string_offset_offset = first_string_offset_offset;
|
||||
(string_offset_offset == first_string_offset_offset) || !used_offsets.count(string_offset_offset);
|
||||
string_offset_offset += 4) {
|
||||
collection.emplace_back(r.pget_cstr(r.pget_u32l(string_offset_offset)));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BinaryTextAndKeyboardsSet::BinaryTextAndKeyboardsSet(const string& pr2_data, bool big_endian) {
|
||||
if (big_endian) {
|
||||
this->parse_t<true>(pr2_data);
|
||||
} else {
|
||||
this->parse_t<false>(pr2_data);
|
||||
}
|
||||
}
|
||||
|
||||
BinaryTextAndKeyboardsSet::BinaryTextAndKeyboardsSet(const JSON& json) {
|
||||
for (const auto& collection_json : json.at("collections").as_list()) {
|
||||
auto& collection = this->collections.emplace_back();
|
||||
for (const auto& string_json : collection_json->as_list()) {
|
||||
collection.emplace_back(string_json->as_string());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& keyboard_json : json.at("keyboards").as_list()) {
|
||||
auto& keyboard = this->keyboards.emplace_back(make_unique<Keyboard>());
|
||||
for (size_t y = 0; y < keyboard->size(); y++) {
|
||||
auto& row = keyboard->at(y);
|
||||
const auto& row_json = keyboard_json->at(y);
|
||||
for (size_t x = 0; x < row.size(); x++) {
|
||||
row[x] = row_json.at(x).as_int();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->keyboard_selector_width = json.at("keyboard_selector_width").as_int();
|
||||
}
|
||||
|
||||
JSON BinaryTextAndKeyboardsSet::json() const {
|
||||
auto collections_json = this->TextSet::json();
|
||||
auto keyboards_json = JSON::list();
|
||||
for (const auto& kb : this->keyboards) {
|
||||
JSON keyboard_json = JSON::list();
|
||||
for (size_t y = 0; y < kb->size(); y++) {
|
||||
const auto& row = kb->at(y);
|
||||
JSON row_json = JSON::list();
|
||||
for (size_t x = 0; x < row.size(); x++) {
|
||||
row_json.emplace_back(row[x]);
|
||||
}
|
||||
keyboard_json.emplace_back(std::move(row_json));
|
||||
}
|
||||
keyboards_json.emplace_back(std::move(keyboard_json));
|
||||
}
|
||||
return JSON::dict({
|
||||
{"collections", std::move(collections_json)},
|
||||
{"keyboards", std::move(keyboards_json)},
|
||||
{"keyboard_selector_width", this->keyboard_selector_width},
|
||||
});
|
||||
}
|
||||
|
||||
const BinaryTextAndKeyboardsSet::Keyboard& BinaryTextAndKeyboardsSet::get_keyboard(size_t kb_index) const {
|
||||
return *this->keyboards.at(kb_index);
|
||||
}
|
||||
|
||||
void BinaryTextAndKeyboardsSet::set_keyboard(size_t kb_index, const Keyboard& kb) {
|
||||
if (kb_index >= this->keyboards.size()) {
|
||||
this->keyboards.resize(kb_index + 1);
|
||||
}
|
||||
this->keyboards[kb_index] = make_unique<Keyboard>(kb);
|
||||
}
|
||||
|
||||
void BinaryTextAndKeyboardsSet::resize_keyboards(size_t num_keyboards) {
|
||||
this->keyboards.resize(num_keyboards);
|
||||
}
|
||||
|
||||
pair<string, string> BinaryTextAndKeyboardsSet::serialize(bool big_endian) const {
|
||||
if (big_endian) {
|
||||
return this->serialize_t<true>();
|
||||
} else {
|
||||
return this->serialize_t<false>();
|
||||
}
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
void BinaryTextAndKeyboardsSet::parse_t(const string& pr2_data) {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
using U16T = std::conditional_t<IsBigEndian, be_uint16_t, le_uint16_t>;
|
||||
|
||||
// The structure is as follows:
|
||||
// Footer:
|
||||
// U32T keyboard_index_offset ->:
|
||||
// U8 num_keyboards
|
||||
// U8 keyboard_selector_width
|
||||
// U8 unused[2]
|
||||
// U32T keyboards_offset ->:
|
||||
// U32T keyboard_offset[num_keyboards] ->:
|
||||
// U16T key_defs[7][16]
|
||||
// U32T collections_offset ->:
|
||||
// U32T[...] strings_offset ->:
|
||||
// U32T[...] string_offset ->:
|
||||
// char string[...\0]
|
||||
// <EOF>
|
||||
|
||||
auto pr2_decrypted = decrypt_pr2_data<IsBigEndian>(pr2_data);
|
||||
auto decompressed = prs_decompress(pr2_decrypted.compressed_data);
|
||||
StringReader r(decompressed);
|
||||
|
||||
// Annoyingly, there doesn't appear to be any bounds-checking on the language
|
||||
// functions, so there are no counts of strings in each collection. We have to
|
||||
// figure out where each collection ends by collecting all the relevant
|
||||
// offsets in the file instead.
|
||||
::set<uint32_t> used_offsets;
|
||||
used_offsets.emplace(r.size() - 8);
|
||||
|
||||
uint32_t keyboard_index_offset = r.pget<U32T>(r.size() - 8);
|
||||
used_offsets.emplace(keyboard_index_offset);
|
||||
size_t num_keyboards = r.pget_u8(keyboard_index_offset);
|
||||
this->keyboard_selector_width = r.pget_u8(keyboard_index_offset + 1);
|
||||
uint32_t keyboards_offset = r.pget<U32T>(keyboard_index_offset + 4);
|
||||
used_offsets.emplace(keyboards_offset);
|
||||
while (this->keyboards.size() < num_keyboards) {
|
||||
uint32_t keyboard_offset = r.pget<U32T>(keyboards_offset + 4 * this->keyboards.size());
|
||||
used_offsets.emplace(keyboard_offset);
|
||||
auto& kb = this->keyboards.emplace_back(make_unique<Keyboard>());
|
||||
auto key_r = r.sub(keyboard_offset, sizeof(Keyboard));
|
||||
for (size_t y = 0; y < kb->size(); y++) {
|
||||
auto& row = kb->at(y);
|
||||
for (size_t x = 0; x < row.size(); x++) {
|
||||
row[x] = key_r.get<U16T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t collections_offset = r.pget<U32T>(r.size() - 4);
|
||||
for (uint32_t offset = collections_offset; !used_offsets.count(offset); offset += 4) {
|
||||
used_offsets.emplace(r.pget<U32T>(offset));
|
||||
}
|
||||
used_offsets.emplace(collections_offset);
|
||||
|
||||
for (uint32_t offset = collections_offset; (offset == collections_offset) || !used_offsets.count(offset); offset += 4) {
|
||||
auto& collection = this->collections.emplace_back();
|
||||
uint32_t first_string_offset_offset = r.pget<U32T>(offset);
|
||||
for (uint32_t string_offset_offset = first_string_offset_offset;
|
||||
(string_offset_offset == first_string_offset_offset) || !used_offsets.count(string_offset_offset);
|
||||
string_offset_offset += 4) {
|
||||
collection.emplace_back(r.pget_cstr(r.pget<U32T>(string_offset_offset)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
pair<string, string> BinaryTextAndKeyboardsSet::serialize_t() const {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
using U16T = std::conditional_t<IsBigEndian, be_uint16_t, le_uint16_t>;
|
||||
|
||||
StringWriter w;
|
||||
::set<size_t> relocation_offsets;
|
||||
auto put_offset_u32 = [&](uint32_t v) {
|
||||
relocation_offsets.emplace(w.size());
|
||||
w.put<U32T>(v);
|
||||
};
|
||||
|
||||
uint32_t collections_offset;
|
||||
{
|
||||
unordered_map<string, uint32_t> string_to_offset;
|
||||
for (const auto& collection : this->collections) {
|
||||
for (const auto& s : collection) {
|
||||
if (string_to_offset.emplace(s, w.size()).second) {
|
||||
w.write(s);
|
||||
w.put_u8(0);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<uint32_t> collection_offsets;
|
||||
for (const auto& collection : this->collections) {
|
||||
collection_offsets.emplace_back(w.size());
|
||||
for (const auto& s : collection) {
|
||||
put_offset_u32(string_to_offset.at(s));
|
||||
}
|
||||
}
|
||||
|
||||
collections_offset = w.size();
|
||||
for (uint32_t collection_offset : collection_offsets) {
|
||||
put_offset_u32(collection_offset);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t keyboard_index_offset;
|
||||
{
|
||||
vector<uint32_t> keyboard_offsets;
|
||||
for (const auto& keyboard : this->keyboards) {
|
||||
keyboard_offsets.emplace_back(w.size());
|
||||
for (size_t y = 0; y < keyboard->size(); y++) {
|
||||
const auto& row = keyboard->at(y);
|
||||
for (size_t x = 0; x < row.size(); x++) {
|
||||
w.put<U16T>(row[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t keyboards_offset = w.size();
|
||||
for (uint32_t keyboard_offset : keyboard_offsets) {
|
||||
put_offset_u32(keyboard_offset);
|
||||
}
|
||||
|
||||
keyboard_index_offset = w.size();
|
||||
w.put_u8(keyboard_offsets.size());
|
||||
w.put_u8(this->keyboard_selector_width);
|
||||
w.put_u16(0);
|
||||
put_offset_u32(keyboards_offset);
|
||||
}
|
||||
|
||||
put_offset_u32(keyboard_index_offset);
|
||||
put_offset_u32(collections_offset);
|
||||
|
||||
StringWriter reloc_w;
|
||||
reloc_w.put_u32(0);
|
||||
reloc_w.put<U32T>(relocation_offsets.size());
|
||||
reloc_w.put_u64(0);
|
||||
reloc_w.put<U32T>(w.size() - 8);
|
||||
reloc_w.put_u32(0);
|
||||
reloc_w.put_u64(0);
|
||||
{
|
||||
size_t offset = 0;
|
||||
for (size_t reloc_offset : relocation_offsets) {
|
||||
if (reloc_offset & 3) {
|
||||
throw logic_error("misaligned relocation");
|
||||
}
|
||||
size_t num_words = (reloc_offset - offset) >> 2;
|
||||
if (num_words > 0xFFFF) {
|
||||
throw runtime_error("relocation offset too far away");
|
||||
}
|
||||
reloc_w.put<U16T>(num_words);
|
||||
offset = reloc_offset;
|
||||
}
|
||||
}
|
||||
|
||||
const string& pr2_data = w.str();
|
||||
const string& pr3_data = reloc_w.str();
|
||||
print_data(stderr, pr2_data);
|
||||
string pr2_compressed = prs_compress_optimal(pr2_data.data(), pr2_data.size());
|
||||
string pr3_compressed = prs_compress_optimal(pr3_data.data(), pr3_data.size());
|
||||
print_data(stderr, pr2_compressed);
|
||||
string pr2_ret = encrypt_pr2_data<IsBigEndian>(pr2_compressed, pr2_data.size(), random_object<uint32_t>());
|
||||
string pr3_ret = encrypt_pr2_data<IsBigEndian>(pr3_compressed, pr3_data.size(), random_object<uint32_t>());
|
||||
print_data(stderr, pr2_ret);
|
||||
return make_pair(std::move(pr2_ret), std::move(pr3_ret));
|
||||
}
|
||||
|
||||
TextIndex::TextIndex(
|
||||
const string& directory,
|
||||
function<shared_ptr<const string>(Version, const string&)> get_patch_file)
|
||||
: log("[TextIndex] ", static_game_data_log.min_level) {
|
||||
if (!directory.empty()) {
|
||||
auto add_version = [&](Version version, const string& subdirectory, function<shared_ptr<TextSet>(const string&)> make_set) -> void {
|
||||
static const map<string, uint8_t> bintext_filenames({
|
||||
{"TextJapanese.pr2", 0x00},
|
||||
{"TextEnglish.pr2", 0x01},
|
||||
{"TextGerman.pr2", 0x02},
|
||||
{"TextFrench.pr2", 0x03},
|
||||
{"TextSpanish.pr2", 0x04},
|
||||
});
|
||||
static const map<string, uint8_t> unitext_filenames({
|
||||
{"unitxt_j.prs", 0x00}, // PC/BB Japanese
|
||||
{"unitxt_e.prs", 0x01}, // PC/BB English
|
||||
{"unitxt_g.prs", 0x02}, // PC/BB German
|
||||
{"unitxt_f.prs", 0x03}, // PC/BB French
|
||||
{"unitxt_s.prs", 0x04}, // PC/BB Spanish
|
||||
{"unitxt_b.prs", 0x05}, // PC Simplified Chinese
|
||||
{"unitxt_cs.prs", 0x05}, // BB Simplified Chinese
|
||||
{"unitxt_t.prs", 0x06}, // PC Traditional Chinese
|
||||
{"unitxt_ct.prs", 0x06}, // BB Traditional Chinese
|
||||
{"unitxt_k.prs", 0x07}, // PC Korean
|
||||
{"unitxt_h.prs", 0x07}, // BB Korean
|
||||
});
|
||||
if (!uses_utf16(version)) {
|
||||
for (const auto& it : bintext_filenames) {
|
||||
string file_path = directory + "/" + subdirectory + "/" + it.first;
|
||||
string json_path = file_path + ".json";
|
||||
if (isfile(json_path)) {
|
||||
this->log.info("Loading %s %c JSON text set from %s", name_for_enum(version), char_for_language_code(it.second), json_path.c_str());
|
||||
this->add_set(version, it.second, make_shared<BinaryTextSet>(JSON::parse(load_file(json_path))));
|
||||
} else if (isfile(file_path)) {
|
||||
this->log.info("Loading %s %c binary text set from %s", name_for_enum(version), char_for_language_code(it.second), file_path.c_str());
|
||||
this->add_set(version, it.second, make_set(load_file(file_path)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto& it : unitext_filenames) {
|
||||
string file_path = directory + "/" + subdirectory + "/" + it.first;
|
||||
string json_path = file_path + ".json";
|
||||
if (isfile(json_path)) {
|
||||
this->log.info("Loading %s %c JSON text set from %s", name_for_enum(version), char_for_language_code(it.second), json_path.c_str());
|
||||
this->add_set(version, it.second, make_shared<UnicodeTextSet>(JSON::parse(load_file(json_path))));
|
||||
} else {
|
||||
auto patch_file = get_patch_file ? get_patch_file(version, it.first) : nullptr;
|
||||
if (patch_file) {
|
||||
this->log.info("Loading %s %c Unicode text set from %s in patch tree", name_for_enum(version), char_for_language_code(it.second), it.first.c_str());
|
||||
this->add_set(version, it.second, make_set(*patch_file));
|
||||
} else {
|
||||
if (isfile(file_path)) {
|
||||
this->log.info("Loading %s %c Unicode text set from %s", name_for_enum(version), char_for_language_code(it.second), file_path.c_str());
|
||||
this->add_set(version, it.second, make_set(load_file(file_path)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto make_binary_dc112000 = +[](const string& data) { return make_shared<BinaryTextSet>(data, 21, true); };
|
||||
auto make_binary_dcnte_dcv1 = +[](const string& data) { return make_shared<BinaryTextSet>(data, 26, true); };
|
||||
auto make_binary_dcv2 = +[](const string& data) { return make_shared<BinaryTextSet>(data, 37, false); };
|
||||
auto make_binary_gc = +[](const string& data) { return make_shared<BinaryTextAndKeyboardsSet>(data, true); };
|
||||
auto make_binary_xb = +[](const string& data) { return make_shared<BinaryTextAndKeyboardsSet>(data, false); };
|
||||
auto make_unitxt = +[](const string& data) { return make_shared<UnicodeTextSet>(data); };
|
||||
|
||||
add_version(Version::DC_NTE, "dc-nte", make_binary_dcnte_dcv1);
|
||||
add_version(Version::DC_V1_11_2000_PROTOTYPE, "dc-11-2000", make_binary_dc112000);
|
||||
add_version(Version::DC_V1, "dc-v1", make_binary_dcnte_dcv1);
|
||||
add_version(Version::DC_V2, "dc-v2", make_binary_dcv2);
|
||||
add_version(Version::PC_NTE, "pc-nte", make_unitxt);
|
||||
add_version(Version::PC_V2, "pc-v2", make_unitxt);
|
||||
add_version(Version::GC_NTE, "gc-nte", make_binary_gc);
|
||||
add_version(Version::GC_V3, "gc-v3", make_binary_gc);
|
||||
add_version(Version::GC_EP3_NTE, "gc-ep3-nte", make_binary_gc);
|
||||
add_version(Version::GC_EP3, "gc-ep3", make_binary_gc);
|
||||
add_version(Version::XB_V3, "xb-v3", make_binary_xb);
|
||||
add_version(Version::BB_V4, "bb-v4", make_unitxt);
|
||||
}
|
||||
}
|
||||
|
||||
void TextIndex::add_set(Version version, uint8_t language, std::shared_ptr<const TextSet> ts) {
|
||||
this->sets[this->key_for_set(version, language)] = ts;
|
||||
}
|
||||
|
||||
void TextIndex::delete_set(Version version, uint8_t language) {
|
||||
this->sets.erase(this->key_for_set(version, language));
|
||||
}
|
||||
|
||||
const std::string& TextIndex::get(Version version, uint8_t language, size_t collection_index, size_t string_index) const {
|
||||
return this->get(version, language)->get(collection_index, string_index);
|
||||
}
|
||||
|
||||
const std::vector<std::string>& TextIndex::get(Version version, uint8_t language, size_t collection_index) const {
|
||||
return this->get(version, language)->get(collection_index);
|
||||
}
|
||||
|
||||
std::shared_ptr<const TextSet> TextIndex::get(Version version, uint8_t language) const {
|
||||
return this->sets.at(this->key_for_set(version, language));
|
||||
}
|
||||
|
||||
uint32_t TextIndex::key_for_set(Version version, uint8_t language) {
|
||||
return (static_cast<uint32_t>(version) << 8) | language;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <phosg/JSON.hh>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class TextSet {
|
||||
public:
|
||||
virtual ~TextSet() = default;
|
||||
virtual JSON json() const;
|
||||
|
||||
size_t count(size_t collection_index) const;
|
||||
size_t count() const;
|
||||
|
||||
const std::string& get(size_t collection, size_t index) const;
|
||||
const std::vector<std::string>& get(size_t collection) const;
|
||||
|
||||
void set(size_t collection_index, size_t string_index, const std::string& data);
|
||||
void set(size_t collection_index, size_t string_index, std::string&& data);
|
||||
void set(size_t collection_index, const std::vector<std::string>& coll);
|
||||
void set(size_t collection_index, std::vector<std::string>&& coll);
|
||||
|
||||
void truncate_collection(size_t collection, size_t new_entry_count);
|
||||
void truncate(size_t new_collection_count);
|
||||
|
||||
protected:
|
||||
std::vector<std::vector<std::string>> collections;
|
||||
|
||||
TextSet() = default;
|
||||
TextSet(const JSON& json);
|
||||
TextSet(JSON&& json);
|
||||
|
||||
void ensure_slot_exists(size_t collection_index, size_t string_index);
|
||||
void ensure_collection_exists(size_t collection_index);
|
||||
};
|
||||
|
||||
class UnicodeTextSet : public TextSet {
|
||||
public:
|
||||
explicit UnicodeTextSet(const JSON& json) : TextSet(json) {}
|
||||
explicit UnicodeTextSet(JSON&& json) : TextSet(json) {}
|
||||
explicit UnicodeTextSet(const std::string& unitxt_prs_data);
|
||||
virtual ~UnicodeTextSet() = default;
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
class BinaryTextSet : public TextSet {
|
||||
public:
|
||||
explicit BinaryTextSet(const JSON& json) : TextSet(json) {}
|
||||
explicit BinaryTextSet(JSON&& json) : TextSet(json) {}
|
||||
BinaryTextSet(const std::string& pr2_data, size_t collection_count, bool has_rel_footer);
|
||||
~BinaryTextSet() = default;
|
||||
// TODO: Implement serialize functions
|
||||
};
|
||||
|
||||
class BinaryTextAndKeyboardsSet : public TextSet {
|
||||
public:
|
||||
using Keyboard = parray<parray<uint16_t, 0x10>, 7>;
|
||||
|
||||
explicit BinaryTextAndKeyboardsSet(const JSON& json);
|
||||
explicit BinaryTextAndKeyboardsSet(JSON&& json);
|
||||
BinaryTextAndKeyboardsSet(const std::string& pr2_data, bool big_endian);
|
||||
~BinaryTextAndKeyboardsSet() = default;
|
||||
|
||||
virtual JSON json() const;
|
||||
|
||||
const Keyboard& get_keyboard(size_t kb_index) const;
|
||||
void set_keyboard(size_t kb_index, const Keyboard& kb);
|
||||
void resize_keyboards(size_t num_keyboards);
|
||||
|
||||
uint8_t get_keyboard_selector_width() const;
|
||||
void set_keyboard_selector_width(uint8_t width);
|
||||
|
||||
// Returns (pr2_data, pr3_data)
|
||||
std::pair<std::string, std::string> serialize(bool big_endian) const;
|
||||
|
||||
protected:
|
||||
template <bool IsBigEndian>
|
||||
void parse_t(const std::string& pr2_data);
|
||||
template <bool IsBigEndian>
|
||||
std::pair<std::string, std::string> serialize_t() const;
|
||||
|
||||
std::vector<std::unique_ptr<Keyboard>> keyboards;
|
||||
uint8_t keyboard_selector_width;
|
||||
};
|
||||
|
||||
class TextIndex {
|
||||
public:
|
||||
explicit TextIndex(
|
||||
const std::string& directory = "",
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_patch_file = nullptr);
|
||||
~TextIndex() = default;
|
||||
|
||||
void add_set(Version version, uint8_t language, std::shared_ptr<const TextSet> ts);
|
||||
void delete_set(Version version, uint8_t language);
|
||||
|
||||
const std::string& get(Version version, uint8_t language, size_t collection_index, size_t string_index) const;
|
||||
const std::vector<std::string>& get(Version version, uint8_t language, size_t collection_index) const;
|
||||
std::shared_ptr<const TextSet> get(Version version, uint8_t language) const;
|
||||
|
||||
protected:
|
||||
static uint32_t key_for_set(Version version, uint8_t language);
|
||||
|
||||
PrefixedLogger log;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<const TextSet>> sets;
|
||||
};
|
||||
@@ -1,75 +0,0 @@
|
||||
#include "UnicodeTextSet.hh"
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Compression.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
vector<vector<string>> parse_unicode_text_set(const string& prs_data) {
|
||||
string data = prs_decompress(prs_data);
|
||||
StringReader r(data);
|
||||
|
||||
uint32_t num_collections = r.get_u32l();
|
||||
deque<uint32_t> collection_sizes;
|
||||
while (collection_sizes.size() < num_collections) {
|
||||
collection_sizes.emplace_back(r.get_u32l());
|
||||
}
|
||||
|
||||
vector<vector<string>> ret;
|
||||
ret.reserve(collection_sizes.size());
|
||||
while (!collection_sizes.empty()) {
|
||||
uint32_t num_strings = collection_sizes.front();
|
||||
collection_sizes.pop_front();
|
||||
|
||||
auto& strings = ret.emplace_back();
|
||||
strings.reserve(num_strings);
|
||||
while (strings.size() < num_strings) {
|
||||
StringReader sub_r = r.sub(r.get_u32l());
|
||||
StringWriter w;
|
||||
for (uint16_t ch = sub_r.get_u16l(); ch != 0; ch = sub_r.get_u16l()) {
|
||||
w.put_u16l(ch);
|
||||
}
|
||||
strings.emplace_back(tt_utf16_to_utf8(w.str()));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
string serialize_unicode_text_set(const vector<vector<string>>& collections) {
|
||||
StringWriter header_w;
|
||||
StringWriter data_w;
|
||||
|
||||
size_t total_num_strings = 0;
|
||||
header_w.put_u32l(collections.size());
|
||||
for (const auto& collection : collections) {
|
||||
header_w.put_u32l(collection.size());
|
||||
total_num_strings += collection.size();
|
||||
}
|
||||
|
||||
unordered_map<string, uint32_t> encoded;
|
||||
|
||||
size_t data_base_offset = (total_num_strings * 4) + header_w.size();
|
||||
for (const auto& collection : collections) {
|
||||
for (const auto& s : collection) {
|
||||
auto encoded_it = encoded.find(s);
|
||||
if (encoded_it == encoded.end()) {
|
||||
uint32_t offset = data_base_offset + data_w.size();
|
||||
encoded.emplace(s, offset);
|
||||
string s_utf16 = tt_utf8_to_utf16(s);
|
||||
data_w.write(s_utf16.data(), s_utf16.size());
|
||||
data_w.put_u16(0);
|
||||
while (data_w.size() & 3) {
|
||||
data_w.put_u8(0);
|
||||
}
|
||||
} else {
|
||||
header_w.put_u32l(encoded_it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header_w.write(data_w.str());
|
||||
return std::move(header_w.str());
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
std::vector<std::vector<std::string>> parse_unicode_text_set(const std::string& prs_data);
|
||||
std::string serialize_unicode_text_set(const std::vector<std::vector<std::string>>& collections);
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user