rewrite text encoding to handle non-English properly

This commit is contained in:
Martin Michelsen
2023-10-24 12:02:22 -07:00
parent 6b97c628ef
commit 0c53a0dc41
65 changed files with 2483 additions and 2731 deletions
+2 -2
View File
@@ -21,7 +21,7 @@ template <bool IsBigEndian>
struct BMLHeaderEntry {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
ptext<char, 0x20> filename;
pstring<TextEncoding::ASCII, 0x20> filename;
U32T compressed_size;
parray<uint8_t, 0x04> unknown_a1;
U32T decompressed_size;
@@ -52,7 +52,7 @@ void BMLArchive::load_t() {
size_t gvm_offset = offset;
offset = (offset + entry.compressed_gvm_size + 0x1F) & (~0x1F);
this->entries.emplace(entry.filename, Entry{data_offset, entry.compressed_size, gvm_offset, entry.compressed_gvm_size});
this->entries.emplace(entry.filename.decode(), Entry{data_offset, entry.compressed_size, gvm_offset, entry.compressed_gvm_size});
}
}
+1
View File
@@ -40,6 +40,7 @@ CatSession::CatSession(
log("[CatSession] ", proxy_server_log.min_level),
channel(
version,
1,
CatSession::dispatch_on_channel_input,
CatSession::dispatch_on_channel_error,
this,
+5
View File
@@ -24,6 +24,7 @@ static void flush_and_free_bufferevent(struct bufferevent* bev) {
Channel::Channel(
GameVersion version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
@@ -32,6 +33,7 @@ Channel::Channel(
TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent),
version(version),
language(language),
name(name),
terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color),
@@ -43,6 +45,7 @@ Channel::Channel(
Channel::Channel(
struct bufferevent* bev,
GameVersion version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
@@ -51,6 +54,7 @@ Channel::Channel(
TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent),
version(version),
language(language),
name(name),
terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color),
@@ -71,6 +75,7 @@ void Channel::replace_with(
this->remote_addr = other.remote_addr;
this->is_virtual_connection = other.is_virtual_connection;
this->version = other.version;
this->language = other.language;
this->crypt_in = other.crypt_in;
this->crypt_out = other.crypt_out;
this->name = name;
+3
View File
@@ -16,6 +16,7 @@ struct Channel {
bool is_virtual_connection;
GameVersion version;
uint8_t language;
std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOEncryption> crypt_out;
@@ -39,6 +40,7 @@ struct Channel {
// Creates an unconnected channel
Channel(
GameVersion version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
@@ -49,6 +51,7 @@ struct Channel {
Channel(
struct bufferevent* bev,
GameVersion version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
+310 -328
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -10,5 +10,5 @@
#include "ProxyServer.hh"
#include "ServerState.hh"
void on_chat_command(std::shared_ptr<Client> c, const std::u16string& text);
void on_chat_command(std::shared_ptr<ProxyServer::LinkedSession> ses, const std::u16string& text);
void on_chat_command(std::shared_ptr<Client> c, const std::string& text);
void on_chat_command(std::shared_ptr<ProxyServer::LinkedSession> ses, const std::string& text);
+1 -2
View File
@@ -54,14 +54,13 @@ Client::Client(
bb_game_state(0),
flags(flags_for_version(version, -1)),
specific_version(default_specific_version_for_version(version, -1)),
channel(bev, version, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
channel(bev, version, 1, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
server_behavior(server_behavior),
should_disconnect(false),
should_send_to_lobby_server(false),
should_send_to_proxy_server(false),
proxy_destination_address(0),
proxy_destination_port(0),
language(1),
x(0.0f),
z(0.0f),
area(0),
+3 -1
View File
@@ -150,7 +150,6 @@ struct Client : public std::enable_shared_from_this<Client> {
// Lobby/positioning
ClientOptions options;
uint8_t language;
float x;
float z;
uint32_t area;
@@ -195,6 +194,9 @@ struct Client : public std::enable_shared_from_this<Client> {
inline GameVersion version() const {
return this->channel.version;
}
inline uint8_t language() const {
return this->channel.language;
}
QuestScriptVersion quest_version() const;
void set_license(std::shared_ptr<License> l);
+266 -282
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -354,7 +354,7 @@ void BattleRecordPlayer::schedule_events() {
send_command(l, 0xC9, 0x00, ev.data);
break;
case BattleRecord::Event::Type::CHAT_MESSAGE:
send_chat_message(l, ev.guild_card_number, decode_sjis(ev.data));
send_prepared_chat_message(l, ev.guild_card_number, ev.data);
break;
}
this->event_it++;
+9 -5
View File
@@ -166,10 +166,12 @@ ssize_t Card::apply_abnormal_condition(
cond.value8 = value + existing_cond_value;
cond.random_percent = random_percent;
switch (eff.arg1[0]) {
case 'a':
cond.a_arg_value = atoi(&eff.arg1[1]);
switch (eff.arg1.at(0)) {
case 'a': {
string s = eff.arg1.decode();
cond.a_arg_value = atoi(s.c_str() + 1);
break;
}
case 'e':
cond.remaining_turns = 99;
break;
@@ -179,8 +181,10 @@ ssize_t Card::apply_abnormal_condition(
case 'r':
cond.remaining_turns = 102;
break;
case 't':
cond.remaining_turns = atoi(&eff.arg1[1]);
case 't': {
string s = eff.arg1.decode();
cond.remaining_turns = atoi(s.c_str() + 1);
}
}
string cond_str = cond.str();
+19 -10
View File
@@ -358,7 +358,7 @@ bool CardSpecial::apply_defense_condition(
defense_state, defender_card, dice_roll, defender_cond->card_ref,
defender_cond->condition_giver_card_ref);
string expr = orig_eff->expr;
string expr = orig_eff->expr.decode();
int16_t expr_value = this->evaluate_effect_expr(astats, expr.c_str(), dice_roll);
this->execute_effect(
*defender_cond, defender_card, expr_value, defender_cond->value,
@@ -1397,9 +1397,9 @@ bool CardSpecial::evaluate_effect_arg2_condition(
if (ce->def.effects[cond_index].type == ConditionType::NONE) {
break;
}
uint8_t arg2_command = ce->def.effects[cond_index].arg2[0];
uint8_t arg2_command = ce->def.effects[cond_index].arg2.at(0);
if ((arg2_command == 'c') || (arg2_command == 'C')) {
uint8_t other_ch1 = ce->def.effects[cond_index].arg2[1] - 0x30;
uint8_t other_ch1 = ce->def.effects[cond_index].arg2.at(1) - 0x30;
if ((other_ch1 > 9)) {
return false;
}
@@ -2007,8 +2007,9 @@ bool CardSpecial::execute_effect(
case ConditionType::BONUS_FROM_LEADER:
if (unknown_p7 & 1) {
clamped_unknown_p5 = this->count_cards_with_card_id_except_card_ref(expr_value, 0xFFFF) + (card->action_chain).chain.ap_effect_bonus;
(card->action_chain).chain.ap_effect_bonus = clamp<int16_t>(clamped_unknown_p5, -99, 99);
size_t leader_count = this->count_cards_with_card_id_except_card_ref(expr_value, 0xFFFF);
card->action_chain.chain.ap_effect_bonus = clamp<int16_t>(
leader_count + card->action_chain.chain.ap_effect_bonus, -99, 99);
}
return true;
@@ -3192,12 +3193,16 @@ bool CardSpecial::is_card_targeted_by_condition(
}
if (cond.remaining_turns == 102) {
if (sc_card && ((sc_card == card) || !(sc_card->card_flags & 2))) {
string arg3_s = ce->def.effects[cond.card_definition_effect_index].arg3.decode();
if (arg3_s.size() < 1) {
throw runtime_error("card definition arg3 is missing");
}
auto target_cards = this->get_targeted_cards_for_condition(
cond.card_ref,
cond.card_definition_effect_index,
cond.condition_giver_card_ref,
as,
atoi(&ce->def.effects[cond.card_definition_effect_index].arg3[1]),
atoi(arg3_s.c_str() + 1),
0);
for (auto c : target_cards) {
if (c == card) {
@@ -3686,7 +3691,11 @@ void CardSpecial::evaluate_and_apply_effects(
continue;
}
int16_t arg3_value = atoi(&card_effect.arg3[1]);
string arg3_s = card_effect.arg3.decode();
if (arg3_s.size() < 1) {
throw runtime_error("card effect arg3 is missing");
}
int16_t arg3_value = atoi(arg3_s.c_str() + 1);
effect_log.debug("arg3_value=%hd", arg3_value);
auto targeted_cards = this->get_targeted_cards_for_condition(
set_card_ref, def_effect_index, sc_card_ref, as, arg3_value, 1);
@@ -3701,7 +3710,7 @@ void CardSpecial::evaluate_and_apply_effects(
size_t count = 0;
for (size_t z = 0; z < targeted_cards.size(); z++) {
dice_roll.value_used_in_expr = false;
string arg2_text = card_effect.arg2;
string arg2_text = card_effect.arg2.decode();
if (this->evaluate_effect_arg2_condition(
as, targeted_cards[z], arg2_text.c_str(), dice_roll,
set_card_ref, sc_card_ref, random_percent, when)) {
@@ -3731,7 +3740,7 @@ void CardSpecial::evaluate_and_apply_effects(
for (size_t z = 0; z < targeted_cards.size(); z++) {
auto target_log = effect_log.sub(string_printf("(target:@%04hX) ", targeted_cards[z]->get_card_ref()));
dice_roll.value_used_in_expr = false;
string arg2_str = card_effect.arg2;
string arg2_str = card_effect.arg2.decode();
target_log.debug("arg2_str = %s", arg2_str.c_str());
if (all_targets_matched ||
this->evaluate_effect_arg2_condition(
@@ -3739,7 +3748,7 @@ void CardSpecial::evaluate_and_apply_effects(
target_log.debug("arg2 condition passed");
auto env_stats = this->compute_attack_env_stats(
as, targeted_cards[z], dice_roll, set_card_ref, sc_card_ref);
string expr_str = card_effect.expr;
string expr_str = card_effect.expr.decode();
int16_t value = this->evaluate_effect_expr(env_stats, expr_str.c_str(), dice_roll);
target_log.debug("expr = %s, value = %hd", expr_str.c_str(), value);
+37 -36
View File
@@ -702,11 +702,11 @@ string CardDefinition::Stat::str() const {
bool CardDefinition::Effect::is_empty() const {
return (this->effect_num == 0 &&
this->type == ConditionType::NONE &&
this->expr.is_filled_with(0) &&
this->expr.empty() &&
this->when == 0 &&
this->arg1.is_filled_with(0) &&
this->arg2.is_filled_with(0) &&
this->arg3.is_filled_with(0) &&
this->arg1.empty() &&
this->arg2.empty() &&
this->arg3.empty() &&
this->apply_criterion == CriterionCode::NONE &&
this->name_index == 0);
}
@@ -781,12 +781,12 @@ string CardDefinition::Effect::str(const char* separator, const TextArchive* tex
tokens.emplace_back(std::move(cmd_str));
}
if (!this->expr.empty()) {
tokens.emplace_back("expr=" + string(this->expr));
tokens.emplace_back("expr=" + this->expr.decode());
}
tokens.emplace_back(string_printf("when=%02hhX", this->when));
tokens.emplace_back("arg1=" + this->str_for_arg(this->arg1));
tokens.emplace_back("arg2=" + this->str_for_arg(this->arg2));
tokens.emplace_back("arg3=" + this->str_for_arg(this->arg3));
tokens.emplace_back("arg1=" + this->str_for_arg(this->arg1.decode()));
tokens.emplace_back("arg2=" + this->str_for_arg(this->arg2.decode()));
tokens.emplace_back("arg3=" + this->str_for_arg(this->arg3.decode()));
{
uint8_t type = static_cast<uint8_t>(this->apply_criterion);
string cond_str = string_printf("cond=%02hhX", type);
@@ -1118,6 +1118,7 @@ string CardDefinition::str(bool single_line, const TextArchive* text_archive) co
}
}
string en_name_s = this->en_name.decode();
if (single_line) {
string range_str = string_for_range(this->range);
return string_printf(
@@ -1126,7 +1127,7 @@ string CardDefinition::str(bool single_line, const TextArchive* text_archive) co
"cannot_attack=%s cannot_drop=%s hp=%s ap=%s tp=%s mv=%s left=%s right=%s "
"top=%s class=%s assist_ai_params=[target=%s priority=%hhu effect=%hhu] drop_rates=[%s, %s] effects=[%s]]",
this->card_id.load(),
this->en_name.data(),
en_name_s.c_str(),
type_str.c_str(),
criterion_str.c_str(),
rank_str.c_str(),
@@ -1192,7 +1193,7 @@ Card: %04" PRIX32 " \"%s\"\n\
%s\n\
Effects:%s",
this->card_id.load(),
this->en_name.data(),
en_name_s.c_str(),
type_str.c_str(),
card_class_str.c_str(),
criterion_str.c_str(),
@@ -1635,7 +1636,7 @@ string MapDefinition::CameraSpec::str() const {
this->unknown_a2[1].load(), this->unknown_a2[2].load());
}
string MapDefinition::str(const CardIndex* card_index) const {
string MapDefinition::str(const CardIndex* card_index, uint8_t language) const {
deque<string> lines;
auto add_map = [&](const parray<parray<uint8_t, 0x10>, 0x10>& tiles) {
for (size_t y = 0; y < this->height; y++) {
@@ -1691,14 +1692,14 @@ string MapDefinition::str(const CardIndex* card_index) const {
" a5[0x70:0x74]: %02hhX %02hhX %02hhX %02hhX",
this->unknown_a5[0x70], this->unknown_a5[0x71], this->unknown_a5[0x72], this->unknown_a5[0x73]));
lines.emplace_back(" default_rules: " + this->default_rules.str());
lines.emplace_back(" name: " + format_data_string(this->name));
lines.emplace_back(" location_name: " + format_data_string(this->location_name));
lines.emplace_back(" quest_name: " + format_data_string(this->quest_name));
lines.emplace_back(" description: " + format_data_string(this->description));
lines.emplace_back(" name: " + format_data_string(this->name.decode(language)));
lines.emplace_back(" location_name: " + format_data_string(this->location_name.decode(language)));
lines.emplace_back(" quest_name: " + format_data_string(this->quest_name.decode(language)));
lines.emplace_back(" description: " + format_data_string(this->description.decode(language)));
lines.emplace_back(string_printf(" map_xy: %hu %hu", this->map_x.load(), this->map_y.load()));
for (size_t z = 0; z < 3; z++) {
lines.emplace_back(string_printf(" npc_chars[%zu]:", z));
lines.emplace_back(" name: " + format_data_string(this->npc_ai_params[z].name));
lines.emplace_back(" name: " + format_data_string(this->npc_ai_params[z].name.decode(language)));
lines.emplace_back(string_printf(
" ai_params: (a1: %04hX %04hX, is_arkz: %02hhX, a2: %02hX %02hX %02hX)",
this->npc_ai_params[z].unknown_a1[0].load(), this->npc_ai_params[z].unknown_a1[1].load(),
@@ -1719,7 +1720,7 @@ string MapDefinition::str(const CardIndex* card_index) const {
this->npc_ai_params[z].params[0x7A].load(), this->npc_ai_params[z].params[0x7B].load(),
this->npc_ai_params[z].params[0x7C].load(), this->npc_ai_params[z].params[0x7D].load()));
lines.emplace_back(string_printf(" npc_decks[%zu]:", z));
lines.emplace_back(" name: " + format_data_string(this->npc_decks[z].name));
lines.emplace_back(" name: " + format_data_string(this->npc_decks[z].name.decode(language)));
for (size_t w = 0; w < 0x20; w++) {
uint16_t card_id = this->npc_decks[z].card_ids[w];
shared_ptr<const CardIndex::CardEntry> entry;
@@ -1730,7 +1731,7 @@ string MapDefinition::str(const CardIndex* card_index) const {
}
}
if (entry) {
string name = entry->def.en_name;
string name = entry->def.en_name.decode(language);
lines.emplace_back(string_printf(" cards[%02zu]: %04hX (%s)", w, card_id, name.c_str()));
} else {
lines.emplace_back(string_printf(" cards[%02zu]: %04hX", w, card_id));
@@ -1744,8 +1745,8 @@ string MapDefinition::str(const CardIndex* card_index) const {
lines.emplace_back(string_printf(" npc_dialogue[%zu][%zu] (when: %04hX, chance: %hu%%):",
z, x, set.when.load(), set.percent_chance.load()));
for (size_t w = 0; w < 4; w++) {
if (set.strings[w][0] != 0 && static_cast<uint8_t>(set.strings[w][0]) != 0xFF) {
string escaped = format_data_string(set.strings[w]);
if (!set.strings[w].empty() && set.strings[w].at(0) != 0xFF) {
string escaped = format_data_string(set.strings[w].decode(language));
lines.emplace_back(string_printf(" strings[%zu]: %s", w, escaped.c_str()));
}
}
@@ -1754,14 +1755,14 @@ string MapDefinition::str(const CardIndex* card_index) const {
lines.emplace_back(" a7: " + format_data_string(this->unknown_a7.data(), this->unknown_a7.bytes()));
lines.emplace_back(string_printf(" npc_ai_params_entry_index: [%08" PRIX32 ", %08" PRIX32 ", %08" PRIX32 "]",
this->npc_ai_params_entry_index[0].load(), this->npc_ai_params_entry_index[1].load(), this->npc_ai_params_entry_index[2].load()));
if (this->before_message[0]) {
lines.emplace_back(" before_message: " + format_data_string(this->before_message));
if (!this->before_message.empty()) {
lines.emplace_back(" before_message: " + format_data_string(this->before_message.decode(language)));
}
if (this->after_message[0]) {
lines.emplace_back(" after_message: " + format_data_string(this->after_message));
if (!this->after_message.empty()) {
lines.emplace_back(" after_message: " + format_data_string(this->after_message.decode(language)));
}
if (this->dispatch_message[0]) {
lines.emplace_back(" dispatch_message: " + format_data_string(this->dispatch_message));
if (!this->dispatch_message.empty()) {
lines.emplace_back(" dispatch_message: " + format_data_string(this->dispatch_message.decode(language)));
}
for (size_t z = 0; z < 0x10; z++) {
uint16_t card_id = this->reward_card_ids[z];
@@ -1773,7 +1774,7 @@ string MapDefinition::str(const CardIndex* card_index) const {
}
}
if (entry) {
string name = entry->def.en_name;
string name = entry->def.en_name.decode(language);
lines.emplace_back(string_printf(" reward_cards[%02zu]: %04hX (%s)", z, card_id, name.c_str()));
} else {
lines.emplace_back(string_printf(" reward_cards[%02zu]: %04hX", z, card_id));
@@ -1955,7 +1956,7 @@ MapDefinitionTrial::operator MapDefinition() const {
ret.dialogue_sets[z][x].percent_chance = 0xFFFF;
for (size_t w = 0; w < 4; w++) {
ret.dialogue_sets[z][x].strings[w].clear(0xFF);
ret.dialogue_sets[z][x].strings[w][0] = 0x00;
ret.dialogue_sets[z][x].strings[w].set_byte(0, 0);
}
}
}
@@ -1972,7 +1973,7 @@ MapDefinitionTrial::operator MapDefinition() const {
// guess and fill in the field appropriately here.
size_t num_npc_decks = 0;
for (size_t z = 0; z < ret.npc_decks.size(); z++) {
if (ret.npc_decks[z].name[0]) {
if (!ret.npc_decks[z].name.empty()) {
num_npc_decks++;
}
}
@@ -2270,7 +2271,7 @@ CardIndex::CardIndex(
// Some cards intentionally have the same name, so we just leave them
// unindexed (they can still be looked up by ID, of course)
string name = entry->def.en_name;
string name = entry->def.en_name.decode(1);
this->card_definitions_by_name.emplace(name, entry);
this->card_definitions_by_name_normalized.emplace(this->normalize_card_name(name), entry);
@@ -2510,7 +2511,7 @@ MapIndex::MapIndex(const string& directory) {
throw runtime_error("unknown map file format");
}
string name = format_data_string(vm->map->name);
string name = format_data_string(vm->map->name.decode(vm->language));
auto map_it = this->maps.find(vm->map->map_number);
if (map_it == this->maps.end()) {
map_it = this->maps.emplace(vm->map->map_number, new Map(vm)).first;
@@ -2529,7 +2530,7 @@ MapIndex::MapIndex(const string& directory) {
vm->map->is_quest() ? "quest" : "free",
name.c_str());
}
this->maps_by_name.emplace(vm->map->name, map_it->second);
this->maps_by_name.emplace(vm->map->name.decode(vm->language), map_it->second);
} catch (const exception& e) {
static_game_data_log.warning("Failed to index Episode 3 map %s: %s",
@@ -2579,16 +2580,16 @@ const string& MapIndex::get_compressed_list(size_t num_players, uint8_t language
e.modification_tiles = vm->map->modification_tiles;
e.name_offset = strings_w.size();
strings_w.write(vm->map->name.data(), vm->map->name.len());
strings_w.write(vm->map->name.data, vm->map->name.used_bytes_8());
strings_w.put_u8(0);
e.location_name_offset = strings_w.size();
strings_w.write(vm->map->location_name.data(), vm->map->location_name.len());
strings_w.write(vm->map->location_name.data, vm->map->location_name.used_bytes_8());
strings_w.put_u8(0);
e.quest_name_offset = strings_w.size();
strings_w.write(vm->map->quest_name.data(), vm->map->quest_name.len());
strings_w.write(vm->map->quest_name.data, vm->map->quest_name.used_bytes_8());
strings_w.put_u8(0);
e.description_offset = strings_w.size();
strings_w.write(vm->map->description.data(), vm->map->description.len());
strings_w.write(vm->map->description.data, vm->map->description.used_bytes_8());
strings_w.put_u8(0);
e.map_category = vm->map->map_category;
+28 -28
View File
@@ -492,16 +492,16 @@ struct CardDefinition {
// operators to perform basic computations on them. Operators are evaluated
// left-to-right in the expression, and there are no operator precedence
// rules; for example, the expression "4+4//2" results in 4, not 6.
/* 02 */ ptext<char, 0x0F> expr;
/* 02 */ pstring<TextEncoding::ASCII, 0x0F> expr;
// when specifies in which phase the effect should activate.
// TODO: There are many values that can be used here; document them.
/* 11 */ uint8_t when;
// arg1 generally specifies how long the effect activates for.
/* 12 */ ptext<char, 4> arg1;
/* 12 */ pstring<TextEncoding::ASCII, 4> arg1;
// arg2 generally specifies a condition for when the effect activates.
/* 16 */ ptext<char, 4> arg2;
/* 16 */ pstring<TextEncoding::ASCII, 4> arg2;
// arg3 generally specifies who is targeted by the effect.
/* 1A */ ptext<char, 4> arg3;
/* 1A */ pstring<TextEncoding::ASCII, 4> arg3;
// apply_criterion can be used to apply an additional condition for when the
// effect should activate. For example, it can be used to make the effect
// only activate if the target is not a Story Character.
@@ -764,9 +764,9 @@ struct CardDefinition {
// enormous comment? That's what this array stores.
/* 009C */ parray<be_uint16_t, 2> drop_rates;
/* 00A0 */ ptext<char, 0x14> en_name;
/* 00B4 */ ptext<char, 0x0B> jp_short_name;
/* 00BF */ ptext<char, 0x08> en_short_name;
/* 00A0 */ pstring<TextEncoding::SJIS, 0x14> en_name;
/* 00B4 */ pstring<TextEncoding::SJIS, 0x0B> jp_short_name;
/* 00BF */ pstring<TextEncoding::SJIS, 0x08> en_short_name;
// These effects modify the card's behavior in various situations. Only
// effects for which effect_num is not zero are used.
/* 00C7 */ parray<Effect, 3> effects;
@@ -800,7 +800,7 @@ struct CardDefinitionsFooter {
} __attribute__((packed));
struct DeckDefinition {
/* 00 */ ptext<char, 0x10> name;
/* 00 */ pstring<TextEncoding::SJIS, 0x10> name;
/* 10 */ be_uint32_t client_id; // 0-3
// List of card IDs. The card count is the number of nonzero entries here
// before a zero entry (or 50 if no entries are nonzero). The first card ID is
@@ -823,7 +823,7 @@ struct PlayerConfig {
// The game splits this internally into two structures. The first column of
// offsets is relative to the start of the first structure; the second column
// is relative to the start of the second structure.
/* 0000:---- */ ptext<char, 12> rank_text; // From B7 command
/* 0000:---- */ pstring<TextEncoding::SJIS, 12> rank_text; // From B7 command
/* 000C:---- */ parray<uint8_t, 0x1C> unknown_a1;
/* 0028:---- */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
/* 0050:---- */ parray<be_uint32_t, 10> choice_search_config;
@@ -885,7 +885,7 @@ struct PlayerConfig {
/* 2124:1FD0 */ be_uint32_t online_clv_exp; // CLvOn = this / 100
struct PlayerReference {
/* 00 */ be_uint32_t guild_card_number;
/* 04 */ ptext<char, 0x18> player_name;
/* 04 */ pstring<TextEncoding::SJIS, 0x18> player_name;
} __attribute__((packed));
// This array is updated when a battle is started (via a 6xB4x05 command). The
// client adds the opposing players' info to ths first two entries here if the
@@ -1198,10 +1198,10 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 1D68 */ parray<uint8_t, 0x74> unknown_a5;
/* 1DDC */ Rules default_rules;
/* 1DF0 */ ptext<char, 0x14> name;
/* 1E04 */ ptext<char, 0x14> location_name;
/* 1E18 */ ptext<char, 0x3C> quest_name; // == location_name if not a quest
/* 1E54 */ ptext<char, 0x190> description;
/* 1DF0 */ pstring<TextEncoding::SJIS, 0x14> name;
/* 1E04 */ pstring<TextEncoding::SJIS, 0x14> location_name;
/* 1E18 */ pstring<TextEncoding::SJIS, 0x3C> quest_name; // == location_name if not a quest
/* 1E54 */ pstring<TextEncoding::SJIS, 0x190> description;
// These fields describe where the map cursor on the preview screen should
// scroll to
@@ -1209,7 +1209,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 1FE6 */ be_uint16_t map_y;
struct NPCDeck {
/* 00 */ ptext<char, 0x18> name;
/* 00 */ pstring<TextEncoding::SJIS, 0x18> name;
/* 18 */ parray<be_uint16_t, 0x20> card_ids; // Last one appears to always be FFFF
/* 58 */
} __attribute__((packed));
@@ -1223,7 +1223,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 0000 */ parray<be_uint16_t, 2> unknown_a1;
/* 0004 */ uint8_t is_arkz;
/* 0005 */ parray<uint8_t, 3> unknown_a2;
/* 0008 */ ptext<char, 0x10> name;
/* 0008 */ pstring<TextEncoding::SJIS, 0x10> name;
// TODO: Figure out exactly how these are used and document here.
/* 0018 */ parray<be_uint16_t, 0x7E> params;
/* 0114 */
@@ -1257,9 +1257,9 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// appears after the battle if it's not blank. dispatch_message appears right
// before the player chooses a deck if it's not blank; usually it says
// something like "You can only dispatch <character>".
/* 2440 */ ptext<char, 0x190> before_message;
/* 25D0 */ ptext<char, 0x190> after_message;
/* 2760 */ ptext<char, 0x190> dispatch_message;
/* 2440 */ pstring<TextEncoding::SJIS, 0x190> before_message;
/* 25D0 */ pstring<TextEncoding::SJIS, 0x190> after_message;
/* 2760 */ pstring<TextEncoding::SJIS, 0x190> dispatch_message;
struct DialogueSet {
// Dialogue sets specify lines that COMs can say at certain points during
@@ -1277,7 +1277,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 0002 */ be_uint16_t percent_chance; // 0-100, or FFFF if unused
// If the dialogue set activates, the game randomly chooses one of these
// strings, excluding any that are empty or begin with the character '^'.
/* 0004 */ parray<ptext<char, 0x40>, 4> strings;
/* 0004 */ parray<pstring<TextEncoding::SJIS, 0x40>, 4> strings;
/* 0104 */
} __attribute__((packed));
@@ -1375,7 +1375,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// text may differ.
void assert_semantically_equivalent(const MapDefinition& other) const;
std::string str(const CardIndex* card_index = nullptr) const;
std::string str(const CardIndex* card_index, uint8_t language) const;
} __attribute__((packed));
struct MapDefinitionTrial {
@@ -1396,19 +1396,19 @@ struct MapDefinitionTrial {
/* 1C68 */ parray<parray<uint8_t, 0x10>, 0x10> modification_tiles;
/* 1D68 */ parray<uint8_t, 0x74> unknown_a5;
/* 1DD4 */ RulesTrial default_rules;
/* 1DE8 */ ptext<char, 0x14> name;
/* 1DFC */ ptext<char, 0x14> location_name;
/* 1E10 */ ptext<char, 0x3C> quest_name;
/* 1E4C */ ptext<char, 0x190> description;
/* 1DE8 */ pstring<TextEncoding::SJIS, 0x14> name;
/* 1DFC */ pstring<TextEncoding::SJIS, 0x14> location_name;
/* 1E10 */ pstring<TextEncoding::SJIS, 0x3C> quest_name;
/* 1E4C */ pstring<TextEncoding::SJIS, 0x190> description;
/* 1FDC */ be_uint16_t map_x;
/* 1FDE */ be_uint16_t map_y;
/* 1FE0 */ parray<MapDefinition::NPCDeck, 3> npc_decks;
/* 20E8 */ parray<MapDefinition::AIParams, 3> npc_ai_params;
/* 2424 */ parray<uint8_t, 8> unknown_a7;
/* 242C */ parray<be_int32_t, 3> npc_ai_params_entry_index;
/* 2438 */ ptext<char, 0x190> before_message;
/* 25C8 */ ptext<char, 0x190> after_message;
/* 2758 */ ptext<char, 0x190> dispatch_message;
/* 2438 */ pstring<TextEncoding::SJIS, 0x190> before_message;
/* 25C8 */ pstring<TextEncoding::SJIS, 0x190> after_message;
/* 2758 */ pstring<TextEncoding::SJIS, 0x190> dispatch_message;
/* 28E8 */ parray<parray<MapDefinition::DialogueSet, 8>, 3> dialogue_sets;
/* 4148 */ parray<be_uint16_t, 0x10> reward_card_ids;
/* 4168 */ be_int32_t win_level_override;
+1 -1
View File
@@ -305,7 +305,7 @@ void DeckState::print(FILE* stream, std::shared_ptr<const CardIndex> card_index)
}
}
if (ce) {
string name = ce->def.en_name;
string name = ce->def.en_name.decode(1);
fprintf(stream, " (%02zu) index=%02hhX ref=@%04hX card_id=#%04hX \"%s\" %s\n",
z, e.deck_index, this->card_refs[z], e.card_id, name.c_str(), name_for_card_state(e.state));
} else {
+1 -1
View File
@@ -22,7 +22,7 @@ struct NameEntry {
} __attribute__((packed));
struct DeckEntry {
ptext<char, 0x10> name;
pstring<TextEncoding::SJIS, 0x10> name;
le_uint32_t team_id;
parray<le_uint16_t, 31> card_ids;
// If the following flag is not set to 3, then the God Whim assist effect can
+25 -28
View File
@@ -228,8 +228,8 @@ void Server::send_6xB4x46() const {
}
G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46;
cmd46.version_signature = VERSION_SIGNATURE;
cmd46.date_str1 = format_time(this->options.card_index->definitions_mtime() * 1000000);
cmd46.version_signature.encode(VERSION_SIGNATURE, 1);
cmd46.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
string date_str2 = string_printf(
"Lobby:%08" PRIX32 " Random:%08" PRIX32 "+%08" PRIX32,
l->lobby_id,
@@ -238,7 +238,7 @@ void Server::send_6xB4x46() const {
if (this->last_chosen_map) {
date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number);
}
cmd46.date_str2 = date_str2;
cmd46.date_str2.encode(date_str2, 1);
this->send(cmd46);
}
@@ -255,7 +255,7 @@ string Server::prepare_6xB6x41_map_definition(
return std::move(w.str());
}
void Server::send_commands_for_joining_spectator(Channel& c, uint8_t language, bool is_trial) const {
void Server::send_commands_for_joining_spectator(Channel& ch, bool is_trial) const {
bool should_send_state = true;
if (this->setup_phase == SetupPhase::REGISTRATION) {
// If registration is still in progress, we only need to send the map data
@@ -267,38 +267,38 @@ void Server::send_commands_for_joining_spectator(Channel& c, uint8_t language, b
}
if (this->last_chosen_map) {
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, language, is_trial);
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(language), this->last_chosen_map->map_number);
c.send(0x6C, 0x00, data);
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch.language, is_trial);
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(ch.language), this->last_chosen_map->map_number);
ch.send(0x6C, 0x00, data);
}
if (should_send_state) {
c.send(0xC9, 0x00, this->prepare_6xB4x03());
ch.send(0xC9, 0x00, this->prepare_6xB4x03());
for (uint8_t client_id = 0; client_id < 4; client_id++) {
auto ps = this->player_states[client_id];
if (ps) {
c.send(0xC9, 0x00, ps->prepare_6xB4x02());
c.send(0xC9, 0x00, ps->prepare_6xB4x04());
ch.send(0xC9, 0x00, ps->prepare_6xB4x02());
ch.send(0xC9, 0x00, ps->prepare_6xB4x04());
}
}
{
G_UpdateMap_GC_Ep3_6xB4x05 cmd_05;
cmd_05.state = *this->map_and_rules;
this->send(cmd_05);
ch.send(0xC9, 0x00, cmd_05);
}
// TODO: Sega does something like this; do we have to do this too?
// for (uint8_t client_id = 0; client_id < 4; client_id++) {
// (send 6xB4x4E, 6xB4x4C, 6xB4x4D for each set card)
// (send 6xB4x4F for client_id)
// }
c.send(0xC9, 0x00, this->prepare_6xB4x07_decks_update());
ch.send(0xC9, 0x00, this->prepare_6xB4x07_decks_update());
// TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also
// send 6xB4x02 again for each player after that (but not 6xB4x04)
c.send(0xC9, 0x00, this->prepare_6xB4x1C_names_update());
c.send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations());
ch.send(0xC9, 0x00, this->prepare_6xB4x1C_names_update());
ch.send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations());
{
G_LoadCurrentEnvironment_GC_Ep3_6xB4x3B cmd_3B;
c.send(0xC9, 0x00, &cmd_3B, sizeof(cmd_3B));
ch.send(0xC9, 0x00, &cmd_3B, sizeof(cmd_3B));
}
}
}
@@ -310,8 +310,7 @@ __attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(con
va_start(va, fmt);
std::string buf = string_vprintf(fmt, va);
va_end(va);
std::u16string decoded = decode_sjis(buf);
send_text_message(l, decoded.c_str());
send_text_message(l, buf);
}
}
@@ -322,8 +321,7 @@ __attribute__((format(printf, 2, 3))) void Server::send_info_message_printf(cons
va_start(va, fmt);
std::string buf = string_vprintf(fmt, va);
va_end(va);
std::u16string decoded = decode_sjis(buf);
send_text_message(l, decoded.c_str());
send_text_message(l, buf);
}
}
@@ -2317,8 +2315,7 @@ void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const st
throw runtime_error("lobby is deleted");
}
const auto& list_data = this->options.map_index->get_compressed_list(
l->count_clients(), sender_c->language);
const auto& list_data = this->options.map_index->get_compressed_list(l->count_clients(), sender_c->language());
StringWriter w;
uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3);
@@ -2347,15 +2344,15 @@ void Server::send_6xB6x41_to_all_clients() const {
if (!c) {
return;
}
if (map_commands_by_language.size() <= c->language) {
map_commands_by_language.resize(c->language + 1);
if (map_commands_by_language.size() <= c->language()) {
map_commands_by_language.resize(c->language() + 1);
}
if (map_commands_by_language[c->language].empty()) {
map_commands_by_language[c->language] = this->prepare_6xB6x41_map_definition(
this->last_chosen_map, c->language, l->flags & Lobby::Flag::IS_EP3_TRIAL);
if (map_commands_by_language[c->language()].empty()) {
map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition(
this->last_chosen_map, c->language(), l->flags & Lobby::Flag::IS_EP3_TRIAL);
}
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language), this->last_chosen_map->map_number);
send_command(c, 0x6C, 0x00, map_commands_by_language[c->language]);
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language()), this->last_chosen_map->map_number);
send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]);
};
for (const auto& c : l->clients) {
send_to_client(c);
+1 -1
View File
@@ -112,7 +112,7 @@ public:
this->send(&cmd, cmd.header.size * 4);
}
void send(const void* data, size_t size) const;
void send_commands_for_joining_spectator(Channel& ch, uint8_t language, bool is_trial) const;
void send_commands_for_joining_spectator(Channel& ch, bool is_trial) const;
void force_battle_result(uint8_t surrendered_client_id, bool set_winner);
void force_destroy_field_character(uint8_t client_id, size_t set_index);
+2 -2
View File
@@ -16,7 +16,7 @@ Tournament::PlayerEntry::PlayerEntry(uint32_t serial_number, const string& playe
Tournament::PlayerEntry::PlayerEntry(shared_ptr<Client> c)
: serial_number(c->license->serial_number),
client(c),
player_name(encode_sjis(c->game_data.player()->disp.name)) {}
player_name(c->game_data.player()->disp.name.decode(c->language())) {}
Tournament::PlayerEntry::PlayerEntry(
shared_ptr<const COMDeckDefinition> com_deck)
@@ -743,7 +743,7 @@ void Tournament::print_bracket(FILE* stream) const {
fprintf(stream, "Tournament \"%s\"\n", this->name.c_str());
auto en_vm = this->map->version(1);
if (en_vm) {
string map_name = en_vm->map->name;
string map_name = en_vm->map->name.decode(en_vm->language);
fprintf(stream, " Map: %08" PRIX32 " (%s)\n", this->map->map_number, map_name.c_str());
} else {
fprintf(stream, " Map: %08" PRIX32 "\n", this->map->map_number);
+6 -8
View File
@@ -236,13 +236,12 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
shared_ptr<const Menu> FunctionCodeIndex::patch_menu(uint32_t specific_version) const {
auto suffix = string_printf("-%08" PRIX32, specific_version);
shared_ptr<Menu> ret(new Menu(MenuID::PATCHES, u"Patches"));
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0);
shared_ptr<Menu> ret(new Menu(MenuID::PATCHES, "Patches"));
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
for (const auto& it : this->name_and_specific_version_to_patch_function) {
const auto& fn = it.second;
if (!fn->hide_from_patches_menu && ends_with(it.first, suffix)) {
ret->items.emplace_back(fn->menu_item_id, decode_sjis(fn->patch_name), u"",
MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
ret->items.emplace_back(fn->menu_item_id, fn->patch_name, "", MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
}
return ret;
@@ -267,9 +266,9 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
return;
}
shared_ptr<Menu> menu(new Menu(MenuID::PROGRAMS, u"Programs"));
shared_ptr<Menu> menu(new Menu(MenuID::PROGRAMS, "Programs"));
this->menu = menu;
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0);
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
uint32_t next_menu_item_id = 0;
for (const auto& filename : list_directory_sorted(directory)) {
@@ -326,8 +325,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
this->name_to_file.emplace(dol->name, dol);
this->item_id_to_file.emplace_back(dol);
menu->items.emplace_back(dol->menu_item_id, decode_sjis(dol->name),
decode_sjis(description), MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
menu->items.emplace_back(dol->menu_item_id, dol->name, description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
} catch (const exception& e) {
function_compiler_log.warning("Failed to load DOL file %s: %s", filename.c_str(), e.what());
+3 -3
View File
@@ -12,7 +12,7 @@ template <bool IsBigEndian>
struct GSLHeaderEntry {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
ptext<char, 0x20> filename;
pstring<TextEncoding::ASCII, 0x20> filename;
U32T offset; // In pages, so actual offset is this * 0x800
U32T size;
uint64_t unused;
@@ -24,14 +24,14 @@ void GSLArchive::load_t() {
uint64_t min_data_offset = 0xFFFFFFFFFFFFFFFF;
while (r.where() < min_data_offset) {
const auto& entry = r.get<GSLHeaderEntry<IsBigEndian>>();
if (entry.filename.len() == 0) {
if (entry.filename.empty()) {
break;
}
uint64_t offset = static_cast<uint64_t>(entry.offset) * 0x800;
if (offset + entry.size > this->data->size()) {
throw runtime_error("GSL entry extends beyond end of data");
}
this->entries.emplace(entry.filename, Entry{offset, entry.size});
this->entries.emplace(entry.filename.decode(), Entry{offset, entry.size});
}
}
+2 -2
View File
@@ -26,7 +26,7 @@ static uint32_t encode_argb8888(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
struct GVMFileEntry {
be_uint16_t file_num;
ptext<char, 28> name;
pstring<TextEncoding::ASCII, 0x1C> name;
parray<be_uint32_t, 2> unknown_a1;
} __attribute__((packed));
@@ -78,7 +78,7 @@ string encode_gvm(const Image& img, GVRDataFormat data_format) {
w.put<GVMFileHeader>({.magic = 0x47564D48, .header_size = 0x48, .flags = 0x010F, .num_files = 1});
GVMFileEntry file_entry;
file_entry.file_num = 0;
file_entry.name = "img";
file_entry.name.encode("img", 1);
file_entry.unknown_a1.clear(0);
w.put(file_entry);
w.extend_to(0x50, 0x00);
+2
View File
@@ -19,6 +19,8 @@ public:
} __attribute__((packed));
struct ItemBase {
// id specifies several things; notably, it doubles as the index of the
// item's name in the text archive (e.g. TextEnglish) collection 0.
le_uint32_t id;
le_uint16_t type;
le_uint16_t skin;
+8 -8
View File
@@ -82,11 +82,11 @@ string License::str() const {
}
struct BinaryLicense {
ptext<char, 0x14> username; // BB username (max. 16 chars; should technically be Unicode)
ptext<char, 0x14> bb_password; // BB password (max. 16 chars)
pstring<TextEncoding::ASCII, 0x14> username; // BB username (max. 16 chars; should technically be Unicode)
pstring<TextEncoding::ASCII, 0x14> bb_password; // BB password (max. 16 chars)
uint32_t serial_number; // PC/GC serial number. MUST BE PRESENT FOR BB LICENSES TOO; this is also the player's guild card number.
ptext<char, 0x10> access_key; // PC/GC access key. (to log in using PC on a GC license, just enter the first 8 characters of the GC access key)
ptext<char, 0x0C> gc_password; // GC password
pstring<TextEncoding::ASCII, 0x10> access_key; // PC/GC access key. (to log in using PC on a GC license, just enter the first 8 characters of the GC access key)
pstring<TextEncoding::ASCII, 0x0C> gc_password; // GC password
uint32_t privileges; // privilege level
uint64_t ban_end_time; // end time of ban (zero = not banned)
} __attribute__((packed));
@@ -107,10 +107,10 @@ LicenseIndex::LicenseIndex() {
} catch (const missing_license&) {
License license;
license.serial_number = bin_license.serial_number;
license.access_key = bin_license.access_key;
license.gc_password = bin_license.gc_password;
license.bb_username = bin_license.username;
license.bb_password = bin_license.bb_password;
license.access_key = bin_license.access_key.decode();
license.gc_password = bin_license.gc_password.decode();
license.bb_username = bin_license.username.decode();
license.bb_password = bin_license.bb_password.decode();
license.flags = bin_license.privileges;
license.ban_end_time = bin_license.ban_end_time;
license.ep3_current_meseta = 0;
+10 -10
View File
@@ -191,11 +191,11 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
PlayerLobbyDataDCGC lobby_data;
lobby_data.player_tag = 0x00010000;
lobby_data.guild_card = c->license->serial_number;
lobby_data.name = encode_sjis(p->disp.name);
lobby_data.name.encode(p->disp.name.decode(c->language()), c->language());
this->battle_record->add_player(
lobby_data,
p->inventory,
p->disp.to_dcpcv3(),
p->disp.to_dcpcv3(c->language(), c->language()),
c->game_data.ep3_config ? (c->game_data.ep3_config->online_clv_exp / 100) : 0);
}
@@ -276,18 +276,18 @@ void Lobby::move_client_to_lobby(
dest_lobby->add_client(c, required_client_id);
}
shared_ptr<Client> Lobby::find_client(
const u16string* identifier, uint64_t serial_number) {
shared_ptr<Client> Lobby::find_client(const string* identifier, uint64_t serial_number) {
for (size_t x = 0; x < this->max_clients; x++) {
if (!this->clients[x]) {
auto lc = this->clients[x];
if (!lc) {
continue;
}
if (serial_number && this->clients[x]->license &&
(this->clients[x]->license->serial_number == serial_number)) {
return this->clients[x];
if (serial_number && lc->license &&
(lc->license->serial_number == serial_number)) {
return lc;
}
if (identifier && (this->clients[x]->game_data.player()->disp.name == *identifier)) {
return this->clients[x];
if (identifier && (lc->game_data.player()->disp.name.eq(*identifier, lc->language()))) {
return lc;
}
}
+3 -3
View File
@@ -80,8 +80,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
GameMode mode;
uint8_t difficulty; // 0-3
uint16_t exp_multiplier;
std::u16string password;
std::u16string name;
std::string password;
std::string name;
// This seed is also sent to the client for rare enemy generation
uint32_t random_seed;
std::shared_ptr<PSOLFGEncryption> random_crypt;
@@ -154,7 +154,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
ssize_t required_client_id = -1);
std::shared_ptr<Client> find_client(
const std::u16string* identifier = nullptr,
const std::string* identifier = nullptr,
uint64_t serial_number = 0);
void add_item(const ItemData& item, uint8_t area, float x, float z);
+12 -22
View File
@@ -312,7 +312,6 @@ enum class Behavior {
DECODE_QUEST_FILE,
ENCODE_QST,
DISASSEMBLE_QUEST_SCRIPT,
DECODE_SJIS,
EXTRACT_AFS,
EXTRACT_GSL,
EXTRACT_BML,
@@ -368,7 +367,6 @@ static bool behavior_takes_input_filename(Behavior b) {
(b == Behavior::DECODE_QUEST_FILE) ||
(b == Behavior::ENCODE_QST) ||
(b == Behavior::DISASSEMBLE_QUEST_SCRIPT) ||
(b == Behavior::DECODE_SJIS) ||
(b == Behavior::FORMAT_RARE_ITEM_SET) ||
(b == Behavior::CONVERT_ITEMRT_REL_TO_JSON) ||
(b == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) ||
@@ -415,7 +413,6 @@ static bool behavior_takes_output_filename(Behavior b) {
(b == Behavior::CONVERT_ITEMRT_REL_TO_JSON) ||
(b == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) ||
(b == Behavior::CONVERT_ITEMRT_AFS_TO_JSON) ||
(b == Behavior::DECODE_SJIS) ||
(b == Behavior::EXTRACT_AFS) ||
(b == Behavior::EXTRACT_GSL) ||
(b == Behavior::EXTRACT_BML) ||
@@ -611,8 +608,6 @@ int main(int argc, char** argv) {
behavior = Behavior::FIND_DECRYPTION_SEED;
} else if (!strcmp(argv[x], "salvage-gci")) {
behavior = Behavior::SALVAGE_GCI;
} else if (!strcmp(argv[x], "decode-sjis")) {
behavior = Behavior::DECODE_SJIS;
} else if (!strcmp(argv[x], "decode-gci")) {
behavior = Behavior::DECODE_QUEST_FILE;
quest_file_type = QuestFileFormat::BIN_DAT_GCI;
@@ -1000,10 +995,12 @@ int main(int argc, char** argv) {
case Behavior::ENCRYPT_CHALLENGE_DATA:
case Behavior::DECRYPT_CHALLENGE_DATA: {
string data = read_input_data();
string result = (behavior == Behavior::DECRYPT_CHALLENGE_DATA)
? decrypt_challenge_rank_text(data)
: encrypt_challenge_rank_text(data);
write_output_data(result.data(), result.size());
if (behavior == Behavior::DECRYPT_CHALLENGE_DATA) {
decrypt_challenge_rank_text_t<uint8_t>(data.data(), data.size());
} else {
encrypt_challenge_rank_text_t<uint8_t>(data.data(), data.size());
}
write_output_data(data.data(), data.size());
break;
}
@@ -1414,18 +1411,11 @@ int main(int argc, char** argv) {
if (!expect_decompressed) {
data = prs_decompress(data);
}
string result = disassemble_quest_script(data.data(), data.size(), cli_quest_version);
string result = disassemble_quest_script(data.data(), data.size(), cli_quest_version, 1);
write_output_data(result.data(), result.size());
break;
}
case Behavior::DECODE_SJIS: {
string data = read_input_data();
auto decoded = decode_sjis(data);
write_output_data(decoded.data(), decoded.size() * sizeof(decoded[0]));
break;
}
case Behavior::EXTRACT_AFS:
case Behavior::EXTRACT_GSL:
case Behavior::EXTRACT_BML: {
@@ -1516,8 +1506,8 @@ int main(int argc, char** argv) {
case Behavior::DECODE_UNICODE_TEXT_SET: {
auto strings = parse_unicode_text_set(read_input_data());
JSON j = JSON::list();
for (const u16string& s : strings) {
j.emplace_back(encode_sjis(s));
for (const string& s : strings) {
j.emplace_back(s);
}
string out_data = j.serialize(JSON::SerializeOption::FORMAT);
write_output_data(out_data.data(), out_data.size());
@@ -1525,9 +1515,9 @@ int main(int argc, char** argv) {
}
case Behavior::ENCODE_UNICODE_TEXT_SET: {
auto json = JSON::parse(read_input_data());
vector<u16string> strings;
vector<string> strings;
for (const auto& s_json : json.as_list()) {
strings.emplace_back(decode_sjis(s_json->as_string()));
strings.emplace_back(s_json->as_string());
}
string encoded = serialize_unicode_text_set(strings);
write_output_data(encoded.data(), encoded.size());
@@ -1982,7 +1972,7 @@ int main(int argc, char** argv) {
if (!vms[language]) {
continue;
}
string s = vms[language]->map->str(&card_index);
string s = vms[language]->map->str(&card_index, language);
fprintf(stdout, "(%c) %s\n", char_for_language_code(language), s.c_str());
}
}
+3 -7
View File
@@ -2,24 +2,20 @@
using namespace std;
MenuItem::MenuItem(
uint32_t item_id, const u16string& name,
const u16string& description, uint32_t flags)
MenuItem::MenuItem(uint32_t item_id, const string& name, const string& description, uint32_t flags)
: item_id(item_id),
name(name),
description(description),
get_description(nullptr),
flags(flags) {}
MenuItem::MenuItem(
uint32_t item_id, const u16string& name,
std::function<std::u16string()> get_description, uint32_t flags)
MenuItem::MenuItem(uint32_t item_id, const string& name, std::function<std::string()> get_description, uint32_t flags)
: item_id(item_id),
name(name),
description(),
get_description(std::move(get_description)),
flags(flags) {}
Menu::Menu(uint32_t menu_id, const std::u16string& name)
Menu::Menu(uint32_t menu_id, const std::string& name)
: menu_id(menu_id),
name(name) {}
+7 -9
View File
@@ -106,22 +106,20 @@ struct MenuItem {
};
uint32_t item_id;
std::u16string name;
std::u16string description;
std::function<std::u16string()> get_description;
std::string name;
std::string description;
std::function<std::string()> get_description;
uint32_t flags;
MenuItem(uint32_t item_id, const std::u16string& name,
const std::u16string& description, uint32_t flags);
MenuItem(uint32_t item_id, const std::u16string& name,
std::function<std::u16string()> get_description, uint32_t flags);
MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags);
MenuItem(uint32_t item_id, const std::string& name, std::function<std::string()> get_description, uint32_t flags);
};
struct Menu {
uint32_t menu_id;
std::u16string name;
std::string name;
std::vector<MenuItem> items;
Menu() = delete;
Menu(uint32_t menu_id, const std::u16string& name);
Menu(uint32_t menu_id, const std::string& name);
};
-57
View File
@@ -932,63 +932,6 @@ uint16_t decrypt_challenge_time(uint32_t value) {
: ((mask ^ value) & 0xFFFF);
}
template <typename StringT, bool IsEncrypt>
StringT crypt_challenge_rank_text(const void* src, size_t count) {
if (count == 0) {
return StringT();
}
StringT ret;
StringReader r(src, count * sizeof(typename StringT::value_type));
typename StringT::value_type prev = 0;
while (!r.eof()) {
typename StringT::value_type ch = r.get<typename StringT::value_type>();
if (ch == 0) {
break;
}
if (ret.empty()) {
ret.push_back(ch ^ 0x7F);
} else {
ret.push_back((IsEncrypt ? ((ch - prev) ^ 0x7F) : ((ch ^ 0x7F) + ret.back())) & 0xFF);
}
prev = ch;
}
return ret;
}
string encrypt_challenge_rank_text(const char* src, size_t count) {
return crypt_challenge_rank_text<string, true>(src, count);
}
string decrypt_challenge_rank_text(const char* src, size_t count) {
return crypt_challenge_rank_text<string, false>(src, count);
}
u16string encrypt_challenge_rank_text(const char16_t* src, size_t count) {
return crypt_challenge_rank_text<u16string, true>(src, count);
}
u16string decrypt_challenge_rank_text(const char16_t* src, size_t count) {
return crypt_challenge_rank_text<u16string, false>(src, count);
}
std::string decrypt_challenge_rank_text(const std::string& data) {
return decrypt_challenge_rank_text(data.data(), data.size());
}
std::string encrypt_challenge_rank_text(const std::string& data) {
return encrypt_challenge_rank_text(data.data(), data.size());
}
std::u16string decrypt_challenge_rank_text(const std::u16string& data) {
return decrypt_challenge_rank_text(data.data(), data.size());
}
std::u16string encrypt_challenge_rank_text(const std::u16string& data) {
return encrypt_challenge_rank_text(data.data(), data.size());
}
string decrypt_v2_registry_value(const void* data, size_t size) {
string ret(reinterpret_cast<const char*>(data), size);
PSOV2Encryption crypt(0x66);
-29
View File
@@ -250,35 +250,6 @@ void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis);
uint32_t encrypt_challenge_time(uint16_t value);
uint16_t decrypt_challenge_time(uint32_t value);
std::string decrypt_challenge_rank_text(const char* data, size_t count);
std::string decrypt_challenge_rank_text(const std::string& data);
std::string encrypt_challenge_rank_text(const char* data, size_t count);
std::string encrypt_challenge_rank_text(const std::string& data);
std::u16string decrypt_challenge_rank_text(const char16_t* data, size_t count);
std::u16string decrypt_challenge_rank_text(const std::u16string& data);
std::u16string encrypt_challenge_rank_text(const char16_t* data, size_t count);
std::u16string encrypt_challenge_rank_text(const std::u16string& data);
template <size_t Size>
std::string decrypt_challenge_rank_text(const ptext<char, Size>& data) {
return decrypt_challenge_rank_text(data.data(), data.size());
}
template <size_t Size>
std::u16string decrypt_challenge_rank_text(const ptext<char16_t, Size>& data) {
return decrypt_challenge_rank_text(data.data(), data.size());
}
template <size_t Size>
std::string encrypt_challenge_rank_text(const ptext<char, Size>& data) {
return encrypt_challenge_rank_text(data.data(), data.size());
}
template <size_t Size>
std::u16string encrypt_challenge_rank_text(const ptext<char16_t, Size>& data) {
return encrypt_challenge_rank_text(data.data(), data.size());
}
std::string decrypt_v2_registry_value(const void* data, size_t size);
struct DecryptedPR2 {
+3 -3
View File
@@ -138,7 +138,7 @@ shared_ptr<SavedAccountDataBB> ClientGameData::account(bool allow_load) {
if (!this->account_data.get() && allow_load) {
if (this->bb_username.empty()) {
this->account_data.reset(new SavedAccountDataBB());
this->account_data->signature = ACCOUNT_FILE_SIGNATURE;
this->account_data->signature.encode(ACCOUNT_FILE_SIGNATURE);
} else {
this->load_account_data();
}
@@ -223,7 +223,7 @@ void ClientGameData::load_account_data() {
try {
data.reset(new SavedAccountDataBB(
player_files_cache.get_obj_or_load<SavedAccountDataBB>(filename).obj));
if (data->signature != ACCOUNT_FILE_SIGNATURE) {
if (!data->signature.eq(ACCOUNT_FILE_SIGNATURE)) {
throw runtime_error("account data header is incorrect");
}
player_data_log.info("Loaded account data file %s", filename.c_str());
@@ -236,7 +236,7 @@ void ClientGameData::load_account_data() {
player_files_cache.get_obj_or_load<SavedAccountDataBB>(
"system/players/default.nsa")
.obj));
if (data->signature != ACCOUNT_FILE_SIGNATURE) {
if (!data->signature.eq(ACCOUNT_FILE_SIGNATURE)) {
throw runtime_error("default account data header is incorrect");
}
player_data_log.info("Loaded default account data file");
+5 -5
View File
@@ -36,12 +36,12 @@ struct SavedPlayerDataBB { // .nsc file format
/* 0008 */ parray<uint8_t, 0x20> unused;
/* 0028 */ PlayerRecords_Battle<false> battle_records;
/* 0040 */ PlayerDispDataBBPreview preview;
/* 00BC */ ptext<char16_t, 0x00AC> auto_reply;
/* 00BC */ pstring<TextEncoding::UTF16, 0x00AC> auto_reply;
/* 0214 */ PlayerBank bank;
/* 14DC */ PlayerRecordsBB_Challenge challenge_records;
/* 161C */ PlayerDispDataBB disp;
/* 17AC */ ptext<char16_t, 0x0058> guild_card_description;
/* 185C */ ptext<char16_t, 0x00AC> info_board;
/* 17AC */ pstring<TextEncoding::UTF16, 0x0058> guild_card_description;
/* 185C */ pstring<TextEncoding::UTF16, 0x00AC> info_board;
/* 19B4 */ PlayerInventory inventory;
/* 1D00 */ parray<uint8_t, 0x0208> quest_data1;
/* 1F08 */ parray<le_uint32_t, 0x0016> quest_data2;
@@ -80,7 +80,7 @@ enum AccountFlag {
};
struct SavedAccountDataBB { // .nsa file format
ptext<char, 0x40> signature;
pstring<TextEncoding::ASCII, 0x40> signature;
parray<le_uint32_t, 0x001E> blocked_senders;
GuildCardFileBB guild_cards;
KeyAndTeamConfigBB key_config;
@@ -88,7 +88,7 @@ struct SavedAccountDataBB { // .nsa file format
le_uint32_t option_flags;
parray<uint8_t, 0x0A40> shortcuts;
parray<uint8_t, 0x04E0> symbol_chats;
ptext<char16_t, 0x0010> team_name;
pstring<TextEncoding::UTF16, 0x0010> team_name;
} __attribute__((packed));
class ClientGameData {
+39 -30
View File
@@ -134,29 +134,38 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits(GameVersion target_version)
this->visual.compute_name_color_checksum();
this->visual.class_flags = class_flags_for_class(this->visual.char_class);
if (this->visual.name.at(0) == '\t' && (this->visual.name.at(1) == 'J' || this->visual.name.at(1) == 'E')) {
this->visual.name.encode(this->visual.name.decode().substr(2));
}
}
void PlayerDispDataBB::enforce_lobby_join_limits(GameVersion) {
void PlayerDispDataBB::enforce_lobby_join_limits(GameVersion version) {
if (version != GameVersion::BB) {
throw logic_error("PlayerDispDataBB being sent to non-BB client");
}
this->play_time = 0;
if (this->name.at(0) != '\t' || (this->name.at(1) != 'E' && this->name.at(1) != 'J')) {
this->name.encode("\tJ" + this->name.decode());
}
}
PlayerDispDataBB PlayerDispDataDCPCV3::to_bb() const {
PlayerDispDataBB PlayerDispDataDCPCV3::to_bb(uint8_t to_language, uint8_t from_language) const {
PlayerDispDataBB bb;
bb.stats = this->stats;
bb.visual = this->visual;
bb.visual.name = " 0";
bb.name = this->visual.name;
bb.visual.name.encode(" 0");
bb.name.encode(this->visual.name.decode(from_language), to_language);
bb.config = this->config;
bb.technique_levels_v1 = this->technique_levels_v1;
return bb;
}
PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3() const {
PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3(uint8_t to_language, uint8_t from_language) const {
PlayerDispDataDCPCV3 ret;
ret.stats = this->stats;
ret.visual = this->visual;
ret.visual.name = this->name;
remove_language_marker_inplace(ret.visual.name);
ret.visual.name.encode(this->name.decode(from_language), to_language);
ret.config = this->config;
ret.technique_levels_v1 = this->technique_levels_v1;
return ret;
@@ -203,9 +212,9 @@ void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
void GuildCardBB::clear() {
this->guild_card_number = 0;
this->name.clear(0);
this->team_name.clear(0);
this->description.clear(0);
this->name.clear();
this->team_name.clear();
this->description.clear();
this->present = 0;
this->language = 0;
this->section_id = 0;
@@ -240,7 +249,7 @@ void PlayerLobbyDataPC::clear() {
this->guild_card = 0;
this->ip_address = 0;
this->client_id = 0;
ptext<char16_t, 0x10> name;
this->name.clear();
}
void PlayerLobbyDataDCGC::clear() {
@@ -248,7 +257,7 @@ void PlayerLobbyDataDCGC::clear() {
this->guild_card = 0;
this->ip_address = 0;
this->client_id = 0;
ptext<char, 0x10> name;
this->name.clear();
}
void XBNetworkLocation::clear() {
@@ -266,7 +275,7 @@ void PlayerLobbyDataXB::clear() {
this->guild_card = 0;
this->netloc.clear();
this->client_id = 0;
this->name.clear(0);
this->name.clear();
}
void PlayerLobbyDataBB::clear() {
@@ -275,7 +284,7 @@ void PlayerLobbyDataBB::clear() {
this->ip_address = 0;
this->unknown_a1.clear(0);
this->client_id = 0;
this->name.clear(0);
this->name.clear();
this->unknown_a2 = 0;
}
@@ -289,11 +298,11 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Chall
grave_deaths(rec.grave_deaths),
unknown_u4(0),
grave_coords_time(rec.grave_coords_time),
grave_team(rec.grave_team),
grave_message(rec.grave_message),
grave_team(rec.grave_team.decode(), 1),
grave_message(rec.grave_message.decode(), 1),
unknown_m5(0),
unknown_t6(0),
rank_title(encrypt_challenge_rank_text(decode_sjis(decrypt_challenge_rank_text(rec.rank_title)))),
rank_title(rec.rank_title.decode(), 1),
unknown_l7(0) {}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec)
@@ -306,8 +315,8 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Chall
grave_deaths(rec.grave_deaths),
unknown_u4(0),
grave_coords_time(rec.grave_coords_time),
grave_team(rec.grave_team),
grave_message(rec.grave_message),
grave_team(rec.grave_team.decode(), 1),
grave_message(rec.grave_message.decode(), 1),
unknown_m5(0),
unknown_t6(0),
rank_title(rec.rank_title),
@@ -323,24 +332,24 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsV3_Chall
grave_deaths(rec.stats.grave_deaths),
unknown_u4(rec.stats.unknown_u4),
grave_coords_time(rec.stats.grave_coords_time),
grave_team(rec.stats.grave_team),
grave_message(rec.stats.grave_message),
grave_team(rec.stats.grave_team.decode(), 1),
grave_message(rec.stats.grave_message.decode(), 1),
unknown_m5(rec.stats.unknown_m5),
unknown_t6(rec.stats.unknown_t6),
rank_title(encrypt_challenge_rank_text(decode_sjis(decrypt_challenge_rank_text(rec.rank_title)))),
rank_title(rec.rank_title.decode(), 1),
unknown_l7(rec.unknown_l7) {}
PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const {
PlayerRecordsDC_Challenge ret;
ret.title_color = this->title_color;
ret.unknown_u0 = this->unknown_u0;
ret.rank_title = encrypt_challenge_rank_text(encode_sjis(decrypt_challenge_rank_text(this->rank_title)));
ret.rank_title.encode(this->rank_title.decode());
ret.times_ep1_online = this->times_ep1_online;
ret.unknown_g3 = 0;
ret.grave_deaths = this->grave_deaths;
ret.grave_coords_time = this->grave_coords_time;
ret.grave_team = this->grave_team;
ret.grave_message = this->grave_message;
ret.grave_team.encode(this->grave_team.decode());
ret.grave_message.encode(this->grave_message.decode());
ret.times_ep1_offline = this->times_ep1_offline;
ret.unknown_l4.clear(0);
return ret;
@@ -355,8 +364,8 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const {
ret.unknown_g3 = 0;
ret.grave_deaths = this->grave_deaths;
ret.grave_coords_time = this->grave_coords_time;
ret.grave_team = this->grave_team;
ret.grave_message = this->grave_message;
ret.grave_team.encode(this->grave_team.decode());
ret.grave_message.encode(this->grave_message.decode());
ret.times_ep1_offline = this->times_ep1_offline;
ret.unknown_l4.clear(0);
return ret;
@@ -373,11 +382,11 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge<false>() const {
ret.stats.grave_deaths = this->grave_deaths;
ret.stats.unknown_u4 = this->unknown_u4;
ret.stats.grave_coords_time = this->grave_coords_time;
ret.stats.grave_team = this->grave_team;
ret.stats.grave_message = this->grave_message;
ret.stats.grave_team.encode(this->grave_team.decode());
ret.stats.grave_message.encode(this->grave_message.decode());
ret.stats.unknown_m5 = this->unknown_m5;
ret.stats.unknown_t6 = this->unknown_t6;
ret.rank_title = encrypt_challenge_rank_text(encode_sjis(decrypt_challenge_rank_text(this->rank_title)));
ret.rank_title.encode(this->rank_title.decode());
ret.unknown_l7 = this->unknown_l7;
return ret;
}
+53 -44
View File
@@ -102,7 +102,7 @@ struct PlayerBank {
struct PlayerDispDataBB;
struct PlayerVisualConfig {
/* 00 */ ptext<char, 0x10> name;
/* 00 */ pstring<TextEncoding::ASCII, 0x10> name;
/* 10 */ parray<uint8_t, 8> unknown_a2;
/* 18 */ le_uint32_t name_color = 0x00000000; // ARGB
/* 1C */ uint8_t extra_model = 0;
@@ -149,7 +149,7 @@ struct PlayerDispDataDCPCV3 {
/* BC */ parray<uint8_t, 0x14> technique_levels_v1;
/* D0 */
void enforce_lobby_join_limits(GameVersion target_version);
PlayerDispDataBB to_bb() const;
PlayerDispDataBB to_bb(uint8_t to_language, uint8_t from_language) const;
} __attribute__((packed));
struct PlayerDispDataBBPreview {
@@ -158,7 +158,7 @@ struct PlayerDispDataBBPreview {
// The name field in this structure is used for the player's Guild Card
// number, apparently (possibly because it's a char array and this is BB)
/* 08 */ PlayerVisualConfig visual;
/* 58 */ ptext<char16_t, 0x10> name;
/* 58 */ pstring<TextEncoding::UTF16, 0x10> name;
/* 78 */ uint32_t play_time = 0;
/* 7C */
} __attribute__((packed));
@@ -167,7 +167,7 @@ struct PlayerDispDataBBPreview {
struct PlayerDispDataBB {
/* 0000 */ PlayerStats stats;
/* 0024 */ PlayerVisualConfig visual;
/* 0074 */ ptext<char16_t, 0x0C> name;
/* 0074 */ pstring<TextEncoding::UTF16, 0x0C> name;
/* 008C */ le_uint32_t play_time = 0;
/* 0090 */ uint32_t unknown_a3 = 0;
/* 0094 */ parray<uint8_t, 0xE8> config;
@@ -175,18 +175,31 @@ struct PlayerDispDataBB {
/* 0190 */
void enforce_lobby_join_limits(GameVersion target_version);
PlayerDispDataDCPCV3 to_dcpcv3() const;
PlayerDispDataDCPCV3 to_dcpcv3(uint8_t to_language, uint8_t from_language) const;
PlayerDispDataBBPreview to_preview() const;
void apply_preview(const PlayerDispDataBBPreview&);
void apply_dressing_room(const PlayerDispDataBBPreview&);
} __attribute__((packed));
struct GuildCardDC {
/* 00 */ le_uint32_t player_tag = 0;
/* 04 */ le_uint32_t guild_card_number = 0;
/* 08 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 20 */ pstring<TextEncoding::MARKED, 0x48> description;
/* 68 */ parray<uint8_t, 0x11> unused2;
/* 79 */ uint8_t present = 0;
/* 7A */ uint8_t language = 0;
/* 7B */ uint8_t section_id = 0;
/* 7C */ uint8_t char_class = 0;
/* 7D */
} __attribute__((packed));
struct GuildCardPC {
/* 00 */ le_uint32_t player_tag = 0;
/* 04 */ le_uint32_t guild_card_number = 0;
// TODO: Is the length of the name field correct here?
/* 08 */ ptext<char16_t, 0x18> name;
/* 38 */ ptext<char16_t, 0x5A> description;
/* 08 */ pstring<TextEncoding::UTF16, 0x18> name;
/* 38 */ pstring<TextEncoding::UTF16, 0x5A> description;
/* EC */ uint8_t present = 0;
/* ED */ uint8_t language = 0;
/* EE */ uint8_t section_id = 0;
@@ -199,8 +212,8 @@ struct GuildCardPC {
struct GuildCardV3 {
/* 00 */ le_uint32_t player_tag = 0;
/* 04 */ le_uint32_t guild_card_number = 0;
/* 08 */ ptext<char, 0x18> name;
/* 20 */ ptext<char, 0x6C> description;
/* 08 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 20 */ pstring<TextEncoding::MARKED, 0x6C> description;
/* 8C */ uint8_t present = 0;
/* 8D */ uint8_t language = 0;
/* 8E */ uint8_t section_id = 0;
@@ -208,12 +221,11 @@ struct GuildCardV3 {
/* 90 */
} __attribute__((packed));
// BB guild card format
struct GuildCardBB {
/* 0000 */ le_uint32_t guild_card_number = 0;
/* 0004 */ ptext<char16_t, 0x18> name;
/* 0034 */ ptext<char16_t, 0x10> team_name;
/* 0054 */ ptext<char16_t, 0x58> description;
/* 0004 */ pstring<TextEncoding::UTF16, 0x18> name;
/* 0034 */ pstring<TextEncoding::UTF16, 0x10> team_name;
/* 0054 */ pstring<TextEncoding::UTF16, 0x58> description;
/* 0104 */ uint8_t present = 0;
/* 0105 */ uint8_t language = 0;
/* 0106 */ uint8_t section_id = 0;
@@ -226,7 +238,7 @@ struct GuildCardBB {
// an entry in the BB guild card file
struct GuildCardEntryBB {
GuildCardBB data;
ptext<char16_t, 0x58> comment;
pstring<TextEncoding::UTF16, 0x58> comment;
parray<uint8_t, 0x4> unknown_a1;
void clear();
@@ -251,7 +263,7 @@ struct KeyAndTeamConfigBB {
le_uint64_t team_info = 0; // 02C0
le_uint16_t team_privilege_level = 0; // 02C8
le_uint16_t reserved = 0; // 02CA
ptext<char16_t, 0x0010> team_name; // 02CC
pstring<TextEncoding::UTF16, 0x0010> team_name; // 02CC
parray<uint8_t, 0x0800> team_flag; // 02EC
le_uint32_t team_rewards = 0; // 0AEC
} __attribute__((packed));
@@ -266,7 +278,7 @@ struct PlayerLobbyDataPC {
// nonzero on all other versions too.
be_uint32_t ip_address = 0x7F000001;
le_uint32_t client_id = 0;
ptext<char16_t, 0x10> name;
pstring<TextEncoding::UTF16, 0x10> name;
void clear();
} __attribute__((packed));
@@ -276,7 +288,7 @@ struct PlayerLobbyDataDCGC {
le_uint32_t guild_card = 0;
be_uint32_t ip_address = 0x7F000001;
le_uint32_t client_id = 0;
ptext<char, 0x10> name;
pstring<TextEncoding::ASCII, 0x10> name;
void clear();
} __attribute__((packed));
@@ -298,7 +310,7 @@ struct PlayerLobbyDataXB {
le_uint32_t guild_card = 0;
XBNetworkLocation netloc;
le_uint32_t client_id = 0;
ptext<char, 0x10> name;
pstring<TextEncoding::ASCII, 0x10> name;
void clear();
} __attribute__((packed));
@@ -311,34 +323,32 @@ struct PlayerLobbyDataBB {
be_uint32_t ip_address = 0x7F000001;
parray<uint8_t, 0x10> unknown_a1;
le_uint32_t client_id = 0;
ptext<char16_t, 0x10> name;
pstring<TextEncoding::UTF16, 0x10> name;
le_uint32_t unknown_a2 = 0;
void clear();
} __attribute__((packed));
template <bool IsWideChar>
template <TextEncoding UnencryptedEncoding, TextEncoding EncryptedEncoding>
struct PlayerRecordsDCPC_Challenge {
using CharT = typename std::conditional<IsWideChar, char16_t, char>::type;
/* 00 */ le_uint16_t title_color = 0x7FFF;
/* 02 */ parray<uint8_t, 2> unknown_u0;
/* 04 */ ptext<CharT, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 04 */ pstring<EncryptedEncoding, 0x0C> rank_title;
/* 10 */ parray<le_uint32_t, 9> times_ep1_online; // Encrypted; see decrypt_challenge_time. TODO: This might be offline times
/* 34 */ le_uint16_t unknown_g3 = 0;
/* 36 */ le_uint16_t grave_deaths = 0;
/* 38 */ parray<le_uint32_t, 5> grave_coords_time;
/* 4C */ ptext<CharT, 0x14> grave_team;
/* 60 */ ptext<CharT, 0x18> grave_message;
/* 4C */ pstring<UnencryptedEncoding, 0x14> grave_team;
/* 60 */ pstring<UnencryptedEncoding, 0x18> grave_message;
/* 78 */ parray<le_uint32_t, 9> times_ep1_offline; // Encrypted; see decrypt_challenge_time. TODO: This might be online times
/* 9C */ parray<uint8_t, 4> unknown_l4;
/* A0 */
} __attribute__((packed));
struct PlayerRecordsDC_Challenge : PlayerRecordsDCPC_Challenge<false> {
struct PlayerRecordsDC_Challenge : PlayerRecordsDCPC_Challenge<TextEncoding::CHALLENGE8, TextEncoding::ASCII> {
} __attribute__((packed));
struct PlayerRecordsPC_Challenge : PlayerRecordsDCPC_Challenge<true> {
struct PlayerRecordsPC_Challenge : PlayerRecordsDCPC_Challenge<TextEncoding::CHALLENGE16, TextEncoding::UTF16> {
} __attribute__((packed));
template <bool IsBigEndian>
@@ -358,22 +368,21 @@ struct PlayerRecordsV3_Challenge {
/* 64:80 */ U16T grave_deaths = 0;
/* 66:82 */ parray<uint8_t, 2> unknown_u4;
/* 68:84 */ parray<U32T, 5> grave_coords_time;
/* 7C:98 */ ptext<char, 0x14> grave_team;
/* 90:AC */ ptext<char, 0x20> grave_message;
/* 7C:98 */ pstring<TextEncoding::ASCII, 0x14> grave_team;
/* 90:AC */ pstring<TextEncoding::ASCII, 0x20> grave_message;
/* B0:CC */ parray<uint8_t, 4> unknown_m5;
/* B4:D0 */ parray<U32T, 9> unknown_t6;
/* D8:F4 */
} __attribute__((packed));
/* 0000:001C */ Stats stats;
// On Episode 3, there are special cases that apply to this field - if the
// text ends with certain strings (after decrypt_challenge_rank_text), the
// player will have particle effects emanate from their character in the
// lobby every 2 seconds. These effects are:
// text ends with certain strings, the player will have particle effects
// emanate from their character in the lobby every 2 seconds. The effects are:
// Ends with ":GOD" => blue circle
// Ends with ":KING" => white particles
// Ends with ":LORD" => rising yellow sparkles
// Ends with ":CHAMP" => green circle
/* 00D8:00F4 */ ptext<char, 0x0C> rank_title;
/* 00D8:00F4 */ pstring<TextEncoding::CHALLENGE8, 0x0C> rank_title;
/* 00E4:0100 */ parray<uint8_t, 0x1C> unknown_l7;
/* 0100:011C */
} __attribute__((packed));
@@ -388,11 +397,11 @@ struct PlayerRecordsBB_Challenge {
/* 0064 */ le_uint16_t grave_deaths = 0;
/* 0066 */ parray<uint8_t, 2> unknown_u4;
/* 0068 */ parray<le_uint32_t, 5> grave_coords_time;
/* 007C */ ptext<char16_t, 0x14> grave_team;
/* 00A4 */ ptext<char16_t, 0x20> grave_message;
/* 007C */ pstring<TextEncoding::UTF16, 0x14> grave_team;
/* 00A4 */ pstring<TextEncoding::UTF16, 0x20> grave_message;
/* 00E4 */ parray<uint8_t, 4> unknown_m5;
/* 00E8 */ parray<le_uint32_t, 9> unknown_t6;
/* 010C */ ptext<char16_t, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 010C */ pstring<TextEncoding::UTF16, 0x0C> rank_title; // Encrypted; see decrypt_challenge_rank_text
/* 0124 */ parray<uint8_t, 0x1C> unknown_l7;
/* 0140 */
@@ -432,32 +441,32 @@ struct ChoiceSearchConfig {
} __attribute__((packed));
template <typename DestT, typename SrcT = DestT>
DestT convert_player_disp_data(const SrcT&) {
DestT convert_player_disp_data(const SrcT&, uint8_t, uint8_t) {
static_assert(always_false<DestT, SrcT>::v,
"unspecialized strcpy_t should never be called");
"unspecialized convert_player_disp_data should never be called");
}
template <>
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3>(
const PlayerDispDataDCPCV3& src) {
const PlayerDispDataDCPCV3& src, uint8_t, uint8_t) {
return src;
}
template <>
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(
const PlayerDispDataBB& src) {
return src.to_dcpcv3();
const PlayerDispDataBB& src, uint8_t to_language, uint8_t from_language) {
return src.to_dcpcv3(to_language, from_language);
}
template <>
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB, PlayerDispDataDCPCV3>(
const PlayerDispDataDCPCV3& src) {
return src.to_bb();
const PlayerDispDataDCPCV3& src, uint8_t to_language, uint8_t from_language) {
return src.to_bb(to_language, from_language);
}
template <>
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB>(
const PlayerDispDataBB& src) {
const PlayerDispDataBB& src, uint8_t, uint8_t) {
return src;
}
+120 -114
View File
@@ -107,7 +107,7 @@ static HandlerResult default_handler(shared_ptr<ProxyServer::LinkedSession>, uin
static HandlerResult S_invalid(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t command, uint32_t flag, string&) {
ses->log.error("Server sent invalid command");
string error_str = (ses->version == GameVersion::BB)
string error_str = (ses->version() == GameVersion::BB)
? string_printf("Server sent invalid\ncommand: %04hX %08" PRIX32, command, flag)
: string_printf("Server sent invalid\ncommand: %02hX %02" PRIX32, command, flag);
ses->send_to_game_server(error_str.c_str());
@@ -115,7 +115,7 @@ static HandlerResult S_invalid(shared_ptr<ProxyServer::LinkedSession> ses, uint1
}
static HandlerResult C_05(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string&) {
ses->disconnect_action = ses->version == GameVersion::BB
ses->disconnect_action = ses->version() == GameVersion::BB
? ProxyServer::LinkedSession::DisconnectAction::MEDIUM_TIMEOUT
: ProxyServer::LinkedSession::DisconnectAction::SHORT_TIMEOUT;
return HandlerResult::Type::FORWARD;
@@ -187,15 +187,15 @@ static HandlerResult S_G_9A(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t
cmd.unused2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = (ses->remote_guild_card_number < 0) ? 1 : 0;
cmd.language = ses->language;
cmd.serial_number = string_printf("%08" PRIX32 "", ses->license->serial_number);
cmd.access_key = ses->license->access_key;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
if (ses->options.blank_name) {
cmd.name = " ";
cmd.name.encode(" ", ses->language());
} else {
cmd.name = ses->character_name;
cmd.name.encode(ses->character_name, ses->language());
}
cmd.client_config.data = ses->remote_client_config_data;
@@ -213,7 +213,7 @@ static HandlerResult S_V123P_02_17(
uint16_t command,
uint32_t flag,
string& data) {
if (ses->version == GameVersion::PATCH && command == 0x17) {
if (ses->version() == GameVersion::PATCH && command == 0x17) {
throw invalid_argument("patch server sent 17 server init");
}
@@ -228,8 +228,8 @@ static HandlerResult S_V123P_02_17(
// client will be able to understand it.
forward_command(ses, false, command, flag, data);
if ((ses->version == GameVersion::GC) ||
(ses->version == GameVersion::XB)) {
if ((ses->version() == GameVersion::GC) ||
(ses->version() == GameVersion::XB)) {
ses->server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key));
ses->server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key));
ses->client_channel.crypt_in.reset(new PSOV3Encryption(cmd.client_key));
@@ -247,7 +247,7 @@ static HandlerResult S_V123P_02_17(
ses->log.info("Existing license in linked session");
// This isn't forwarded to the client, so don't recreate the client's crypts
switch (ses->version) {
switch (ses->version()) {
case GameVersion::DC:
case GameVersion::PC:
case GameVersion::PATCH:
@@ -267,18 +267,16 @@ static HandlerResult S_V123P_02_17(
// because it believes it already did (when it was in an unlinked session, or
// in the patch server case, during the current session due to a hidden
// redirect).
if (ses->version == GameVersion::PATCH) {
if (ses->version() == GameVersion::PATCH) {
ses->server_channel.send(0x02);
return HandlerResult::Type::SUPPRESS;
} else if ((ses->version == GameVersion::DC) ||
(ses->version == GameVersion::PC)) {
} else if ((ses->version() == GameVersion::DC) || (ses->version() == GameVersion::PC)) {
if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_DC_V1) {
if (command == 0x17) {
C_LoginV1_DC_PC_V3_90 cmd;
cmd.serial_number = string_printf("%08" PRIX32 "",
ses->license->serial_number);
cmd.access_key = ses->license->access_key;
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
ses->server_channel.send(0x90, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
@@ -295,13 +293,12 @@ static HandlerResult S_V123P_02_17(
cmd.unknown_a2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = 0;
cmd.language = ses->language;
cmd.serial_number = string_printf("%08" PRIX32 "",
ses->license->serial_number);
cmd.access_key = ses->license->access_key;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.hardware_id = ses->hardware_id;
cmd.name = ses->character_name;
cmd.hardware_id.encode(ses->hardware_id);
cmd.name.encode(ses->character_name);
ses->server_channel.send(0x93, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
}
@@ -316,9 +313,8 @@ static HandlerResult S_V123P_02_17(
cmd.guild_card_number = ses->remote_guild_card_number;
}
cmd.sub_version = ses->sub_version;
cmd.serial_number = string_printf("%08" PRIX32 "",
ses->license->serial_number);
cmd.access_key = ses->license->access_key;
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
@@ -340,33 +336,31 @@ static HandlerResult S_V123P_02_17(
cmd.unused2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = 0;
cmd.language = ses->language;
cmd.serial_number = string_printf("%08" PRIX32 "",
ses->license->serial_number);
cmd.access_key = ses->license->access_key;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
if (ses->options.blank_name) {
cmd.name = " ";
cmd.name.encode(" ", ses->language());
} else {
cmd.name = ses->character_name;
cmd.name.encode(ses->character_name);
}
ses->server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
}
}
} else if (ses->version == GameVersion::GC) {
} else if (ses->version() == GameVersion::GC) {
if (command == 0x17) {
C_VerifyLicense_V3_DB cmd;
cmd.serial_number = string_printf("%08" PRIX32 "",
ses->license->serial_number);
cmd.access_key = ses->license->access_key;
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.sub_version = ses->sub_version;
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.password = ses->license->gc_password;
cmd.password.encode(ses->license->gc_password);
ses->server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
@@ -394,15 +388,15 @@ static HandlerResult S_V123P_02_17(
cmd.unused2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = 0;
cmd.language = ses->language;
cmd.serial_number = string_printf("%08" PRIX32, fake_serial_number);
cmd.access_key = fake_access_key_str;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32, fake_serial_number));
cmd.access_key.encode(fake_access_key_str);
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
if (ses->options.blank_name) {
cmd.name = " ";
cmd.name.encode(" ", ses->language());
} else {
cmd.name = ses->character_name;
cmd.name.encode(ses->character_name, ses->language());
}
cmd.client_config.data = ses->remote_client_config_data;
ses->server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E));
@@ -413,7 +407,7 @@ static HandlerResult S_V123P_02_17(
return S_G_9A(ses, command, flag, data);
}
} else if (ses->version == GameVersion::XB) {
} else if (ses->version() == GameVersion::XB) {
throw runtime_error("xbox licenses are not implemented");
} else {
@@ -504,7 +498,7 @@ static HandlerResult S_V123_04(shared_ptr<ProxyServer::LinkedSession> ses, uint1
string message = string_printf(
"The remote server\nhas assigned your\nGuild Card number:\n\tC6%" PRId64,
ses->remote_guild_card_number);
send_ship_info(ses->client_channel, decode_sjis(message));
send_ship_info(ses->client_channel, message);
}
if (ses->license) {
cmd.guild_card_number = ses->license->serial_number;
@@ -621,7 +615,7 @@ static HandlerResult S_B2(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
if (ses->newserv_client_config.cfg.flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) {
StringReader r(code);
bool is_big_endian = (ses->version == GameVersion::GC || ses->version == GameVersion::DC);
bool is_big_endian = (ses->version() == GameVersion::GC || ses->version() == GameVersion::DC);
uint32_t decompressed_size = is_big_endian ? r.get_u32b() : r.get_u32l();
uint32_t key = is_big_endian ? r.get_u32b() : r.get_u32l();
@@ -657,7 +651,7 @@ static HandlerResult S_B2(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
ses->log.info("Wrote code from server to file %s", output_filename.c_str());
#ifdef HAVE_RESOURCE_FILE
if (ses->version == GameVersion::GC) {
if (ses->version() == GameVersion::GC) {
try {
if (code.size() < sizeof(S_ExecuteCode_Footer_GC_B2)) {
throw runtime_error("code section is too small");
@@ -817,7 +811,7 @@ static HandlerResult S_19_P_14(shared_ptr<ProxyServer::LinkedSession> ses, uint1
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(
&ses->next_destination);
sin->sin_family = AF_INET;
if (ses->version == GameVersion::PATCH) {
if (ses->version() == GameVersion::PATCH) {
auto& cmd = check_size_t<S_Reconnect_Patch_14>(data);
sin->sin_addr.s_addr = cmd.address.load_raw(); // Already big-endian
sin->sin_port = htons(cmd.port);
@@ -833,7 +827,7 @@ static HandlerResult S_19_P_14(shared_ptr<ProxyServer::LinkedSession> ses, uint1
ses->log.warning("Received reconnect command with no destination present");
return HandlerResult::Type::SUPPRESS;
} else if (ses->version != GameVersion::BB) {
} else if (ses->version() != GameVersion::BB) {
// Hide redirects from the client completely. The new destination server
// will presumably send a new encryption init command, which the handlers
// will appropriately respond to.
@@ -861,7 +855,7 @@ static HandlerResult S_V3_1A_D5(shared_ptr<ProxyServer::LinkedSession> ses, uint
// If the client is a version that sends close confirmations and the client
// has the no-close-confirmation flag set in its newserv client config, send a
// fake confirmation to the remote server immediately.
if (((ses->version == GameVersion::GC) || (ses->version == GameVersion::XB)) &&
if (((ses->version() == GameVersion::GC) || (ses->version() == GameVersion::XB)) &&
(ses->newserv_client_config.cfg.flags & Client::Flag::NO_D6)) {
ses->server_channel.send(0xD6);
}
@@ -870,7 +864,7 @@ static HandlerResult S_V3_1A_D5(shared_ptr<ProxyServer::LinkedSession> ses, uint
static HandlerResult S_V3_BB_DA(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t flag, string&) {
// This command is supported on all V3 versions except Ep1&2 Trial
if ((ses->version == GameVersion::GC) &&
if ((ses->version() == GameVersion::GC) &&
(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) {
return HandlerResult::Type::SUPPRESS;
} else if ((ses->options.override_lobby_event >= 0) &&
@@ -883,7 +877,7 @@ static HandlerResult S_V3_BB_DA(shared_ptr<ProxyServer::LinkedSession> ses, uint
static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
if (ses->options.save_files) {
if ((ses->version == GameVersion::GC) && (data.size() >= 0x14)) {
if ((ses->version() == GameVersion::GC) && (data.size() >= 0x14)) {
if (static_cast<uint8_t>(data[0]) == 0xB6) {
const auto& header = check_size_t<G_MapSubsubcommand_GC_Ep3_6xB6>(data, 0xFFFF);
if (header.subsubcommand == 0x00000041) {
@@ -907,7 +901,7 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
bool modified = false;
if (!data.empty()) {
// Unmask any masked Episode 3 commands from the server
if ((ses->version == GameVersion::GC) && (data.size() > 8) &&
if ((ses->version() == GameVersion::GC) && (data.size() > 8) &&
((static_cast<uint8_t>(data[0]) == 0xB3) ||
(static_cast<uint8_t>(data[0]) == 0xB4) ||
(static_cast<uint8_t>(data[0]) == 0xB5))) {
@@ -964,7 +958,7 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
return HandlerResult::Type::SUPPRESS;
}
} else if ((data[0] == 0x60) && ses->next_drop_item.data1d[0] && (ses->version != GameVersion::BB)) {
} else if ((data[0] == 0x60) && ses->next_drop_item.data1d[0] && (ses->version() != GameVersion::BB)) {
const auto& cmd = check_size_t<G_StandardDropItemRequest_DC_6x60>(
data, sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60));
ses->next_drop_item.id = ses->next_item_id++;
@@ -977,7 +971,7 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
// the comparison is always false (which even happens in some environments
// if we use -0x5E... apparently char is unsigned on some systems, or
// std::string's char_type isn't char??)
} else if ((static_cast<uint8_t>(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && (ses->version != GameVersion::BB)) {
} else if ((static_cast<uint8_t>(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && (ses->version() != GameVersion::BB)) {
const auto& cmd = check_size_t<G_SpecializableItemDropRequest_6xA2>(data);
ses->next_drop_item.id = ses->next_item_id++;
send_drop_item(ses->server_channel, ses->next_drop_item, false, cmd.area, cmd.x, cmd.z, cmd.entity_id);
@@ -986,7 +980,7 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
return HandlerResult::Type::SUPPRESS;
} else if ((static_cast<uint8_t>(data[0]) == 0xB5) &&
(ses->version == GameVersion::GC) &&
(ses->version() == GameVersion::GC) &&
(data.size() > 4)) {
if (data[4] == 0x1A) {
return HandlerResult::Type::SUPPRESS;
@@ -1007,13 +1001,13 @@ static HandlerResult C_GXB_61(shared_ptr<ProxyServer::LinkedSession> ses, uint16
// TODO: We should check if the info board text was actually modified and
// return MODIFIED if so.
if (ses->version == GameVersion::BB) {
if (ses->version() == GameVersion::BB) {
auto& pd = check_size_t<C_CharacterData_BB_61_98>(data, 0xFFFF);
if (ses->options.enable_chat_filter) {
add_color_inplace(pd.info_board.data(), pd.info_board.size());
pd.info_board.encode(add_color(pd.info_board.decode(ses->language())), ses->language());
}
if (ses->options.blank_name) {
pd.disp.name = " ";
pd.disp.name.encode(" ", ses->language());
modified = true;
}
if (ses->options.red_name && pd.disp.visual.name_color != 0xFFFF0000) {
@@ -1026,7 +1020,7 @@ static HandlerResult C_GXB_61(shared_ptr<ProxyServer::LinkedSession> ses, uint16
}
if (!ses->challenge_rank_title_override.empty()) {
pd.records.challenge.title_color = encode_xrgb1555(ses->challenge_rank_color_override);
pd.records.challenge.rank_title = encrypt_challenge_rank_text(ses->challenge_rank_title_override);
pd.records.challenge.rank_title.encode(ses->challenge_rank_title_override, ses->language());
}
} else {
@@ -1047,10 +1041,10 @@ static HandlerResult C_GXB_61(shared_ptr<ProxyServer::LinkedSession> ses, uint16
pd = &check_size_t<C_CharacterData_V3_61_98>(data, 0xFFFF);
}
if (ses->options.enable_chat_filter) {
add_color_inplace(pd->info_board.data(), pd->info_board.size());
pd->info_board.encode(add_color(pd->info_board.decode(ses->language())), ses->language());
}
if (ses->options.blank_name) {
pd->disp.visual.name = " ";
pd->disp.visual.name.encode(" ", ses->language());
modified = true;
}
if (ses->options.red_name && pd->disp.visual.name_color != 0xFFFF0000) {
@@ -1063,7 +1057,7 @@ static HandlerResult C_GXB_61(shared_ptr<ProxyServer::LinkedSession> ses, uint16
}
if (!ses->challenge_rank_title_override.empty()) {
pd->records.challenge.stats.title_color = encode_xrgb1555(ses->challenge_rank_color_override);
pd->records.challenge.rank_title = encrypt_challenge_rank_text(ses->challenge_rank_title_override);
pd->records.challenge.rank_title.encode(ses->challenge_rank_title_override, ses->language());
}
}
@@ -1072,7 +1066,7 @@ static HandlerResult C_GXB_61(shared_ptr<ProxyServer::LinkedSession> ses, uint16
static HandlerResult C_GX_D9(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
if (ses->options.enable_chat_filter) {
add_color_inplace(data.data(), data.size());
data = add_color(data);
// TODO: We should check if the info board text was actually modified and
// return MODIFIED if so.
}
@@ -1081,8 +1075,13 @@ static HandlerResult C_GX_D9(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
static HandlerResult C_B_D9(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
if (ses->options.enable_chat_filter) {
char16_t* text = reinterpret_cast<char16_t*>(data.data());
add_color_inplace(text, data.size() / sizeof(char16_t));
try {
string decoded = tt_utf16_to_utf8(data.data(), data.size());
add_color_inplace(decoded);
data = tt_utf8_to_utf16(data.data(), data.size());
} catch (const runtime_error& e) {
ses->log.warning("Failed to replace escape characters in D9 command: %s", e.what());
}
// TODO: We should check if the info board text was actually modified and
// return HandlerResult::MODIFIED if so.
}
@@ -1093,7 +1092,7 @@ template <typename T>
static HandlerResult S_44_A6(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t command, uint32_t, string& data) {
const auto& cmd = check_size_t<T>(data);
string filename = cmd.filename;
string filename = cmd.filename.decode();
string output_filename;
bool is_download = (command == 0xA6);
if (ses->options.save_files) {
@@ -1126,8 +1125,8 @@ static HandlerResult S_44_A6(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
// Episode 3 download quests aren't DLQ-encoded
bool decode_dlq = is_download && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3);
ProxyServer::LinkedSession::SavingFile sf(cmd.filename, output_filename, cmd.file_size, decode_dlq);
ses->saving_files.emplace(cmd.filename, std::move(sf));
ProxyServer::LinkedSession::SavingFile sf(filename, output_filename, cmd.file_size, decode_dlq);
ses->saving_files.emplace(filename, std::move(sf));
if (ses->options.save_files) {
ses->log.info("Saving %s from server to %s", filename.c_str(), output_filename.c_str());
} else {
@@ -1148,9 +1147,9 @@ static HandlerResult S_13_A7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
ProxyServer::LinkedSession::SavingFile* sf = nullptr;
try {
sf = &ses->saving_files.at(cmd.filename);
sf = &ses->saving_files.at(cmd.filename.decode());
} catch (const out_of_range&) {
string filename = cmd.filename;
string filename = cmd.filename.decode();
ses->log.warning("Received data for non-open file %s", filename.c_str());
return HandlerResult::Type::FORWARD;
}
@@ -1181,7 +1180,7 @@ static HandlerResult S_13_A7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
} else {
ses->log.info("Download complete for file %s", sf->basename.c_str());
}
ses->saving_files.erase(cmd.filename);
ses->saving_files.erase(cmd.filename.decode());
}
return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD;
@@ -1301,7 +1300,7 @@ static void update_leader_id(shared_ptr<ProxyServer::LinkedSession> ses, uint8_t
ses->leader_client_id = leader_id;
ses->log.info("Changed room leader to %zu", ses->leader_client_id);
if (ses->options.enable_player_notifications && (ses->leader_client_id == ses->lobby_client_id)) {
send_text_message(ses->client_channel, u"$C6You are now the leader");
send_text_message(ses->client_channel, "$C6You are now the leader");
}
}
}
@@ -1332,25 +1331,27 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ProxyServer::LinkedSession> ses, u
ses->lobby_client_id = cmd.lobby_flags.client_id;
update_leader_id(ses, cmd.lobby_flags.leader_id);
for (size_t x = 0; x < flag; x++) {
size_t index = cmd.entries[x].lobby_data.client_id;
auto& entry = cmd.entries[x];
size_t index = entry.lobby_data.client_id;
if (index >= ses->lobby_players.size()) {
ses->log.warning("Ignoring invalid player index %zu at position %zu", index, x);
} else {
string name = encode_sjis(cmd.entries[x].disp.visual.name);
string name = entry.disp.visual.name.decode(entry.inventory.language);
if (ses->license && (cmd.entries[x].lobby_data.guild_card == ses->remote_guild_card_number)) {
cmd.entries[x].lobby_data.guild_card = ses->license->serial_number;
if (ses->license && (entry.lobby_data.guild_card == ses->remote_guild_card_number)) {
entry.lobby_data.guild_card = ses->license->serial_number;
num_replacements++;
modified = true;
} else if (ses->options.enable_player_notifications && command != 0x67) {
send_text_message_printf(ses->client_channel, "$C6Join: %zu/%" PRIu32 "\n%s",
index, cmd.entries[x].lobby_data.guild_card.load(), name.c_str());
index, entry.lobby_data.guild_card.load(), name.c_str());
}
auto& p = ses->lobby_players[index];
p.guild_card_number = cmd.entries[x].lobby_data.guild_card;
p.guild_card_number = entry.lobby_data.guild_card;
p.name = name;
p.section_id = cmd.entries[x].disp.visual.section_id;
p.char_class = cmd.entries[x].disp.visual.char_class;
p.language = entry.inventory.language;
p.section_id = entry.disp.visual.section_id;
p.char_class = entry.disp.visual.char_class;
ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s",
index, p.guild_card_number, p.name.c_str());
}
@@ -1404,10 +1405,11 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
auto& p = ses->lobby_players[x];
p.guild_card_number = cmd->lobby_data[x].guild_card;
if (cmd_ep3) {
ptext<char, 0x10> name = cmd_ep3->players_ep3[x].disp.visual.name;
p.name = name;
p.section_id = cmd_ep3->players_ep3[x].disp.visual.section_id;
p.char_class = cmd_ep3->players_ep3[x].disp.visual.char_class;
const auto& p_ep3 = cmd_ep3->players_ep3[x];
p.language = p_ep3.inventory.language;
p.name = p_ep3.disp.visual.name.decode(p.language);
p.section_id = p_ep3.disp.visual.section_id;
p.char_class = p_ep3.disp.visual.char_class;
} else {
p.name.clear();
}
@@ -1465,8 +1467,8 @@ static HandlerResult S_E8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
auto& p = ses->lobby_players[x];
p.guild_card_number = player_entry.lobby_data.guild_card;
ptext<char, 0x10> name = player_entry.disp.visual.name;
p.name = name;
p.language = player_entry.inventory.language;
p.name = player_entry.disp.visual.name.decode(p.language);
p.section_id = player_entry.disp.visual.section_id;
p.char_class = player_entry.disp.visual.char_class;
ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s",
@@ -1521,9 +1523,9 @@ static HandlerResult C_98(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
ses->area = 0x0F;
ses->is_in_game = false;
ses->is_in_quest = false;
if (ses->version == GameVersion::GC ||
ses->version == GameVersion::XB ||
ses->version == GameVersion::BB) {
if (ses->version() == GameVersion::GC ||
ses->version() == GameVersion::XB ||
ses->version() == GameVersion::BB) {
return C_GXB_61(ses, command, flag, data);
} else {
return HandlerResult::Type::FORWARD;
@@ -1532,24 +1534,26 @@ static HandlerResult C_98(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
static HandlerResult C_06(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
if (data.size() >= 12) {
const auto& cmd = check_size_t<C_Chat_06>(data, 0xFFFF);
u16string text;
uint8_t private_flags = 0;
if (ses->version == GameVersion::PC || ses->version == GameVersion::BB) {
text = u16string(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t));
} else if ((cmd.text.dcv3[0] != '\t') &&
(ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) {
private_flags = cmd.text.dcv3[0];
text = decode_sjis(cmd.text.dcv3 + 1, data.size() - sizeof(C_Chat_06));
} else {
text = decode_sjis(cmd.text.dcv3, data.size() - sizeof(C_Chat_06));
}
const auto& cmd = check_size_t<SC_TextHeader_01_06_11_B0_EE>(data, 0xFFFF);
string text = data.substr(sizeof(cmd));
strip_trailing_zeroes(text);
uint8_t private_flags = 0;
if (ses->version() == GameVersion::PC || ses->version() == GameVersion::BB) {
if (text.size() & 1) {
text.push_back(0);
}
text = tt_decode_marked(text, ses->language(), true);
} else if (!text.empty() &&
(text[0] != '\t') &&
(ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) {
private_flags = text[0];
text = tt_decode_marked(text.substr(1), ses->language(), false);
} else {
text = tt_decode_marked(text, ses->language(), false);
}
if (text.empty()) {
return HandlerResult::Type::SUPPRESS;
}
@@ -1561,7 +1565,11 @@ static HandlerResult C_06(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
offset += (text[offset] == '$') ? 0 : 2;
text = text.substr(offset);
if (text.size() >= 2 && text[1] == '$') {
send_chat_message(ses->server_channel, text.substr(1), private_flags);
if (ses->options.enable_chat_filter) {
send_chat_message_from_client(ses->server_channel, add_color(text.substr(1)), private_flags);
} else {
send_chat_message_from_client(ses->server_channel, text.substr(1), private_flags);
}
return HandlerResult::Type::SUPPRESS;
} else {
on_chat_command(ses, text);
@@ -1569,10 +1577,8 @@ static HandlerResult C_06(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
}
} else if (ses->options.enable_chat_filter) {
add_color_inplace(data.data() + 8, data.size() - 8);
// TODO: We should return MODIFIED here if the message was changed by
// the add_color_inplace call
return HandlerResult::Type::FORWARD;
send_chat_message_from_client(ses->server_channel, add_color(text), private_flags);
return HandlerResult::Type::SUPPRESS;
} else {
return HandlerResult::Type::FORWARD;
@@ -1611,7 +1617,7 @@ static HandlerResult C_81(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
}
}
// GC clients send uninitialized memory here; don't forward it
cmd.text.clear_after(cmd.text.len());
cmd.text.clear_after(cmd.text.used_bytes_8());
return HandlerResult::Type::MODIFIED;
}
@@ -1631,10 +1637,10 @@ static HandlerResult C_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
if (ses->license && !data.empty()) {
// On BB, the 6x06 command is blank - the server generates the actual Guild
// Card contents and sends it to the target client.
if (data[0] == 0x06 && ses->version != GameVersion::BB) {
if (data[0] == 0x06 && ses->version() != GameVersion::BB) {
auto& cmd = check_size_t<SendGuildCardCmdT>(data);
if (cmd.guild_card_number == ses->license->serial_number) {
cmd.guild_card_number = ses->remote_guild_card_number;
if (cmd.guild_card.guild_card_number == ses->license->serial_number) {
cmd.guild_card.guild_card_number = ses->remote_guild_card_number;
}
}
}
@@ -1996,7 +2002,7 @@ void on_proxy_command(
uint32_t flag,
string& data) {
try {
auto fn = get_handler(ses->version, from_server, command);
auto fn = get_handler(ses->version(), from_server, command);
auto res = fn(ses, command, flag, data);
if (res.type == HandlerResult::Type::FORWARD) {
forward_command(ses, !from_server, command, flag, data, false);
+30 -40
View File
@@ -152,7 +152,7 @@ void ProxyServer::on_client_connect(
auto ses = emplace_ret.first->second;
ses->log.info("Opened linked session");
Channel ch(bev, version, nullptr, nullptr, ses.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN);
Channel ch(bev, version, 1, nullptr, nullptr, ses.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN);
ses->resume(std::move(ch));
// If no default destination exists, or the client is not a patch client,
@@ -229,6 +229,7 @@ ProxyServer::UnlinkedSession::UnlinkedSession(
channel(
bev,
version,
1,
ProxyServer::UnlinkedSession::on_input,
ProxyServer::UnlinkedSession::on_error,
this,
@@ -260,7 +261,6 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
bool should_close_unlinked_session = false;
shared_ptr<License> license;
uint32_t sub_version = 0;
uint8_t language = 1; // Default = English
string character_name;
ClientConfigBB client_config;
string login_command_bb;
@@ -272,20 +272,18 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
// anything else, disconnect
if (command == 0x93) {
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data);
license = s->license_index->verify_v1_v2(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
sub_version = cmd.sub_version;
language = cmd.language;
character_name = cmd.name;
hardware_id = cmd.hardware_id;
ses->channel.language = cmd.language;
character_name = cmd.name.decode(ses->channel.language);
hardware_id = cmd.hardware_id.decode();
client_config.cfg.flags |= Client::Flag::IS_DC_V1;
} else if (command == 0x9D) {
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_DC_GC_9D));
license = s->license_index->verify_v1_v2(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
sub_version = cmd.sub_version;
language = cmd.language;
character_name = cmd.name;
ses->channel.language = cmd.language;
character_name = cmd.name.decode(ses->channel.language);
} else {
throw runtime_error("command is not 93 or 9D");
}
@@ -297,11 +295,10 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
throw runtime_error("command is not 9D");
}
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_PC_9D));
license = s->license_index->verify_v1_v2(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
sub_version = cmd.sub_version;
language = cmd.language;
character_name = cmd.name;
ses->channel.language = cmd.language;
character_name = cmd.name.decode(ses->channel.language);
} else if (ses->version == GameVersion::GC) {
// We should only get a 9E while the session is unlinked; if we get
@@ -311,11 +308,10 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
throw runtime_error("command is not 9E");
}
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_GC_9E));
license = s->license_index->verify_gc(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
sub_version = cmd.sub_version;
language = cmd.language;
character_name = cmd.name;
ses->channel.language = cmd.language;
character_name = cmd.name.decode(ses->channel.language);
client_config.cfg = cmd.client_config.cfg;
} else if (ses->version == GameVersion::XB) {
@@ -329,16 +325,15 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
}
const auto& cmd = check_size_t<C_Login_BB_93>(data);
try {
license = s->license_index->verify_bb(
cmd.username, cmd.password);
license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode());
} catch (const LicenseIndex::missing_license&) {
if (!s->allow_unregistered_users) {
throw;
}
shared_ptr<License> l(new License());
l->serial_number = fnv1a32(cmd.username) & 0x7FFFFFFF;
l->bb_username = cmd.username;
l->bb_password = cmd.password;
l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF;
l->bb_username = cmd.username.decode();
l->bb_password = cmd.password.decode();
s->license_index->add(l);
license = l;
string l_str = l->str();
@@ -392,7 +387,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
if (linked_ses.get()) {
server->id_to_session.emplace(license->serial_number, linked_ses);
if (linked_ses->version != ses->version) {
if (linked_ses->version() != ses->version) {
linked_ses->log.error("Linked session has different game version");
} else {
// Resume the linked session using the unlinked session
@@ -407,7 +402,6 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
std::move(ses->channel),
ses->detector_crypt,
sub_version,
language,
character_name,
hardware_id);
}
@@ -449,6 +443,7 @@ ProxyServer::LinkedSession::LinkedSession(
license(nullptr),
client_channel(
version,
1,
nullptr,
nullptr,
this,
@@ -457,6 +452,7 @@ ProxyServer::LinkedSession::LinkedSession(
TerminalFormat::FG_GREEN),
server_channel(
version,
1,
nullptr,
nullptr,
this,
@@ -467,9 +463,7 @@ ProxyServer::LinkedSession::LinkedSession(
disconnect_action(DisconnectAction::LONG_TIMEOUT),
remote_ip_crc(0),
enable_remote_ip_crc_patch(false),
version(version),
sub_version(0), // This is set during resume()
language(1), // Default = English. This is also set during resume()
remote_guild_card_number(-1),
next_item_id(0x0F000000),
lobby_players(12),
@@ -537,11 +531,9 @@ void ProxyServer::LinkedSession::resume(
Channel&& client_channel,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version,
uint8_t language,
const string& character_name,
const string& hardware_id) {
this->sub_version = sub_version;
this->language = language;
this->character_name = character_name;
this->hardware_id = hardware_id;
this->resume_inner(std::move(client_channel), detector_crypt);
@@ -557,7 +549,6 @@ void ProxyServer::LinkedSession::resume(
void ProxyServer::LinkedSession::resume(Channel&& client_channel) {
this->sub_version = 0;
this->language = 1;
this->character_name.clear();
this->resume_inner(std::move(client_channel), nullptr);
}
@@ -578,6 +569,7 @@ void ProxyServer::LinkedSession::resume_inner(
ProxyServer::LinkedSession::on_error,
this,
string_printf("LinkedSession:%08" PRIX64 ":client", this->id));
this->server_channel.language = this->client_channel.language;
this->detector_crypt = detector_crypt;
this->server_channel.disconnect();
@@ -648,9 +640,9 @@ void ProxyServer::LinkedSession::on_error(Channel& ch, short events) {
ses->log.info("%s channel connected", is_server_stream ? "Server" : "Client");
if (is_server_stream && (ses->options.override_lobby_event >= 0) &&
(((ses->version == GameVersion::GC) && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) ||
(ses->version == GameVersion::XB) ||
(ses->version == GameVersion::BB))) {
(((ses->version() == GameVersion::GC) && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) ||
(ses->version() == GameVersion::XB) ||
(ses->version() == GameVersion::BB))) {
ses->client_channel.send(0xDA, ses->options.override_lobby_event);
}
}
@@ -687,7 +679,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
}
// On BB, do nothing - we can't return to the game server since the remote
// server likely sent different game data than what newserv would have sent
if (this->version == GameVersion::BB) {
if (this->version() == GameVersion::BB) {
this->disconnect();
return;
}
@@ -704,13 +696,12 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
}
auto s = this->require_server_state();
string encoded_name = encode_sjis(s->name);
if (this->is_in_game) {
send_ship_info(this->client_channel, decode_sjis(string_printf("You cannot return\nto $C6%s$C7\nwhile in a game.\n\n%s", encoded_name.c_str(), error_message ? error_message : "")));
send_ship_info(this->client_channel, string_printf("You cannot return\nto $C6%s$C7\nwhile in a game.\n\n%s", s->name.c_str(), error_message ? error_message : ""));
this->disconnect();
} else {
send_ship_info(this->client_channel, decode_sjis(string_printf("You\'ve returned to\n\tC6%s$C7\n\n%s", encoded_name.c_str(), error_message ? error_message : "")));
send_ship_info(this->client_channel, string_printf("You\'ve returned to\n\tC6%s$C7\n\n%s", s->name.c_str(), error_message ? error_message : ""));
// Restore newserv_client_config, so the login server gets the client flags
S_UpdateClientConfig_DC_PC_V3_04 update_client_config_cmd;
@@ -719,8 +710,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
update_client_config_cmd.cfg = this->newserv_client_config.cfg;
this->client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd));
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(
this->version));
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(this->version()));
S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}};
+8 -3
View File
@@ -57,9 +57,7 @@ public:
uint32_t remote_ip_crc;
bool enable_remote_ip_crc_patch;
GameVersion version;
uint32_t sub_version;
uint8_t language;
std::string character_name;
std::string hardware_id; // Only used for DC sessions
std::string login_command_bb;
@@ -79,6 +77,7 @@ public:
struct LobbyPlayer {
uint32_t guild_card_number = 0;
std::string name;
uint8_t language;
uint8_t section_id = 0;
uint8_t char_class = 0;
};
@@ -138,11 +137,17 @@ public:
std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const;
inline GameVersion version() const {
return this->client_channel.version;
}
inline uint8_t language() const {
return this->client_channel.language;
}
void resume(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version,
uint8_t language,
const std::string& character_name,
const std::string& hardware_id);
void resume(
+26 -26
View File
@@ -26,8 +26,8 @@ QuestCategoryIndex::Category::Category(uint32_t category_id, const JSON& json)
this->flags = json.get_int(0);
this->type = json.get_string(1).at(0);
this->short_token = json.get_string(2);
this->name = decode_sjis(json.get_string(3));
this->description = decode_sjis(json.get_string(4));
this->name = json.get_string(3);
this->description = json.get_string(4);
}
bool QuestCategoryIndex::Category::matches_flags(uint8_t request) const {
@@ -252,9 +252,9 @@ VersionedQuest::VersionedQuest(
if (this->quest_number == 0xFFFFFFFF) {
this->quest_number = header->quest_number;
}
this->name = decode_sjis(header->name);
this->short_description = decode_sjis(header->short_description);
this->long_description = decode_sjis(header->long_description);
this->name = header->name.decode(this->language);
this->short_description = header->short_description.decode(this->language);
this->long_description = header->long_description.decode(this->language);
break;
}
@@ -268,9 +268,9 @@ VersionedQuest::VersionedQuest(
if (this->quest_number == 0xFFFFFFFF) {
this->quest_number = header->quest_number;
}
this->name = header->name;
this->short_description = header->short_description;
this->long_description = header->long_description;
this->name = header->name.decode(this->language);
this->short_description = header->short_description.decode(this->language);
this->long_description = header->long_description.decode(this->language);
break;
}
@@ -291,9 +291,9 @@ VersionedQuest::VersionedQuest(
if (this->quest_number == 0xFFFFFFFF) {
this->quest_number = map->map_number;
}
this->name = decode_sjis(map->name);
this->short_description = decode_sjis(map->quest_name);
this->long_description = decode_sjis(map->description);
this->name = map->name.decode(this->language);
this->short_description = map->quest_name.decode(this->language);
this->long_description = map->description.decode(this->language);
break;
}
@@ -309,9 +309,9 @@ VersionedQuest::VersionedQuest(
if (this->quest_number == 0xFFFFFFFF) {
this->quest_number = header->quest_number;
}
this->name = decode_sjis(header->name);
this->short_description = decode_sjis(header->short_description);
this->long_description = decode_sjis(header->long_description);
this->name = header->name.decode(this->language);
this->short_description = header->short_description.decode(this->language);
this->long_description = header->long_description.decode(this->language);
break;
}
@@ -337,9 +337,9 @@ VersionedQuest::VersionedQuest(
if (this->quest_number == 0xFFFFFFFF) {
this->quest_number = header->quest_number;
}
this->name = header->name;
this->short_description = header->short_description;
this->long_description = header->long_description;
this->name = header->name.decode(this->language);
this->short_description = header->short_description.decode(this->language);
this->long_description = header->long_description.decode(this->language);
break;
}
@@ -675,8 +675,8 @@ QuestIndex::QuestIndex(
shared_ptr<VersionedQuest> vq(new VersionedQuest(
quest_number, category_id, version, language, bin_contents, dat_contents, battle_rules, challenge_template_index));
string ascii_name = format_data_string(encode_sjis(vq->name));
auto category_name = encode_sjis(this->category_index->at(vq->category_id).name);
string ascii_name = format_data_string(vq->name);
auto category_name = this->category_index->at(vq->category_id).name;
string dat_str = dat_filename.empty() ? "" : (" with layout " + dat_filename);
string battle_rules_str = battle_rules ? (" with battle rules from " + json_filename) : "";
@@ -1029,7 +1029,7 @@ static pair<string, string> decode_qst_data_t(const string& data) {
throw runtime_error("qst open file command has incorrect size");
}
const auto& cmd = r.get<OpenFileT>();
string internal_filename = cmd.filename;
string internal_filename = cmd.filename.decode();
if (ends_with(internal_filename, ".bin")) {
if (internal_bin_filename.empty()) {
@@ -1059,7 +1059,7 @@ static pair<string, string> decode_qst_data_t(const string& data) {
throw runtime_error("qst write file command has incorrect size");
}
const auto& cmd = r.get<S_WriteFile_13_A7>();
string filename = cmd.filename;
string filename = cmd.filename.decode();
string* dest = nullptr;
if (filename == internal_bin_filename) {
@@ -1134,11 +1134,11 @@ void add_command_header(
}
template <typename HeaderT, typename CmdT>
void add_open_file_command(StringWriter& w, const std::u16string& name, const std::string& filename, size_t file_size, bool is_download) {
void add_open_file_command(StringWriter& w, const std::string& name, const std::string& filename, size_t file_size, bool is_download) {
add_command_header<HeaderT>(w, is_download ? 0xA6 : 0x44, 0x00, sizeof(CmdT));
CmdT cmd;
cmd.name = "PSO/" + encode_sjis(name);
cmd.filename = filename;
cmd.name.assign_raw("PSO/" + name);
cmd.filename.encode(filename);
cmd.type = 0;
cmd.file_size = file_size;
// TODO: It'd be nice to have something like w.emplace(...) to avoid copying
@@ -1157,7 +1157,7 @@ void add_write_file_commands(
size_t chunk_size = min<size_t>(data.size() - z, 0x400);
add_command_header<HeaderT>(w, is_download ? 0xA7 : 0x13, z >> 10, sizeof(S_WriteFile_13_A7));
S_WriteFile_13_A7 cmd;
cmd.filename = filename;
cmd.filename.encode(filename);
memcpy(cmd.data.data(), &data[z], chunk_size);
cmd.data_size = chunk_size;
w.put(cmd);
@@ -1173,7 +1173,7 @@ void add_write_file_commands(
string encode_qst_file(
const string& bin_data,
const string& dat_data,
const u16string& name,
const string& name,
uint32_t quest_number,
QuestScriptVersion version,
bool is_dlq_encoded) {
+7 -7
View File
@@ -37,8 +37,8 @@ struct QuestCategoryIndex {
uint8_t flags;
char type;
std::string short_token;
std::u16string name;
std::u16string description;
std::string name;
std::string description;
explicit Category(uint32_t category_id, const JSON& json);
@@ -58,12 +58,12 @@ struct VersionedQuest {
uint32_t category_id;
Episode episode;
bool joinable;
std::u16string name;
std::string name;
QuestScriptVersion version;
uint8_t language;
bool is_dlq_encoded;
std::u16string short_description;
std::u16string long_description;
std::string short_description;
std::string long_description;
std::shared_ptr<const std::string> bin_contents;
std::shared_ptr<const std::string> dat_contents;
std::shared_ptr<const BattleRules> battle_rules;
@@ -105,7 +105,7 @@ public:
uint32_t category_id;
Episode episode;
bool joinable;
std::u16string name;
std::string name;
std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index;
std::map<uint16_t, std::shared_ptr<const VersionedQuest>> versions;
@@ -147,7 +147,7 @@ std::pair<std::string, std::string> decode_qst_data(const std::string& data);
std::string encode_qst_file(
const std::string& bin_data,
const std::string& dat_data,
const std::u16string& name,
const std::string& name,
uint32_t quest_number,
QuestScriptVersion version,
bool is_dlq_encoded);
+27 -41
View File
@@ -78,19 +78,6 @@ static string format_and_indent_data(const void* data, size_t size, uint64_t sta
return ret;
}
static string dasm_u16string(const char16_t* data, size_t size) {
try {
return format_data_string(encode_sjis(data, size));
} catch (const runtime_error& e) {
return "/* undecodable */ " + format_data_string(data, size * sizeof(char16_t));
}
}
template <size_t Size>
static string dasm_u16string(const parray<char16_t, Size>& data) {
return dasm_u16string(data.data(), data.size());
}
struct ResistData {
le_int16_t evp_bonus;
le_uint16_t unknown_a1;
@@ -867,7 +854,7 @@ opcodes_for_version(QuestScriptVersion v) {
return index;
}
std::string disassemble_quest_script(const void* data, size_t size, QuestScriptVersion version) {
std::string disassemble_quest_script(const void* data, size_t size, QuestScriptVersion version, uint8_t language) {
StringReader r(data, size);
deque<string> lines;
@@ -885,9 +872,9 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
if (header.is_download) {
lines.emplace_back(string_printf(".is_download_quest"));
}
lines.emplace_back(".name " + format_data_string(header.name.data(), header.name.len()));
lines.emplace_back(".short_desc " + format_data_string(header.short_description.data(), header.short_description.len()));
lines.emplace_back(".long_desc " + format_data_string(header.long_description.data(), header.long_description.len()));
lines.emplace_back(".name " + JSON(header.name.decode(language)).serialize());
lines.emplace_back(".short_desc " + JSON(header.short_description.decode(language)).serialize());
lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize());
break;
}
case QuestScriptVersion::PC_V2: {
@@ -899,9 +886,9 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
if (header.is_download) {
lines.emplace_back(string_printf(".is_download_quest"));
}
lines.emplace_back(".name " + dasm_u16string(header.name));
lines.emplace_back(".short_desc " + dasm_u16string(header.short_description));
lines.emplace_back(".long_desc " + dasm_u16string(header.long_description));
lines.emplace_back(".name " + JSON(header.name.decode(language)).serialize());
lines.emplace_back(".short_desc " + JSON(header.short_description.decode(language)).serialize());
lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize());
break;
}
case QuestScriptVersion::GC_NTE:
@@ -916,9 +903,9 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
lines.emplace_back(string_printf(".is_download_quest"));
}
lines.emplace_back(string_printf(".episode %hhu", header.episode));
lines.emplace_back(".name " + format_data_string(header.name.data(), header.name.len()));
lines.emplace_back(".short_desc " + format_data_string(header.short_description.data(), header.short_description.len()));
lines.emplace_back(".long_desc " + format_data_string(header.long_description.data(), header.long_description.len()));
lines.emplace_back(".name " + JSON(header.name.decode(language)).serialize());
lines.emplace_back(".short_desc " + JSON(header.short_description.decode(language)).serialize());
lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize());
break;
}
case QuestScriptVersion::BB_V4: {
@@ -932,9 +919,9 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
if (header.joinable_in_progress) {
lines.emplace_back(".joinable_in_progress");
}
lines.emplace_back(".name " + dasm_u16string(header.name));
lines.emplace_back(".short_desc " + dasm_u16string(header.short_description));
lines.emplace_back(".long_desc " + dasm_u16string(header.long_description));
lines.emplace_back(".name " + JSON(header.name.decode(language)).serialize());
lines.emplace_back(".short_desc " + JSON(header.short_description.decode(language)).serialize());
lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize());
break;
}
default:
@@ -1168,20 +1155,20 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
}
case Type::CSTRING:
if (use_wstrs) {
u16string s;
for (char16_t ch = cmd_r.get_u16l(); ch; ch = cmd_r.get_u16l()) {
s.push_back(ch);
StringWriter w;
for (uint16_t ch = cmd_r.get_u16l(); ch; ch = cmd_r.get_u16l()) {
w.put_u16l(ch);
}
if (def->flags & F_PASS) {
arg_stack_values.emplace_back(encode_sjis(s));
arg_stack_values.emplace_back(tt_utf16_to_utf8(w.str()));
}
dasm_arg = dasm_u16string(s.data(), s.size());
dasm_arg = JSON(w.str()).serialize();
} else {
string s = cmd_r.get_cstr();
if (def->flags & F_PASS) {
arg_stack_values.emplace_back(s);
}
dasm_arg = format_data_string(s);
dasm_arg = JSON(s).serialize();
}
break;
default:
@@ -1383,21 +1370,20 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
}
if (l->has_data_type(Arg::DataType::CSTRING)) {
lines.emplace_back(string_printf(" // As C string (0x%zX bytes)", size));
string data;
string str_data = cmd_r.pread(l->offset, size);
strip_trailing_zeroes(str_data);
if (use_wstrs) {
u16string wdata(reinterpret_cast<const char16_t*>(cmd_r.pgetv(l->offset, size)), size >> 1);
strip_trailing_zeroes(wdata);
data = encode_sjis(wdata);
} else {
data = cmd_r.pread(l->offset, size);
strip_trailing_zeroes(data);
if (str_data.size() & 1) {
str_data.push_back(0);
}
str_data = tt_utf16_to_utf8(str_data);
}
string formatted = format_data_string(data);
string formatted = format_data_string(str_data);
lines.emplace_back(string_printf(" %04" PRIX32 " %s", l->offset, formatted.c_str()));
}
print_as_struct.template operator()<Arg::DataType::PLAYER_VISUAL_CONFIG, PlayerVisualConfig>([&](const PlayerVisualConfig& visual) -> void {
lines.emplace_back(" // As PlayerVisualConfig");
string name = format_data_string(visual.name);
string name = format_data_string(visual.name.decode(language));
lines.emplace_back(string_printf(" %04zX name %s", l->offset + offsetof(PlayerVisualConfig, name), name.c_str()));
lines.emplace_back(string_printf(" %04zX name_color %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, name_color), visual.name_color.load()));
string a2_str = format_data_string(visual.unknown_a2.data(), sizeof(visual.unknown_a2));
+14 -13
View File
@@ -32,9 +32,9 @@ struct PSOQuestHeaderDC { // Same format for DC v1 and v2
/* 0010 */ uint8_t is_download;
/* 0011 */ uint8_t unknown1;
/* 0012 */ le_uint16_t quest_number; // 0xFFFF for challenge quests
/* 0014 */ ptext<char, 0x20> name;
/* 0034 */ ptext<char, 0x80> short_description;
/* 00B4 */ ptext<char, 0x120> long_description;
/* 0014 */ pstring<TextEncoding::MARKED, 0x20> name;
/* 0034 */ pstring<TextEncoding::MARKED, 0x80> short_description;
/* 00B4 */ pstring<TextEncoding::MARKED, 0x120> long_description;
/* 01D4 */
} __attribute__((packed));
@@ -46,9 +46,9 @@ struct PSOQuestHeaderPC {
/* 0010 */ uint8_t is_download;
/* 0011 */ uint8_t unknown1;
/* 0012 */ le_uint16_t quest_number; // 0xFFFF for challenge quests
/* 0014 */ ptext<char16_t, 0x20> name;
/* 0054 */ ptext<char16_t, 0x80> short_description;
/* 0154 */ ptext<char16_t, 0x120> long_description;
/* 0014 */ pstring<TextEncoding::UTF16, 0x20> name;
/* 0054 */ pstring<TextEncoding::UTF16, 0x80> short_description;
/* 0154 */ pstring<TextEncoding::UTF16, 0x120> long_description;
/* 0394 */
} __attribute__((packed));
@@ -63,9 +63,9 @@ struct PSOQuestHeaderGC {
/* 0011 */ uint8_t unknown1;
/* 0012 */ uint8_t quest_number;
/* 0013 */ uint8_t episode; // 1 = Ep2. Apparently some quests have 0xFF here, which means ep1 (?)
/* 0014 */ ptext<char, 0x20> name;
/* 0034 */ ptext<char, 0x80> short_description;
/* 00B4 */ ptext<char, 0x120> long_description;
/* 0014 */ pstring<TextEncoding::MARKED, 0x20> name;
/* 0034 */ pstring<TextEncoding::MARKED, 0x80> short_description;
/* 00B4 */ pstring<TextEncoding::MARKED, 0x120> long_description;
/* 01D4 */
} __attribute__((packed));
@@ -80,10 +80,11 @@ struct PSOQuestHeaderBB {
/* 0015 */ uint8_t max_players;
/* 0016 */ uint8_t joinable_in_progress;
/* 0017 */ uint8_t unknown;
/* 0018 */ ptext<char16_t, 0x20> name;
/* 0058 */ ptext<char16_t, 0x80> short_description;
/* 0158 */ ptext<char16_t, 0x120> long_description;
/* 0018 */ pstring<TextEncoding::UTF16, 0x20> name;
/* 0058 */ pstring<TextEncoding::UTF16, 0x80> short_description;
/* 0158 */ pstring<TextEncoding::UTF16, 0x120> long_description;
/* 0398 */
} __attribute__((packed));
std::string disassemble_quest_script(const void* data, size_t size, QuestScriptVersion version);
std::string disassemble_quest_script(
const void* data, size_t size, QuestScriptVersion version, uint8_t language);
+369 -392
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -7,8 +7,8 @@
std::shared_ptr<Lobby> create_game_generic(
std::shared_ptr<ServerState> s,
std::shared_ptr<Client> c,
const std::u16string& name,
const std::u16string& password = u"",
const std::string& name,
const std::string& password = "",
Episode episode = Episode::EP1,
GameMode mode = GameMode::NORMAL,
uint8_t difficulty = 0,
@@ -19,5 +19,5 @@ std::shared_ptr<Lobby> create_game_generic(
void on_connect(std::shared_ptr<Client> c);
void on_disconnect(std::shared_ptr<Client> c);
void on_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, const std::string& data);
void on_command_with_header(std::shared_ptr<Client> c, std::string& data);
void on_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
void on_command_with_header(std::shared_ptr<Client> c, const std::string& data);
+18 -14
View File
@@ -144,7 +144,7 @@ static void on_forward_sync_joining_player_state(shared_ptr<Client> c, uint8_t c
}
if (c->options.debug) {
string decompressed = bc0_decompress(cmd.data, cmd.compressed_size);
string decompressed = bc0_decompress(reinterpret_cast<const char*>(data) + sizeof(cmd), cmd.compressed_size);
c->log.info("Decompressed sync data (%" PRIX32 " -> %zX bytes; expected %" PRIX32 "):",
cmd.compressed_size.load(), decompressed.size(), cmd.decompressed_size.load());
print_data(stderr, decompressed);
@@ -191,7 +191,7 @@ static void on_sync_joining_player_item_state(shared_ptr<Client> c, uint8_t comm
throw runtime_error("compressed end offset is beyond end of command");
}
string decompressed = bc0_decompress(cmd.data, cmd.compressed_size);
string decompressed = bc0_decompress(reinterpret_cast<const char*>(data) + sizeof(cmd), cmd.compressed_size);
if (c->options.debug) {
c->log.info("Decompressed item sync data (%" PRIX32 " -> %zX bytes; expected %" PRIX32 "):",
cmd.compressed_size.load(), decompressed.size(), cmd.decompressed_size.load());
@@ -216,12 +216,14 @@ static void on_sync_joining_player_item_state(shared_ptr<Client> c, uint8_t comm
"decompressed 6x6D data (0x%zX bytes) is too short for all items (0x%zX bytes)",
decompressed.size(), required_size));
}
auto* floor_items = reinterpret_cast<G_SyncItemState_6x6D_Decompressed::FloorItem*>(
decompressed.data() + sizeof(G_SyncItemState_6x6D_Decompressed));
for (size_t z = 0; z < num_floor_items; z++) {
// NOTE: If we use this codepath for non-V3 in the future, we'll need to
// change this hardcoded version. This only works because GC's mag
// encoding/decoding is symmetric (encode and decode do the same thing).
decompressed_cmd->items[z].item_data.decode_if_mag(GameVersion::GC);
floor_items[z].item_data.decode_if_mag(GameVersion::GC);
}
string out_compressed_data = bc0_compress(decompressed);
@@ -408,18 +410,18 @@ static void on_send_guild_card(shared_ptr<Client> c, uint8_t command, uint8_t fl
switch (c->version()) {
case GameVersion::DC: {
const auto& cmd = check_size_t<G_SendGuildCard_DC_6x06>(data, size);
c->game_data.player(true, false)->guild_card_description = cmd.description;
c->game_data.player(true, false)->guild_card_description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case GameVersion::PC: {
const auto& cmd = check_size_t<G_SendGuildCard_PC_6x06>(data, size);
c->game_data.player(true, false)->guild_card_description = cmd.description;
c->game_data.player(true, false)->guild_card_description = cmd.guild_card.description;
break;
}
case GameVersion::GC:
case GameVersion::XB: {
const auto& cmd = check_size_t<G_SendGuildCard_V3_6x06>(data, size);
c->game_data.player(true, false)->guild_card_description = cmd.description;
c->game_data.player(true, false)->guild_card_description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case GameVersion::BB:
@@ -496,7 +498,7 @@ static void on_word_select_t(shared_ptr<Client> c, uint8_t command, uint8_t, con
}
} catch (const exception& e) {
string name = encode_sjis(c->game_data.player()->disp.name);
string name = c->game_data.player()->disp.name.decode(c->language());
lc->log.warning("Untranslatable Word Select message: %s", e.what());
send_text_message_printf(lc, "$C4Untranslatable Word\nSelect message from\n%s", name.c_str());
}
@@ -523,8 +525,8 @@ static void on_set_player_visibility(shared_ptr<Client> c, uint8_t command, uint
send_arrow_update(l);
}
if (!l->is_game() && (l->flags & Lobby::Flag::IS_OVERFLOW)) {
send_message_box(c, u"$C6All lobbies are full.\n\n$C7You are in a private lobby. You can use the\nteleporter to join other lobbies if there is space\navailable.");
send_lobby_message_box(c, u"");
send_message_box(c, "$C6All lobbies are full.\n\n$C7You are in a private lobby. You can use the\nteleporter to join other lobbies if there is space\navailable.");
send_lobby_message_box(c, "");
}
}
}
@@ -632,7 +634,7 @@ static void on_switch_state_changed(shared_ptr<Client> c, uint8_t command, uint8
(c->last_switch_enabled_command.header.subcommand == 0x05)) {
c->log.info("[Switch assist] Replaying previous enable command");
if (c->options.debug) {
send_text_message(c, u"$C5Switch assist");
send_text_message(c, "$C5Switch assist");
}
forward_subcommand(c, command, flag, &c->last_switch_enabled_command, sizeof(c->last_switch_enabled_command));
send_command_t(c, command, flag, c->last_switch_enabled_command);
@@ -1278,12 +1280,14 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
return;
}
if (!(l->flags & Lobby::Flag::DROPS_ENABLED)) {
return;
}
// If there is no item creator (that is, the game is BB or has server rare
// tables disabled), then forward the request to the leader
if (!l->item_creator) {
if (l->flags & Lobby::Flag::DROPS_ENABLED) {
forward_subcommand(c, command, flag, data, size);
}
forward_subcommand(c, command, flag, data, size);
return;
}
@@ -1531,7 +1535,7 @@ static void on_enemy_killed_bb(shared_ptr<Client> c, uint8_t command, uint8_t fl
throw runtime_error("game does not have a map loaded");
}
if (cmd.enemy_id >= l->map->enemies.size()) {
send_text_message(c, u"$C6Missing enemy killed");
send_text_message(c, "$C6Missing enemy killed");
return;
}
+40 -43
View File
@@ -45,6 +45,7 @@ ReplaySession::Client::Client(
version(version),
channel(
this->version,
1,
&ReplaySession::dispatch_on_command_received,
&ReplaySession::dispatch_on_error,
session,
@@ -113,31 +114,31 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
case GameVersion::PATCH: {
const auto& header = check_size_t<PSOCommandHeaderPC>(ev->data, 0xFFFF);
if (header.command == 0x04) {
check_either(check_size_t<C_Login_Patch_04>(cmd_data, cmd_size).password);
check_either(check_size_t<C_Login_Patch_04>(cmd_data, cmd_size).password.decode());
}
break;
}
case GameVersion::PC: {
const auto& header = check_size_t<PSOCommandHeaderPC>(ev->data, 0xFFFF);
if (header.command == 0x03) {
check_ak(check_size_t<C_LegacyLogin_PC_V3_03>(cmd_data, cmd_size).access_key2);
check_ak(check_size_t<C_LegacyLogin_PC_V3_03>(cmd_data, cmd_size).access_key2.decode());
} else if (header.command == 0x04) {
check_ak(check_size_t<C_LegacyLogin_PC_V3_04>(cmd_data, cmd_size).access_key);
check_ak(check_size_t<C_LegacyLogin_PC_V3_04>(cmd_data, cmd_size).access_key.decode());
} else if (header.command == 0x9A) {
const auto& cmd = check_size_t<C_Login_DC_PC_V3_9A>(cmd_data, cmd_size);
check_ak(cmd.v1_access_key);
check_ak(cmd.access_key);
check_ak(cmd.access_key2);
check_ak(cmd.v1_access_key.decode());
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
} else if (header.command == 0x9C) {
const auto& cmd = check_size_t<C_Register_DC_PC_V3_9C>(cmd_data, cmd_size);
check_ak(cmd.access_key);
check_pw(cmd.password);
check_ak(cmd.access_key.decode());
check_pw(cmd.password.decode());
} else if (header.command == 0x9D) {
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(
cmd_data, cmd_size, sizeof(C_LoginExtended_PC_9D));
check_ak(cmd.v1_access_key);
check_ak(cmd.access_key);
check_ak(cmd.access_key2);
check_ak(cmd.v1_access_key.decode());
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
}
break;
}
@@ -146,62 +147,58 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
case GameVersion::XB: {
const auto& header = check_size_t<PSOCommandHeaderDCV3>(ev->data, 0xFFFF);
if (header.command == 0x03) {
check_ak(check_size_t<C_LegacyLogin_PC_V3_03>(cmd_data, cmd_size).access_key2);
check_ak(check_size_t<C_LegacyLogin_PC_V3_03>(cmd_data, cmd_size).access_key2.decode());
} else if (header.command == 0x04) {
check_ak(check_size_t<C_LegacyLogin_PC_V3_04>(cmd_data, cmd_size).access_key);
check_ak(check_size_t<C_LegacyLogin_PC_V3_04>(cmd_data, cmd_size).access_key.decode());
} else if (header.command == 0x90) {
check_ak(check_size_t<C_LoginV1_DC_PC_V3_90>(cmd_data, cmd_size, 0xFFFF).access_key);
check_ak(check_size_t<C_LoginV1_DC_PC_V3_90>(cmd_data, cmd_size, 0xFFFF).access_key.decode());
} else if (header.command == 0x93) {
const auto& cmd = check_size_t<C_LoginV1_DC_93>(
cmd_data, cmd_size, sizeof(C_LoginExtendedV1_DC_93));
check_ak(cmd.access_key);
const auto& cmd = check_size_t<C_LoginV1_DC_93>(cmd_data, cmd_size, sizeof(C_LoginExtendedV1_DC_93));
check_ak(cmd.access_key.decode());
} else if (header.command == 0x9A) {
const auto& cmd = check_size_t<C_Login_DC_PC_V3_9A>(cmd_data, cmd_size);
check_ak(cmd.v1_access_key);
check_ak(cmd.access_key);
check_ak(cmd.access_key2);
check_ak(cmd.v1_access_key.decode());
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
} else if (header.command == 0x9C) {
const auto& cmd = check_size_t<C_Register_DC_PC_V3_9C>(cmd_data, cmd_size);
check_ak(cmd.access_key);
check_pw(cmd.password);
check_ak(cmd.access_key.decode());
check_pw(cmd.password.decode());
} else if (header.command == 0x9D) {
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(
cmd_data, cmd_size, sizeof(C_LoginExtended_DC_GC_9D));
check_ak(cmd.v1_access_key);
check_ak(cmd.access_key);
check_ak(cmd.access_key2);
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(cmd_data, cmd_size, sizeof(C_LoginExtended_DC_GC_9D));
check_ak(cmd.v1_access_key.decode());
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
} else if (header.command == 0x9E) {
if (version == GameVersion::GC) {
const auto& cmd = check_size_t<C_Login_GC_9E>(
cmd_data, cmd_size, sizeof(C_LoginExtended_GC_9E));
check_ak(cmd.access_key);
check_ak(cmd.access_key2);
const auto& cmd = check_size_t<C_Login_GC_9E>(cmd_data, cmd_size, sizeof(C_LoginExtended_GC_9E));
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
} else { // XB
const auto& cmd = check_size_t<C_Login_XB_9E>(
cmd_data, cmd_size, sizeof(C_LoginExtended_XB_9E));
check_ak(cmd.access_key);
check_ak(cmd.access_key2);
const auto& cmd = check_size_t<C_Login_XB_9E>(cmd_data, cmd_size, sizeof(C_LoginExtended_XB_9E));
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
}
} else if (header.command == 0xDB) {
const auto& cmd = check_size_t<C_VerifyLicense_V3_DB>(cmd_data, cmd_size);
check_ak(cmd.access_key);
check_ak(cmd.access_key2);
check_pw(cmd.password);
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
check_pw(cmd.password.decode());
}
break;
}
case GameVersion::BB: {
const auto& header = check_size_t<PSOCommandHeaderBB>(ev->data, 0xFFFF);
if (header.command == 0x04) {
check_pw(check_size_t<C_LegacyLogin_BB_04>(cmd_data, cmd_size).password);
check_pw(check_size_t<C_LegacyLogin_BB_04>(cmd_data, cmd_size).password.decode());
} else if (header.command == 0x93) {
check_pw(check_size_t<C_Login_BB_93>(cmd_data, cmd_size).password);
check_pw(check_size_t<C_Login_BB_93>(cmd_data, cmd_size).password.decode());
} else if (header.command == 0x9C) {
check_pw(check_size_t<C_Register_BB_9C>(cmd_data, cmd_size).password);
check_pw(check_size_t<C_Register_BB_9C>(cmd_data, cmd_size).password.decode());
} else if (header.command == 0x9E) {
check_pw(check_size_t<C_LoginExtended_BB_9E>(cmd_data, cmd_size).password);
check_pw(check_size_t<C_LoginExtended_BB_9E>(cmd_data, cmd_size).password.decode());
} else if (header.command == 0xDB) {
check_pw(check_size_t<C_VerifyLicense_BB_DB>(cmd_data, cmd_size).password);
check_pw(check_size_t<C_VerifyLicense_BB_DB>(cmd_data, cmd_size).password.decode());
}
break;
}
+24 -24
View File
@@ -26,9 +26,9 @@ struct ShuffleTables {
};
struct PSOVMSFileHeader {
/* 0000 */ ptext<char, 0x10> short_desc;
/* 0010 */ ptext<char, 0x20> long_desc;
/* 0030 */ ptext<char, 0x10> creator_id;
/* 0000 */ pstring<TextEncoding::SJIS, 0x10> short_desc;
/* 0010 */ pstring<TextEncoding::SJIS, 0x20> long_desc;
/* 0030 */ pstring<TextEncoding::SJIS, 0x10> creator_id;
/* 0040 */ le_uint16_t num_icons;
/* 0042 */ le_uint16_t animation_speed;
/* 0044 */ le_uint16_t eyecatch_type;
@@ -54,7 +54,7 @@ struct PSOGCIFileHeader {
// There is a structure for this part of the header, but we don't use it.
/* 0006 */ uint8_t unused;
/* 0007 */ uint8_t image_flags;
/* 0008 */ ptext<char, 0x20> internal_file_name;
/* 0008 */ pstring<TextEncoding::SJIS, 0x20> internal_file_name;
/* 0028 */ be_uint32_t modification_time;
/* 002C */ be_uint32_t image_data_offset;
/* 0030 */ be_uint16_t icon_formats;
@@ -67,9 +67,9 @@ struct PSOGCIFileHeader {
/* 003C */ be_uint32_t comment_offset;
// GCI header ends here (and memcard file data begins here)
// game_name is e.g. "PSO EPISODE I & II" or "PSO EPISODE III"
/* 0040 */ ptext<char, 0x1C> game_name;
/* 0040 */ pstring<TextEncoding::SJIS, 0x1C> game_name;
/* 005C */ be_uint32_t embedded_seed; // Used in some of Ralf's quest packs
/* 0060 */ ptext<char, 0x20> file_name;
/* 0060 */ pstring<TextEncoding::SJIS, 0x20> file_name;
/* 0080 */ parray<uint8_t, 0x1800> banner;
/* 1880 */ parray<uint8_t, 0x800> icon;
// data_size specifies the number of bytes remaining in the file. In all cases
@@ -119,7 +119,7 @@ struct PSOGCEp3SystemFile {
struct PSOGCSaveFileSymbolChatEntry {
/* 00 */ be_uint32_t present;
/* 04 */ ptext<char, 0x18> name;
/* 04 */ pstring<TextEncoding::SJIS, 0x18> name;
/* 1C */ be_uint16_t unused;
/* 1E */ uint8_t flags;
/* 1F */ uint8_t face_spec;
@@ -140,7 +140,7 @@ struct PSOGCSaveFileSymbolChatEntry {
struct PSOPCSaveFileSymbolChatEntry {
/* 00 */ le_uint32_t present;
/* 04 */ ptext<char16_t, 0x18> name;
/* 04 */ pstring<TextEncoding::UTF16, 0x18> name;
/* 34 */ uint8_t face_spec;
/* 35 */ uint8_t flags;
/* 36 */ be_uint16_t unused;
@@ -212,8 +212,8 @@ struct PSOGCCharacterFile {
/* 192C:1510 */ GuildCardV3 guild_card;
/* 19BC:15A0 */ parray<PSOGCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 1DDC:19C0 */ parray<PSOGCSaveFileChatShortcutEntry, 20> chat_shortcuts;
/* 246C:2050 */ ptext<char, 0xAC> auto_reply;
/* 2518:20FC */ ptext<char, 0xAC> info_board;
/* 246C:2050 */ pstring<TextEncoding::SJIS, 0xAC> auto_reply;
/* 2518:20FC */ pstring<TextEncoding::SJIS, 0xAC> info_board;
/* 25C4:21A8 */ PlayerRecords_Battle<true> battle_records;
/* 25DC:21C0 */ parray<uint8_t, 4> unknown_a2;
/* 25E0:21C4 */ PlayerRecordsV3_Challenge<true> challenge_records;
@@ -228,9 +228,9 @@ struct PSOGCCharacterFile {
/* 2798:237C */
} __attribute__((packed));
/* 00004 */ parray<Character, 7> characters;
/* 1152C */ ptext<char, 0x10> serial_number; // As %08X (not decimal)
/* 1153C */ ptext<char, 0x10> access_key;
/* 1154C */ ptext<char, 0x10> password;
/* 1152C */ pstring<TextEncoding::ASCII, 0x10> serial_number; // As %08X (not decimal)
/* 1153C */ pstring<TextEncoding::ASCII, 0x10> access_key;
/* 1154C */ pstring<TextEncoding::ASCII, 0x10> password;
/* 1155C */ be_uint64_t bgm_test_songs_unlocked;
/* 11564 */ be_uint32_t save_count;
/* 11568 */ be_uint32_t round2_seed;
@@ -269,8 +269,8 @@ struct PSOGCEp3CharacterFile {
/* 08CC:04B0 */ GuildCardV3 guild_card;
/* 095C:0540 */ parray<PSOGCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 0D7C:0960 */ parray<PSOGCSaveFileChatShortcutEntry, 20> chat_shortcuts;
/* 140C:0FF0 */ ptext<char, 0xAC> auto_reply;
/* 14B8:109C */ ptext<char, 0xAC> info_board;
/* 140C:0FF0 */ pstring<TextEncoding::SJIS, 0xAC> auto_reply;
/* 14B8:109C */ pstring<TextEncoding::SJIS, 0xAC> info_board;
// In this struct, place_counts[0] is win_count and [1] is loss_count
/* 1564:1148 */ PlayerRecords_Battle<true> battle_records;
/* 157C:1160 */ parray<uint8_t, 4> unknown_a10;
@@ -282,9 +282,9 @@ struct PSOGCEp3CharacterFile {
/* 39B4:3598 */
} __attribute__((packed));
/* 00004 */ parray<Character, 7> characters;
/* 193F0 */ ptext<char, 0x10> serial_number; // As %08X (not decimal)
/* 19400 */ ptext<char, 0x10> access_key; // As 12 ASCII characters (decimal)
/* 19410 */ ptext<char, 0x10> password;
/* 193F0 */ pstring<TextEncoding::ASCII, 0x10> serial_number; // As %08X (not decimal)
/* 19400 */ pstring<TextEncoding::ASCII, 0x10> access_key; // As 12 ASCII characters (decimal)
/* 19410 */ pstring<TextEncoding::ASCII, 0x10> password;
// In Episode 3, this field still exists, but is unused since BGM test was
// removed from the options menu in favor of the jukebox. The jukebox is
// accessible online only, and which songs are available there is controlled
@@ -313,8 +313,8 @@ struct PSOGCGuildCardFile {
// except for 32-bit fields, which are big-endian here.
/* 0000 */ be_uint32_t player_tag; // == 0x00000001 (not 0x00010000)
/* 0004 */ be_uint32_t guild_card_number;
/* 0008 */ ptext<char, 0x18> name;
/* 0020 */ ptext<char, 0x6C> description;
/* 0008 */ pstring<TextEncoding::SJIS, 0x18> name;
/* 0020 */ pstring<TextEncoding::SJIS, 0x6C> description;
/* 008C */ uint8_t present;
/* 008D */ uint8_t language;
/* 008E */ uint8_t section_id;
@@ -327,7 +327,7 @@ struct PSOGCGuildCardFile {
/* 0091 */ uint8_t unknown_a2;
/* 0092 */ uint8_t unknown_a3;
/* 0093 */ uint8_t unknown_a4;
/* 0094 */ ptext<char, 0x6C> comment;
/* 0094 */ pstring<TextEncoding::SJIS, 0x6C> comment;
/* 0100 */
} __attribute__((packed));
/* 00C4 */ parray<GuildCardEntry, 0xD2> entries;
@@ -349,7 +349,7 @@ struct PSOGCSnapshotFile {
/* 1800A */ be_int16_t max_players;
/* 1800C */ parray<be_uint32_t, 12> players_present;
/* 1803C */ parray<be_uint32_t, 12> player_levels;
/* 1806C */ parray<ptext<char, 0x18>, 12> player_names;
/* 1806C */ parray<pstring<TextEncoding::SJIS, 0x18>, 12> player_names;
/* 1818C */
bool checksum_correct() const;
@@ -592,8 +592,8 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
/* 123C */ parray<uint8_t, 0xAA0> unknown_a3;
/* 1CDC */ parray<le_uint16_t, 20> tech_menu_shortcut_entries;
/* 1D04 */ parray<uint8_t, 0x2C> unknown_a4;
/* 1D30 */ ptext<char, 0x10> serial_number; // As %08X (not decimal)
/* 1D40 */ ptext<char, 0x10> access_key; // As decimal
/* 1D30 */ pstring<TextEncoding::ASCII, 0x10> serial_number; // As %08X (not decimal)
/* 1D40 */ pstring<TextEncoding::ASCII, 0x10> access_key; // As decimal
/* 1D50 */ le_uint32_t round2_seed;
/* 1D54 */
} __attribute__((packed));
+269 -282
View File
File diff suppressed because it is too large Load Diff
+29 -28
View File
@@ -174,47 +174,48 @@ void send_complete_player_bb(std::shared_ptr<Client> c);
void send_enter_directory_patch(std::shared_ptr<Client> c, const std::string& dir);
void send_patch_file(std::shared_ptr<Client> c, std::shared_ptr<PatchFileIndex::File> f);
void send_message_box(std::shared_ptr<Client> c, const std::u16string& text);
void send_message_box(std::shared_ptr<Client> c, const std::string& text);
void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const std::string& text);
void send_lobby_name(std::shared_ptr<Client> c, const std::u16string& text);
void send_quest_info(std::shared_ptr<Client> c, const std::u16string& text,
void send_lobby_name(std::shared_ptr<Client> c, const std::string& text);
void send_quest_info(std::shared_ptr<Client> c, const std::string& text,
bool is_download_quest);
void send_lobby_message_box(std::shared_ptr<Client> c, const std::u16string& text);
void send_ship_info(std::shared_ptr<Client> c, const std::u16string& text);
void send_ship_info(Channel& ch, const std::u16string& text);
void send_text_message(Channel& ch, const std::u16string& text);
void send_text_message(std::shared_ptr<Client> c, const std::u16string& text);
void send_text_message(std::shared_ptr<Lobby> l, const std::u16string& text);
void send_text_message(std::shared_ptr<ServerState> s, const std::u16string& text);
void send_lobby_message_box(std::shared_ptr<Client> c, const std::string& text);
void send_ship_info(std::shared_ptr<Client> c, const std::string& text);
void send_ship_info(Channel& ch, const std::string& text);
void send_text_message(Channel& ch, const std::string& text);
void send_text_message(std::shared_ptr<Client> c, const std::string& text);
void send_text_message(std::shared_ptr<Lobby> l, const std::string& text);
void send_text_message(std::shared_ptr<ServerState> s, const std::string& text);
std::u16string prepare_chat_message(
std::string prepare_chat_data(
GameVersion version,
const std::u16string& from_name,
const std::u16string& text,
uint8_t language,
const std::string& from_name,
const std::string& text,
char private_flags);
void send_chat_message(
void send_chat_message_from_client(
Channel& ch,
const std::u16string& text,
const std::string& text,
char private_flags);
void send_chat_message(
void send_prepared_chat_message(
std::shared_ptr<Client> c,
uint32_t from_guild_card_number,
const std::u16string& prepared_data);
void send_chat_message(
const std::string& prepared_data);
void send_prepared_chat_message(
std::shared_ptr<Lobby> l,
uint32_t from_guild_card_number,
const std::u16string& prepared_data);
const std::string& prepared_data);
void send_chat_message(
std::shared_ptr<Client> c,
uint32_t from_guild_card_number,
const std::u16string& from_name,
const std::u16string& text,
const std::string& from_name,
const std::string& text,
char private_flags);
void send_simple_mail(
std::shared_ptr<Client> c,
uint32_t from_serial_number,
const std::u16string& from_name,
const std::u16string& text);
const std::string& from_name,
const std::string& text);
template <typename TargetT>
__attribute__((format(printf, 2, 3))) void send_text_message_printf(
@@ -223,8 +224,7 @@ __attribute__((format(printf, 2, 3))) void send_text_message_printf(
va_start(va, format);
std::string buf = string_vprintf(format, va);
va_end(va);
std::u16string decoded = decode_sjis(buf);
return send_text_message(t, decoded.c_str());
return send_text_message(t, buf.c_str());
}
__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(
@@ -240,9 +240,10 @@ void send_card_search_result(
void send_guild_card(
Channel& ch,
uint32_t guild_card_number,
const std::u16string& name,
const std::u16string& team_name,
const std::u16string& description,
const std::string& name,
const std::string& team_name,
const std::string& description,
uint8_t language,
uint8_t section_id,
uint8_t char_class);
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
+1 -2
View File
@@ -295,7 +295,6 @@ vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident
serial_number_hex = stoul(ident, nullptr, 16);
} catch (const invalid_argument&) {
}
u16string u16name = decode_sjis(ident);
// TODO: It's kind of not great that we do a linear search here, but this is
// only used in the shell, so it should be pretty rare.
@@ -316,7 +315,7 @@ vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident
}
auto p = c->game_data.player(false, false);
if (p && p->disp.name == u16name) {
if (p && p->disp.name.eq(ident, p->inventory.language)) {
results.emplace_back(std::move(c));
continue;
}
+11 -17
View File
@@ -467,8 +467,7 @@ Proxy session commands:\n\
this->state->ep3_menu_song = stoul(command_args, nullptr, 0);
} else if (command_name == "announce") {
u16string message16 = decode_sjis(command_args);
send_text_message(this->state, message16.c_str());
send_text_message(this->state, command_args);
} else if (command_name == "create-tournament") {
string name = get_quoted_string(command_args);
@@ -664,8 +663,9 @@ Proxy session commands:\n\
const auto& player = session->lobby_players[z];
if (player.guild_card_number) {
auto secid_name = name_for_section_id(player.section_id);
fprintf(stderr, " %zu: %" PRIu32 " => %s (%s, %s)\n",
fprintf(stderr, " %zu: %" PRIu32 " => %s (%c, %s, %s)\n",
z, player.guild_card_number, player.name.c_str(),
char_for_language_code(player.language),
name_for_char_class(player.char_class), secid_name.c_str());
} else {
fprintf(stderr, " %zu: (no player)\n", z);
@@ -676,14 +676,8 @@ Proxy session commands:\n\
auto session = this->get_proxy_session(session_name);
bool is_dchat = (command_name == "dchat");
if (!is_dchat && (session->version == GameVersion::PC || session->version == GameVersion::BB)) {
u16string data(4, u'\0');
data.push_back(u'\x09');
data.push_back(u'E');
data += decode_sjis(command_args);
data.push_back(u'\0');
data.resize((data.size() + 1) & (~1));
session->server_channel.send(0x06, 0x00, data.data(), data.size() * sizeof(char16_t));
if (!is_dchat && (session->version() == GameVersion::PC || session->version() == GameVersion::BB)) {
send_chat_message_from_client(session->server_channel, command_args, 0);
} else {
string data(8, '\0');
data.push_back('\x09');
@@ -700,7 +694,7 @@ Proxy session commands:\n\
} else if ((command_name == "wc") || (command_name == "wchat")) {
auto session = this->get_proxy_session(session_name);
if ((session->version != GameVersion::GC) ||
if ((session->version() != GameVersion::GC) ||
!(session->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) {
throw runtime_error("wchat can only be used on Episode 3");
}
@@ -758,8 +752,8 @@ Proxy session commands:\n\
session->options.override_lobby_event = -1;
} else {
session->options.override_lobby_event = event_for_name(command_args);
if ((session->version != GameVersion::DC) &&
(session->version != GameVersion::PC) && (!((session->version == GameVersion::GC) && (session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)))) {
if ((session->version() != GameVersion::DC) &&
(session->version() != GameVersion::PC) && (!((session->version() == GameVersion::GC) && (session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)))) {
session->client_channel.send(0xDA, session->options.override_lobby_event);
}
}
@@ -811,7 +805,7 @@ Proxy session commands:\n\
} else if ((command_name == "create-item") || (command_name == "set-next-item")) {
auto session = this->get_proxy_session(session_name);
if (session->version == GameVersion::BB) {
if (session->version() == GameVersion::BB) {
throw runtime_error("proxy session is BB");
}
if (!session->is_in_game) {
@@ -828,14 +822,14 @@ Proxy session commands:\n\
session->next_drop_item = item;
string name = session->next_drop_item.name(true);
send_text_message(session->client_channel, u"$C7Next drop:\n" + decode_sjis(name));
send_text_message(session->client_channel, "$C7Next drop:\n" + name);
} else {
send_drop_stacked_item(session->client_channel, item, session->area, session->x, session->z);
send_drop_stacked_item(session->server_channel, item, session->area, session->x, session->z);
string name = item.name(true);
send_text_message(session->client_channel, u"$C7Item created:\n" + decode_sjis(name));
send_text_message(session->client_channel, "$C7Item created:\n" + name);
}
} else if (command_name == "close-idle-sessions") {
+25 -29
View File
@@ -54,7 +54,7 @@ void ServerState::init() {
vector<shared_ptr<Lobby>> ep3_only_lobbies;
for (size_t x = 0; x < 20; x++) {
auto lobby_name = decode_sjis(string_printf("LOBBY%zu", x + 1));
auto lobby_name = string_printf("LOBBY%zu", x + 1);
bool v2_and_later_only = (x > 9);
bool is_ep3_only = (x > 14);
@@ -151,7 +151,7 @@ void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
added_to_lobby = this->create_lobby();
added_to_lobby->flags |= Lobby::Flag::PUBLIC | Lobby::Flag::IS_OVERFLOW;
added_to_lobby->block = 100;
added_to_lobby->name = u"Overflow";
added_to_lobby->name = "Overflow";
added_to_lobby->max_clients = 12;
added_to_lobby->event = this->pre_lobby_event;
added_to_lobby->add_client(c);
@@ -279,12 +279,10 @@ void ServerState::remove_lobby(uint32_t lobby_id) {
this->id_to_lobby.erase(lobby_it);
}
shared_ptr<Client> ServerState::find_client(const std::u16string* identifier,
uint64_t serial_number, shared_ptr<Lobby> l) {
shared_ptr<Client> ServerState::find_client(const std::string* identifier, uint64_t serial_number, shared_ptr<Lobby> l) {
if ((serial_number == 0) && identifier) {
try {
serial_number = stoull(encode_sjis(*identifier), nullptr, 0);
serial_number = stoull(*identifier, nullptr, 0);
} catch (const exception&) {
}
}
@@ -521,7 +519,7 @@ static vector<PortConfiguration> parse_port_configuration(const JSON& json) {
void ServerState::parse_config(const JSON& json, bool is_reload) {
config_log.info("Parsing configuration");
this->name = decode_sjis(json.at("ServerName").as_string());
this->name = json.at("ServerName").as_string();
if (!is_reload) {
try {
@@ -743,22 +741,22 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
config_log.info("Creating menus");
shared_ptr<Menu> information_menu_v2(new Menu(MenuID::INFORMATION, u"Information"));
shared_ptr<Menu> information_menu_v3(new Menu(MenuID::INFORMATION, u"Information"));
shared_ptr<vector<u16string>> information_contents(new vector<u16string>());
shared_ptr<Menu> information_menu_v2(new Menu(MenuID::INFORMATION, "Information"));
shared_ptr<Menu> information_menu_v3(new Menu(MenuID::INFORMATION, "Information"));
shared_ptr<vector<string>> information_contents(new vector<string>());
information_menu_v2->items.emplace_back(InformationMenuItemID::GO_BACK, u"Go back",
u"Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU);
information_menu_v3->items.emplace_back(InformationMenuItemID::GO_BACK, u"Go back",
u"Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU);
information_menu_v2->items.emplace_back(InformationMenuItemID::GO_BACK, "Go back",
"Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU);
information_menu_v3->items.emplace_back(InformationMenuItemID::GO_BACK, "Go back",
"Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU);
{
uint32_t item_id = 0;
for (const auto& item : json.at("InformationMenuContents").as_list()) {
u16string name = decode_sjis(item->get_string(0));
u16string short_desc = decode_sjis(item->get_string(1));
string name = item->get_string(0);
string short_desc = item->get_string(1);
information_menu_v2->items.emplace_back(item_id, name, short_desc, 0);
information_menu_v3->items.emplace_back(item_id, name, short_desc, MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
information_contents->emplace_back(decode_sjis(item->get_string(2)));
information_contents->emplace_back(item->get_string(2));
item_id++;
}
}
@@ -767,7 +765,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->information_contents = information_contents;
auto generate_redirect_destinations_menu = [&](vector<pair<string, uint16_t>>& ret_pds, const char* key) -> shared_ptr<const Menu> {
shared_ptr<Menu> ret(new Menu(MenuID::REDIRECT_DESTINATIONS, u"Other servers"));
shared_ptr<Menu> ret(new Menu(MenuID::REDIRECT_DESTINATIONS, "Other servers"));
ret_pds.clear();
try {
@@ -776,13 +774,13 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
sorted_jsons.emplace(it.first, *it.second);
}
ret->items.emplace_back(RedirectDestinationsMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0);
ret->items.emplace_back(RedirectDestinationsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
uint32_t item_id = 0;
for (const auto& item : sorted_jsons) {
const string& netloc_str = item.second.as_string();
const string& description = "$C7Remote server:\n$C6" + netloc_str;
ret->items.emplace_back(item_id, decode_sjis(item.first), decode_sjis(description), 0);
ret->items.emplace_back(item_id, item.first, description, 0);
ret_pds.emplace_back(parse_netloc(netloc_str));
item_id++;
}
@@ -797,7 +795,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->redirect_destinations_menu_xb = generate_redirect_destinations_menu(this->redirect_destinations_xb, "RedirectDestinations-XB");
auto generate_proxy_destinations_menu = [&](vector<pair<string, uint16_t>>& ret_pds, const char* key) -> shared_ptr<const Menu> {
shared_ptr<Menu> ret(new Menu(MenuID::PROXY_DESTINATIONS, u"Proxy server"));
shared_ptr<Menu> ret(new Menu(MenuID::PROXY_DESTINATIONS, "Proxy server"));
ret_pds.clear();
try {
@@ -806,16 +804,14 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
sorted_jsons.emplace(it.first, *it.second);
}
ret->items.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back",
u"Return to the\nmain menu", 0);
ret->items.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, u"Options",
u"Set proxy session\noptions", 0);
ret->items.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
ret->items.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, "Options", "Set proxy session\noptions", 0);
uint32_t item_id = 0;
for (const auto& item : sorted_jsons) {
const string& netloc_str = item.second.as_string();
const string& description = "$C7Remote server:\n$C6" + netloc_str;
ret->items.emplace_back(item_id, decode_sjis(item.first), decode_sjis(description), 0);
ret->items.emplace_back(item_id, item.first, description, 0);
ret_pds.emplace_back(parse_netloc(netloc_str));
item_id++;
}
@@ -856,9 +852,9 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->proxy_destination_bb.second = 0;
}
this->welcome_message = decode_sjis(json.get_string("WelcomeMessage", ""));
this->pc_patch_server_message = decode_sjis(json.get_string("PCPatchServerMessage", ""));
this->bb_patch_server_message = decode_sjis(json.get_string("BBPatchServerMessage", ""));
this->welcome_message = json.get_string("WelcomeMessage", "");
this->pc_patch_server_message = json.get_string("PCPatchServerMessage", "");
this->bb_patch_server_message = json.get_string("BBPatchServerMessage", "");
}
void ServerState::load_bb_private_keys() {
+6 -6
View File
@@ -49,7 +49,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::string config_filename;
bool is_replay;
std::u16string name;
std::string name;
std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config;
std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
std::string username;
@@ -125,7 +125,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3;
std::shared_ptr<std::vector<std::u16string>> information_contents;
std::shared_ptr<std::vector<std::string>> information_contents;
std::shared_ptr<const Menu> redirect_destinations_menu_dc;
std::shared_ptr<const Menu> redirect_destinations_menu_pc;
std::shared_ptr<const Menu> redirect_destinations_menu_gc;
@@ -144,9 +144,9 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb;
std::pair<std::string, uint16_t> proxy_destination_patch;
std::pair<std::string, uint16_t> proxy_destination_bb;
std::u16string welcome_message;
std::u16string pc_patch_server_message;
std::u16string bb_patch_server_message;
std::string welcome_message;
std::string pc_patch_server_message;
std::string bb_patch_server_message;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
@@ -192,7 +192,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
void remove_lobby(uint32_t lobby_id);
std::shared_ptr<Client> find_client(
const std::u16string* identifier = nullptr,
const std::string* identifier = nullptr,
uint64_t serial_number = 0,
std::shared_ptr<Lobby> l = nullptr);
-40
View File
@@ -214,10 +214,6 @@ const string& name_for_section_id(uint8_t section_id) {
}
}
u16string u16name_for_section_id(uint8_t section_id) {
return decode_sjis(name_for_section_id(section_id));
}
uint8_t section_id_for_name(const string& name) {
string lower_name = tolower(name);
try {
@@ -235,10 +231,6 @@ uint8_t section_id_for_name(const string& name) {
return 0xFF;
}
uint8_t section_id_for_name(const u16string& name) {
return section_id_for_name(encode_sjis(name));
}
const string& name_for_event(uint8_t event) {
if (event < lobby_event_to_name.size()) {
return lobby_event_to_name[event];
@@ -248,10 +240,6 @@ const string& name_for_event(uint8_t event) {
}
}
u16string u16name_for_event(uint8_t event) {
return decode_sjis(name_for_event(event));
}
uint8_t event_for_name(const string& name) {
try {
return name_to_lobby_event.at(name);
@@ -268,10 +256,6 @@ uint8_t event_for_name(const string& name) {
return 0xFF;
}
uint8_t event_for_name(const u16string& name) {
return event_for_name(encode_sjis(name));
}
const string& name_for_lobby_type(uint8_t type) {
try {
return lobby_type_to_name.at(type);
@@ -281,10 +265,6 @@ const string& name_for_lobby_type(uint8_t type) {
}
}
u16string u16name_for_lobby_type(uint8_t type) {
return decode_sjis(name_for_lobby_type(type));
}
uint8_t lobby_type_for_name(const string& name) {
try {
return name_to_lobby_type.at(name);
@@ -301,10 +281,6 @@ uint8_t lobby_type_for_name(const string& name) {
return 0x80;
}
uint8_t lobby_type_for_name(const u16string& name) {
return lobby_type_for_name(encode_sjis(name));
}
const string& name_for_npc(uint8_t npc) {
try {
return npc_id_to_name.at(npc);
@@ -314,10 +290,6 @@ const string& name_for_npc(uint8_t npc) {
}
}
u16string u16name_for_npc(uint8_t npc) {
return decode_sjis(name_for_npc(npc));
}
uint8_t npc_for_name(const string& name) {
try {
return name_to_npc_id.at(name);
@@ -334,10 +306,6 @@ uint8_t npc_for_name(const string& name) {
return 0xFF;
}
uint8_t npc_for_name(const u16string& name) {
return npc_for_name(encode_sjis(name));
}
const char* name_for_char_class(uint8_t cls) {
static const array<const char*, 12> names = {
"HUmar",
@@ -555,10 +523,6 @@ const string& name_for_technique(uint8_t tech) {
}
}
u16string u16name_for_technique(uint8_t tech) {
return decode_sjis(name_for_technique(tech));
}
uint8_t technique_for_name(const string& name) {
try {
return name_to_tech_id.at(name);
@@ -575,10 +539,6 @@ uint8_t technique_for_name(const string& name) {
return 0xFF;
}
uint8_t technique_for_name(const u16string& name) {
return technique_for_name(encode_sjis(name));
}
const vector<const char*> name_for_mag_color({
/* 00 */ "red",
/* 01 */ "blue",
-10
View File
@@ -37,29 +37,19 @@ extern const std::vector<std::string> tech_id_to_name;
extern const std::unordered_map<std::string, uint8_t> name_to_tech_id;
const std::string& name_for_technique(uint8_t tech);
std::u16string u16name_for_technique(uint8_t tech);
uint8_t technique_for_name(const std::string& name);
uint8_t technique_for_name(const std::u16string& name);
const std::string& name_for_section_id(uint8_t section_id);
std::u16string u16name_for_section_id(uint8_t section_id);
uint8_t section_id_for_name(const std::string& name);
uint8_t section_id_for_name(const std::u16string& name);
const std::string& name_for_event(uint8_t event);
std::u16string u16name_for_event(uint8_t event);
uint8_t event_for_name(const std::string& name);
uint8_t event_for_name(const std::u16string& name);
const std::string& name_for_lobby_type(uint8_t type);
std::u16string u16name_for_lobby_type(uint8_t type);
uint8_t lobby_type_for_name(const std::string& name);
uint8_t lobby_type_for_name(const std::u16string& name);
const std::string& name_for_npc(uint8_t npc);
std::u16string u16name_for_npc(uint8_t npc);
uint8_t npc_for_name(const std::string& name);
uint8_t npc_for_name(const std::u16string& name);
const char* name_for_char_class(uint8_t cls);
const char* abbreviation_for_char_class(uint8_t cls);
+244 -132
View File
@@ -11,171 +11,283 @@
using namespace std;
int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count) {
size_t x;
for (x = 0; x < count && s1[x] != 0 && s2[x] != 0; x++) {
if (s1[x] < s2[x]) {
return -1;
} else if (s1[x] > s2[x]) {
return 1;
// A third case is when inbuf is NULL or *inbuf is NULL, and outbuf is NULL or *outbuf is NULL. In this case, the iconv function sets cds conversion state to the initial state.
const iconv_t TextTranscoder::INVALID_IC = (iconv_t)(-1);
const size_t TextTranscoder::FAILURE_RESULT = static_cast<size_t>(-1);
TextTranscoder::TextTranscoder(const char* to, const char* from)
: ic(iconv_open(to, from)) {
if (ic == this->INVALID_IC) {
string error_str = string_for_error(errno);
throw runtime_error(string_printf("failed to initialize %s -> %s text converter: %s", from, to, error_str.c_str()));
}
}
TextTranscoder::TextTranscoder(TextTranscoder&& other) : ic(other.ic) {
other.ic = this->INVALID_IC;
}
TextTranscoder& TextTranscoder::operator=(TextTranscoder&& other) {
this->ic = other.ic;
other.ic = this->INVALID_IC;
return *this;
}
TextTranscoder::~TextTranscoder() {
iconv_close(this->ic);
}
TextTranscoder::Result TextTranscoder::operator()(
void* dest, size_t dest_size, const void* src, size_t src_bytes, bool truncate_oversize_result) {
// Clear any conversion state left over from the previous call
iconv(this->ic, nullptr, nullptr, nullptr, nullptr);
void* orig_dest = dest;
const void* orig_src = src;
size_t ret = iconv(
this->ic,
reinterpret_cast<char**>(const_cast<void**>(&src)),
&src_bytes,
reinterpret_cast<char**>(&dest),
&dest_size);
size_t bytes_read = reinterpret_cast<const char*>(src) - reinterpret_cast<const char*>(orig_src);
if (ret == this->FAILURE_RESULT) {
switch (errno) {
case EILSEQ:
throw runtime_error(string_printf("untranslatable character at position 0x%zX", bytes_read));
case EINVAL:
throw runtime_error(string_printf("incomplete multibyte sequence at position 0x%zX", bytes_read));
case E2BIG:
if (!truncate_oversize_result) {
throw runtime_error("string does not fit in buffer");
} else {
break;
}
default:
throw runtime_error("transcoding failed: " + string_for_error(errno));
}
}
if (s1[x] < s2[x]) {
return -1;
} else if (s1[x] > s2[x]) {
return 1;
}
return 0;
size_t bytes_written = reinterpret_cast<char*>(dest) - reinterpret_cast<char*>(orig_dest);
return Result{
.bytes_read = bytes_read,
.bytes_written = bytes_written,
};
}
static vector<char16_t> unicode_to_sjis_table_data;
static vector<char16_t> sjis_to_unicode_table_data;
string TextTranscoder::operator()(const void* src, size_t src_size) {
// Clear any conversion state left over from the previous call
iconv(this->ic, nullptr, nullptr, nullptr, nullptr);
static void load_sjis_tables() {
unicode_to_sjis_table_data.resize(0x10000, 0);
sjis_to_unicode_table_data.resize(0x10000, 0);
// TODO: this is inefficient; it makes multiple copies of the string
auto file_contents = load_file("system/sjis-table.ini");
auto lines = split(file_contents, '\n');
for (auto line : lines) {
auto tokens = split(line, '\t');
if (tokens.size() < 2) {
continue;
const void* orig_src = src;
deque<string> blocks;
while (src_size > 0) {
// Assume 2x input size on average, but always alocate at least 4 bytes
string& block = blocks.emplace_back(max<size_t>((src_size << 2), 4), '\0');
char* dest = block.data();
size_t dest_size = block.size();
size_t ret = iconv(
this->ic,
reinterpret_cast<char**>(const_cast<void**>(&src)),
&src_size,
reinterpret_cast<char**>(&dest),
&dest_size);
block.resize(block.size() - dest_size);
if (block.size() == 0) {
// This should never happen because no character should be more than 4
// bytes long in any known encoding
throw runtime_error("block size too small for conversion");
}
char16_t sjis_char = stoul(tokens[0], nullptr, 0);
char16_t unicode_char = stoul(tokens[1], nullptr, 0);
unicode_to_sjis_table_data[unicode_char] = sjis_char;
sjis_to_unicode_table_data[sjis_char] = unicode_char;
size_t bytes_read = reinterpret_cast<const char*>(src) - reinterpret_cast<const char*>(orig_src);
if (ret == this->FAILURE_RESULT) {
switch (errno) {
case EILSEQ:
throw runtime_error(string_printf("untranslatable character at position %zu", bytes_read));
case EINVAL:
throw runtime_error(string_printf("incomplete multibyte sequence at position %zu", bytes_read));
case E2BIG:
break;
default:
throw runtime_error("transcoding failed: " + string_for_error(errno));
}
}
}
return join(blocks, "");
}
string TextTranscoder::operator()(const string& data) {
return this->operator()(data.data(), data.size());
}
TextTranscoder tt_8859_to_utf8("UTF-8", "ISO-8859-1");
TextTranscoder tt_utf8_to_8859("ISO-8859-1", "UTF-8");
TextTranscoder tt_sjis_to_utf8("UTF-8", "SHIFT_JIS");
TextTranscoder tt_utf8_to_sjis("SHIFT_JIS", "UTF-8");
TextTranscoder tt_utf16_to_utf8("UTF-8", "UTF-16LE");
TextTranscoder tt_utf8_to_utf16("UTF-16LE", "UTF-8");
TextTranscoder tt_ascii_to_utf8("UTF-8", "ASCII");
TextTranscoder tt_utf8_to_ascii("ASCII", "UTF-8");
std::string tt_encode_marked_optional(const std::string& utf8, uint8_t default_language, bool is_utf16) {
if (is_utf16) {
return tt_utf8_to_utf16(utf8);
} else {
if (default_language) {
try {
return tt_utf8_to_8859(utf8);
} catch (const exception& e) {
return "\tJ" + tt_utf8_to_sjis(utf8);
}
} else {
try {
return tt_utf8_to_sjis(utf8);
} catch (const exception& e) {
return "\tE" + tt_utf8_to_8859(utf8);
}
}
}
}
static const vector<char16_t>& sjis_to_unicode_table() {
if (sjis_to_unicode_table_data.empty()) {
load_sjis_tables();
std::string tt_encode_marked(const std::string& utf8, uint8_t default_language, bool is_utf16) {
if (is_utf16) {
return tt_utf8_to_utf16((default_language ? "\tE" : "\tJ") + utf8);
} else {
if (default_language) {
try {
return "\tE" + tt_utf8_to_8859(utf8);
} catch (const exception& e) {
return "\tJ" + tt_utf8_to_sjis(utf8);
}
} else {
try {
return "\tJ" + tt_utf8_to_sjis(utf8);
} catch (const exception& e) {
return "\tE" + tt_utf8_to_8859(utf8);
}
}
}
return sjis_to_unicode_table_data;
}
static const vector<char16_t>& unicode_to_sjis_table() {
if (unicode_to_sjis_table_data.empty()) {
load_sjis_tables();
std::string tt_decode_marked(const std::string& data, uint8_t default_language, bool is_utf16) {
if (is_utf16) {
string ret = tt_utf16_to_utf8(data);
if (ret.size() >= 2 && ret[0] == '\t' && (ret[1] == 'E' || ret[1] == 'J')) {
ret = ret.substr(2);
}
return ret;
} else {
if (data.size() >= 2 && data[0] == '\t') {
if (data[1] == 'J') {
return tt_sjis_to_utf8(data.substr(2));
} else if (data[1] == 'E') {
return tt_8859_to_utf8(data.substr(2));
}
}
return default_language ? tt_8859_to_utf8(data) : tt_sjis_to_utf8(data);
}
return unicode_to_sjis_table_data;
}
std::string encode_sjis(const char16_t* src, size_t src_count) {
const auto& table = unicode_to_sjis_table();
string add_language_marker(const string& s, char marker) {
if ((s.size() >= 2) && (s[0] == '\t') && (s[1] != 'C')) {
return s;
}
const char16_t* src_end = src + src_count;
string ret;
while ((src != src_end) && *src) {
uint16_t ch = *(src++);
uint16_t translated_c = table[ch];
if (translated_c == 0) {
throw runtime_error("untranslatable unicode character");
} else if (translated_c & 0xFF00) {
ret.push_back((translated_c >> 8) & 0xFF);
ret.push_back(translated_c & 0xFF);
} else {
ret.push_back(translated_c & 0xFF);
}
};
ret.push_back('\t');
ret.push_back(marker);
ret += s;
return ret;
}
size_t encode_sjis(
char* dest,
size_t dest_count,
const char16_t* src,
size_t src_count,
bool allow_skip_terminator) {
const auto& table = unicode_to_sjis_table();
if (dest_count == 0) {
throw logic_error("cannot encode into zero-length buffer");
string remove_language_marker(const string& s) {
if ((s.size() < 2) || (s[0] != '\t') || (s[1] == 'C')) {
return s;
}
return s.substr(2);
}
const char* dest_start = dest;
const char16_t* src_end = src + src_count;
const char* dest_end = dest + (allow_skip_terminator ? dest_count : (dest_count - 1));
while ((dest != dest_end) && (src != src_end) && *src) {
uint16_t ch = *(src++);
uint16_t translated_c = table[ch];
if (translated_c == 0) {
throw runtime_error("untranslatable unicode character");
} else if (translated_c & 0xFF00) {
*(dest++) = (translated_c >> 8) & 0xFF;
// If the second byte of this character would cause the null to overrun
// the buffer, erase the first byte instead and return early
if (dest == dest_end) {
*(dest - 1) = 0;
void replace_char_inplace(char* a, char f, char r) {
while (*a) {
if (*a == f) {
*a = r;
}
a++;
}
}
size_t add_color_inplace(char* a, size_t max_chars) {
char* d = a;
char* orig_d = d;
for (size_t x = 0; (x < max_chars) && *a; x++) {
if (*a == '$') {
*(d++) = '\t';
} else if (*a == '#') {
*(d++) = '\n';
} else if (*a == '%') {
a++;
x++;
if (*a == 's') {
*(d++) = '$';
} else if (*a == '%') {
*(d++) = '%';
} else if (*a == 'n') {
*(d++) = '#';
} else if (*a == '\0') {
break;
} else {
*(dest++) = translated_c & 0xFF;
*(d++) = *a;
}
} else {
*(dest++) = translated_c & 0xFF;
*(d++) = *a;
}
a++;
}
if (!allow_skip_terminator || (dest != dest_end)) {
*dest = 0;
dest++;
}
return dest - dest_start;
*d = 0;
// TODO: we should clear the chars after the null if the new string is shorter
// than the original
return d - orig_d;
}
std::u16string decode_sjis(const char* src, size_t src_count) {
const auto& table = sjis_to_unicode_table();
const char* src_end = src + src_count;
u16string ret;
while ((src != src_end) && *src) {
uint16_t src_char = *(src++);
if (src_char & 0x80) {
if (src == src_end) {
throw runtime_error("incomplete extended character");
}
src_char = (src_char << 8) | static_cast<uint8_t>(*(src++));
if ((src_char & 0xFF) == 0) {
throw runtime_error("incomplete extended character");
}
}
ret.push_back(table[src_char]);
};
return ret;
void add_color_inplace(string& s) {
s.resize(add_color_inplace(s.data(), s.size()));
}
size_t decode_sjis(
char16_t* dest,
size_t dest_count,
const char* src,
size_t src_count,
bool allow_skip_terminator) {
const auto& table = sjis_to_unicode_table();
if (dest_count == 0) {
throw logic_error("cannot decode into zero-length buffer");
}
const char16_t* dest_start = dest;
const char* src_end = src + src_count;
const char16_t* dest_end = dest + (allow_skip_terminator ? dest_count : (dest_count - 1));
while ((dest != dest_end) && (src != src_end) && *src) {
uint16_t src_char = *(src++);
if (src_char & 0x80) {
if (src == src_end) {
throw runtime_error("incomplete extended character");
}
src_char = (src_char << 8) | static_cast<uint8_t>(*(src++));
if ((src_char & 0xFF) == 0) {
throw runtime_error("incomplete extended character");
void add_color(StringWriter& w, const char* src, size_t max_input_chars) {
for (size_t x = 0; (x < max_input_chars) && *src; x++) {
if (*src == '$') {
w.put<char>('\t');
} else if (*src == '#') {
w.put<char>('\n');
} else if (*src == '%') {
src++;
x++;
if (*src == 's') {
w.put<char>('$');
} else if (*src == '%') {
w.put<char>('%');
} else if (*src == 'n') {
w.put<char>('#');
} else if (*src == '\0') {
break;
} else {
w.put<char>(*src);
}
} else {
w.put<char>(*src);
}
*(dest++) = table[src_char];
};
if (!allow_skip_terminator || (dest != dest_end)) {
*(dest++) = 0;
src++;
}
return dest - dest_start;
w.put<char>(0);
}
std::string add_color(const std::string& s) {
StringWriter w;
add_color(w, s.data(), s.size());
return std::move(w.str());
}
+298 -456
View File
@@ -1,5 +1,6 @@
#pragma once
#include <iconv.h>
#include <inttypes.h>
#include <stddef.h>
#include <string.h>
@@ -9,179 +10,46 @@
#include <stdexcept>
#include <string>
// (1a) Conversion functions
// Conversion functions
// These return the number of characters written, including the terminating null
// character. In the case of encode_sjis, two-byte characters count as two
// characters, so the returned number is the number of bytes written.
// allow_skip_terminator means no null byte will be written if dest_count
// characters are written to the output. If this argument is false, a null
// terminator is always written, even if the string is truncated.
size_t encode_sjis(
char* dest, size_t dest_count,
const char16_t* src, size_t src_count,
bool allow_skip_terminator = false);
size_t decode_sjis(
char16_t* dest, size_t dest_count,
const char* src, size_t src_count,
bool allow_skip_terminator = false);
class TextTranscoder {
public:
TextTranscoder(const char* to, const char* from);
TextTranscoder(const TextTranscoder&) = delete;
TextTranscoder(TextTranscoder&&);
TextTranscoder& operator=(const TextTranscoder&) = delete;
TextTranscoder& operator=(TextTranscoder&&);
~TextTranscoder();
std::string encode_sjis(const char16_t* source, size_t src_count);
std::u16string decode_sjis(const char* source, size_t src_count);
struct Result {
size_t bytes_read;
size_t bytes_written;
};
Result operator()(void* dest, size_t dest_size, const void* src, size_t src_bytes, bool truncate_oversize_result);
inline std::string encode_sjis(const std::u16string& s) {
return encode_sjis(s.data(), s.size());
}
std::string operator()(const void* src, size_t src_bytes);
std::string operator()(const std::string& data);
inline std::u16string decode_sjis(const std::string& s) {
return decode_sjis(s.data(), s.size());
}
private:
static const iconv_t INVALID_IC;
static const size_t FAILURE_RESULT;
iconv_t ic;
};
// These functions exist so that decode_sjis and encode_sjis can be
// indiscriminately used within templates that use different char types.
inline const std::string& encode_sjis(const std::string& s) { return s; }
inline const std::u16string& decode_sjis(const std::u16string& s) { return s; }
extern TextTranscoder tt_8859_to_utf8;
extern TextTranscoder tt_utf8_to_8859;
extern TextTranscoder tt_sjis_to_utf8;
extern TextTranscoder tt_utf8_to_sjis;
extern TextTranscoder tt_utf16_to_utf8;
extern TextTranscoder tt_utf8_to_utf16;
extern TextTranscoder tt_ascii_to_utf8;
extern TextTranscoder tt_utf8_to_ascii;
// (1b) Type-independent utility functions
std::string tt_encode_marked_optional(const std::string& utf8, uint8_t default_language, bool is_utf16);
std::string tt_encode_marked(const std::string& utf8, uint8_t default_language, bool is_utf16);
std::string tt_decode_marked(const std::string& data, uint8_t default_language, bool is_utf16);
template <typename T>
size_t text_strlen_t(const T* s) {
size_t ret = 0;
for (; s[ret] != 0; ret++) {
}
return ret;
}
template <typename T>
size_t text_strnlen_t(const T* s, size_t count) {
size_t ret = 0;
for (; (ret < count) && (s[ret] != 0); ret++) {
}
return ret;
}
template <typename T>
size_t text_streq_t(const T* a, const T* b) {
for (;;) {
if (*a != *b) {
return false;
}
if (*a == 0) {
return true;
}
a++;
b++;
}
}
template <typename T>
size_t text_strneq_t(const T* a, const T* b, size_t count) {
for (; count; count--) {
if (*a != *b) {
return false;
}
if (*a == 0) {
return true;
}
a++;
b++;
}
return true;
}
template <typename T>
size_t text_strncpy_t(T* dest, const T* src, size_t count) {
size_t x;
for (x = 0; x < count && src[x] != 0; x++) {
dest[x] = src[x];
}
if (x < count) {
dest[x++] = 0;
}
return x;
}
// Like strncpy, but *always* null-terminates the string, even if it has to
// truncate it.
template <typename T>
size_t text_strnzcpy_t(T* dest, const T* src, size_t count) {
size_t x;
for (x = 0; x < count - 1 && src[x] != 0; x++) {
dest[x] = src[x];
}
dest[x++] = 0;
return x;
}
// (2) Type conversion functions
template <typename DestT, typename SrcT = DestT>
size_t text_strncpy_t(DestT*, size_t, const SrcT*, size_t) {
static_assert(always_false<DestT, SrcT>::v,
"unspecialized text_strncpy_t should never be called");
return 0;
}
template <>
inline size_t text_strncpy_t<char>(
char* dest, size_t dest_count, const char* src, size_t src_count) {
size_t count = std::min<size_t>(dest_count, src_count);
return text_strncpy_t(dest, src, count);
}
template <>
inline size_t text_strncpy_t<char, char16_t>(
char* dest, size_t dest_count, const char16_t* src, size_t src_count) {
return encode_sjis(dest, dest_count, src, src_count, true);
}
template <>
inline size_t text_strncpy_t<char16_t, char>(
char16_t* dest, size_t dest_count, const char* src, size_t src_count) {
return decode_sjis(dest, dest_count, src, src_count, true);
}
template <>
inline size_t text_strncpy_t<char16_t>(
char16_t* dest, size_t dest_count, const char16_t* src, size_t src_count) {
size_t count = std::min<size_t>(dest_count, src_count);
return text_strncpy_t(dest, src, count);
}
template <typename DestT, typename SrcT = DestT>
size_t text_strnzcpy_t(DestT*, size_t, const SrcT*, size_t) {
static_assert(always_false<DestT, SrcT>::v,
"unspecialized text_strnzcpy_t should never be called");
return 0;
}
template <>
inline size_t text_strnzcpy_t<char>(
char* dest, size_t dest_count, const char* src, size_t src_count) {
size_t count = std::min<size_t>(dest_count, src_count);
return text_strnzcpy_t(dest, src, count);
}
template <>
inline size_t text_strnzcpy_t<char, char16_t>(
char* dest, size_t dest_count, const char16_t* src, size_t src_count) {
return encode_sjis(dest, dest_count, src, src_count);
}
template <>
inline size_t text_strnzcpy_t<char16_t, char>(
char16_t* dest, size_t dest_count, const char* src, size_t src_count) {
return decode_sjis(dest, dest_count, src, src_count);
}
template <>
inline size_t text_strnzcpy_t<char16_t>(
char16_t* dest, size_t dest_count, const char16_t* src, size_t src_count) {
size_t count = std::min<size_t>(dest_count, src_count);
return text_strnzcpy_t(dest, src, count);
}
// (3) Packed text objects for use in protocol structs
// Packed array object for use in protocol structs
template <typename ItemT, size_t Count>
struct parray {
@@ -357,325 +225,299 @@ struct parray {
}
} __attribute__((packed));
template <typename CharT, size_t Count>
struct ptext : parray<CharT, Count> {
ptext() {
this->clear(0);
}
ptext(const ptext& other) : parray<CharT, Count>(other) {}
ptext(ptext&& s) = delete;
// Packed text objects for use in protocol structs
template <typename OtherCharT>
ptext(const OtherCharT* s) {
if (!s) {
throw std::logic_error("attempted to assign nullptr to ptext");
enum class TextEncoding {
UTF8,
UTF16,
SJIS,
ISO8859,
ASCII,
MARKED,
CHALLENGE8, // MARKED but with challenge encryption on top
CHALLENGE16, // UTF16 but with challenge encryption on top
};
template <typename CharT>
void encrypt_challenge_rank_text_t(void* vdata, size_t count) {
CharT* data = reinterpret_cast<CharT*>(vdata);
CharT prev = 0;
for (CharT* p = data; p != data + count; p++) {
CharT ch = *p;
if (ch == 0) {
break;
}
this->operator=(s);
*p = ((ch - prev) ^ 0x7F) & 0xFF;
prev = ch;
}
template <typename OtherCharT>
ptext(const OtherCharT* s, size_t count) {
if (!s) {
throw std::logic_error("attempted to assign nullptr to ptext");
}
template <typename CharT>
void decrypt_challenge_rank_text_t(void* vdata, size_t count) {
CharT* data = reinterpret_cast<CharT*>(vdata);
for (CharT* p = data; p != data + count; p++) {
if (*p == 0) {
break;
}
if (p == data) {
*p ^= 0x7F;
} else {
*p = ((*p ^ 0x7F) + *(p - 1)) & 0xFF;
}
this->assign(s, count);
}
template <typename OtherCharT>
ptext(const std::basic_string<OtherCharT>& s) {
this->operator=(s);
}
// This struct does not inherit from parray, even though it's semantically
// similar, because we want to enforce that the correct encoding is used.
template <
TextEncoding Encoding,
size_t Chars,
size_t BytesPerChar = (((Encoding == TextEncoding::UTF16) || (Encoding == TextEncoding::CHALLENGE16)) ? 2 : 1)>
struct pstring {
static constexpr size_t Bytes = Chars * BytesPerChar;
static constexpr size_t bytes() {
return Bytes;
}
template <typename OtherCharT, size_t OtherCount>
ptext(const ptext<OtherCharT, OtherCount>& s) {
this->operator=(s);
static constexpr size_t chars() {
return Chars;
}
size_t len() const {
return text_strnlen_t(this->items, Count);
uint8_t data[Bytes];
pstring() {
memset(this->data, 0, Bytes);
}
pstring(const pstring<Encoding, Chars, BytesPerChar>& other) {
memcpy(this->data, other.data, Bytes);
}
pstring(const std::string& s, uint8_t language) {
this->encode(s, language);
}
pstring(pstring<Encoding, Chars, BytesPerChar>&& other) = delete;
// Q: Why is there no c_str() here?
// A: Because the contents of a ptext don't have to be null-terminated.
ptext& operator=(const ptext& s) {
memcpy(this->items, s.items, sizeof(CharT) * Count);
pstring<Encoding, Chars, BytesPerChar>& operator=(const pstring<Encoding, Chars, BytesPerChar>& other) {
memcpy(this->data, other.data, Bytes);
return *this;
}
ptext& operator=(ptext&& s) = delete;
template <typename OtherCharT>
ptext& operator=(const OtherCharT* s) {
if (!s) {
throw std::logic_error("attempted to assign nullptr to ptext");
}
size_t chars_written = text_strncpy_t(this->items, Count, s, Count);
this->clear_after(chars_written);
template <size_t OtherChars>
pstring<Encoding, Chars, BytesPerChar>& operator=(const pstring<Encoding, OtherChars, BytesPerChar>& other) {
size_t end_offset = std::min<size_t>(Bytes, pstring<Encoding, OtherChars, BytesPerChar>::Bytes);
memcpy(this->data, other.data, end_offset);
this->clear_after(end_offset);
return *this;
}
template <typename OtherCharT>
ptext& assign(const OtherCharT* s, size_t s_count) {
if (!s) {
throw std::logic_error("attempted to assign nullptr to ptext");
pstring<Encoding, Chars, BytesPerChar>& operator=(pstring<Encoding, Chars, BytesPerChar>&& s) = delete;
void encode(const std::string& s, uint8_t client_language = 1) {
try {
switch (Encoding) {
case TextEncoding::CHALLENGE8:
case TextEncoding::ASCII: {
auto ret = tt_utf8_to_ascii(this->data, Bytes, s.data(), s.size(), true);
this->clear_after(ret.bytes_written);
if (Encoding == TextEncoding::CHALLENGE8) {
encrypt_challenge_rank_text_t<le_uint16_t>(this->data, Bytes);
}
break;
}
case TextEncoding::ISO8859: {
auto ret = tt_utf8_to_8859(this->data, Bytes, s.data(), s.size(), true);
this->clear_after(ret.bytes_written);
break;
}
case TextEncoding::SJIS: {
auto ret = tt_utf8_to_sjis(this->data, Bytes, s.data(), s.size(), true);
this->clear_after(ret.bytes_written);
break;
}
case TextEncoding::UTF16: {
auto ret = tt_utf8_to_utf16(this->data, Bytes, s.data(), s.size(), true);
this->clear_after(ret.bytes_written);
break;
}
case TextEncoding::UTF8:
memcpy(this->data, s.data(), std::min<size_t>(s.size(), Bytes));
this->clear_after(s.size());
break;
case TextEncoding::CHALLENGE16: {
auto ret = tt_utf8_to_utf16(this->data, Bytes, s.data(), s.size(), true);
encrypt_challenge_rank_text_t<le_uint16_t>(this->data, ret.bytes_written / 2);
this->clear_after(ret.bytes_written);
break;
}
case TextEncoding::MARKED: {
if (client_language == 0) {
try {
auto ret = tt_utf8_to_sjis(this->data, Bytes, s.data(), s.size(), true);
this->clear_after(ret.bytes_written);
} catch (const std::runtime_error&) {
this->data[0] = '\t';
this->data[1] = 'E';
auto ret = tt_utf8_to_8859(this->data + 2, Bytes - 2, s.data(), s.size(), true);
this->clear_after(ret.bytes_written + 2);
}
} else {
try {
auto ret = tt_utf8_to_8859(this->data, Bytes, s.data(), s.size(), true);
this->clear_after(ret.bytes_written);
} catch (const std::runtime_error&) {
this->data[0] = '\t';
this->data[1] = 'J';
auto ret = tt_utf8_to_sjis(this->data + 2, Bytes - 2, s.data(), s.size(), true);
this->clear_after(ret.bytes_written + 2);
}
}
break;
}
default:
throw std::logic_error("unknown text encoding");
}
} catch (const std::runtime_error& e) {
log_warning("Unencodable text: %s", e.what());
if (Bytes >= 3) {
this->data[0] = '<';
this->data[1] = '?';
this->data[2] = '>';
this->clear_after(3);
} else if (Bytes >= 1) {
this->data[0] = '?';
this->clear_after(1);
}
}
size_t chars_written = text_strncpy_t(this->items, Count, s, s_count);
this->clear_after(chars_written);
return *this;
}
template <typename OtherCharT>
ptext& operator=(const std::basic_string<OtherCharT>& s) {
size_t chars_written = text_strncpy_t(this->items, Count, s.c_str(), s.size());
this->clear_after(chars_written);
return *this;
}
template <typename OtherCharT, size_t OtherCount>
ptext& operator=(const ptext<OtherCharT, OtherCount>& s) {
size_t chars_written = text_strncpy_t(this->items, Count, s.items, OtherCount);
this->clear_after(chars_written);
return *this;
}
template <typename OtherCharT>
bool operator==(const OtherCharT* s) const {
if (!s) {
throw std::logic_error("attempted to compare ptext to nullptr");
std::string decode(uint8_t client_language = 1) const {
try {
switch (Encoding) {
case TextEncoding::CHALLENGE8: {
std::string decrypted(reinterpret_cast<const char*>(this->data), this->used_bytes_8());
decrypt_challenge_rank_text_t<uint8_t>(decrypted.data(), decrypted.size());
return tt_ascii_to_utf8(decrypted.data(), decrypted.size());
}
case TextEncoding::ASCII:
return tt_ascii_to_utf8(this->data, this->used_bytes_8());
case TextEncoding::ISO8859:
return tt_8859_to_utf8(this->data, this->used_bytes_8());
case TextEncoding::SJIS:
return tt_sjis_to_utf8(this->data, this->used_bytes_8());
case TextEncoding::UTF16:
return tt_utf16_to_utf8(this->data, this->used_bytes_16());
case TextEncoding::UTF8:
return std::string(reinterpret_cast<const char*>(&this->data[0]), this->used_bytes_8());
case TextEncoding::CHALLENGE16: {
std::string decrypted(reinterpret_cast<const char*>(&this->data[0]), this->used_bytes_8());
decrypt_challenge_rank_text_t<le_uint16_t>(decrypted.data(), decrypted.size());
return tt_utf16_to_utf8(decrypted.data(), decrypted.size());
}
case TextEncoding::MARKED: {
size_t offset = 0;
if (this->data[0] == '\t') {
if (this->data[1] == 'J') {
client_language = 0;
offset = 2;
} else {
client_language = 1;
offset = 2;
}
}
return client_language
? tt_8859_to_utf8(&this->data[offset], this->used_bytes_8() - offset)
: tt_sjis_to_utf8(&this->data[offset], this->used_bytes_8() - offset);
}
default:
throw std::logic_error("unknown text encoding");
}
} catch (const std::runtime_error& e) {
log_warning("Undecodable text: %s", e.what());
return "<?>";
}
return text_strneq_t(this->items, s, Count);
}
template <typename OtherCharT>
bool operator==(const std::basic_string<OtherCharT>& s) const {
return text_strneq_t(this->items, s.c_str(), Count);
}
template <typename OtherCharT, size_t OtherCount>
bool operator==(const ptext<OtherCharT, OtherCount>& s) const {
return text_strneq_t(this->items, s.items, std::min<size_t>(Count, OtherCount));
}
template <typename OtherCharT>
bool operator!=(const OtherCharT* s) const {
if (!s) {
throw std::logic_error("attempted to compare ptext to nullptr");
}
return !this->operator==(s);
}
template <typename OtherCharT>
bool operator!=(const std::basic_string<OtherCharT>& s) const {
return !this->operator==(s);
}
template <typename OtherCharT, size_t OtherCount>
bool operator!=(const ptext<OtherCharT, OtherCount>& s) const {
return !this->operator==(s);
}
template <typename OtherCharT>
bool eq_n(const OtherCharT* s, size_t count) const {
if (!s) {
throw std::logic_error("attempted to compare ptext to nullptr");
}
return text_strneq_t(this->items, s, count);
bool operator==(const pstring<Encoding, Chars, BytesPerChar>& other) const {
return (memcmp(this->data, other.data, Bytes) == 0);
}
template <typename OtherCharT>
bool eq_n(const std::basic_string<OtherCharT>& s, size_t count) const {
return text_strneq_t(this->items, s.c_str(), count);
}
template <typename OtherCharT, size_t OtherCount>
bool eq_n(const ptext<OtherCharT, OtherCount>& s, size_t count) const {
return text_strneq_t(this->items, s.items, count);
bool operator!=(const pstring<Encoding, Chars, BytesPerChar>& other) const {
return (memcmp(this->data, other.data, Bytes) != 0);
}
operator std::basic_string<CharT>() const {
return std::basic_string<CharT>(this->items, this->len());
bool eq(const std::string& other, uint8_t language = 1) const {
return this->decode(language) == other;
}
size_t used_bytes_8() const {
size_t size = 0;
for (size = 0; size < Bytes; size++) {
if (!this->data[size]) {
return size;
}
}
return Bytes;
}
size_t used_bytes_16() const {
if (Bytes & 1) {
throw std::logic_error("used_bytes_16 must not be called on an odd-length pstring");
}
for (size_t z = 0; z < Bytes; z += 2) {
if (!this->data[z] && !this->data[z + 1]) {
return z;
}
}
return Bytes;
}
bool empty() const {
return (this->items[0] == 0);
for (size_t z = 0; z < BytesPerChar; z++) {
if (this->data[z] != 0) {
return false;
}
}
return true;
}
void clear(uint8_t v = 0) {
memset(this->data, v, Chars * BytesPerChar);
}
void clear_after(size_t pos, uint8_t v = 0) {
for (pos *= BytesPerChar; pos < Chars * BytesPerChar; pos++) {
this->data[pos] = v;
}
}
void set_byte(size_t pos, uint8_t v) {
if (pos >= Bytes) {
throw std::out_of_range("pstring byte offset out of range");
}
this->data[pos] = v;
}
void assign_raw(const void* data, size_t size) {
memcpy(this->data, data, std::min<size_t>(size, Bytes));
this->clear_after(size);
}
void assign_raw(const std::string& data) {
this->assign_raw(data.data(), data.size());
}
uint8_t at(size_t pos) const {
if (pos >= Bytes) {
throw std::out_of_range("pstring index out of range");
}
return this->data[pos];
}
// Note: The contents of a pstring do not have to be null-terminated, so there
// is no .c_str() function.
} __attribute__((packed));
// (4) Markers and character replacement
// Helper functions
template <typename CharT>
std::basic_string<CharT> add_language_marker(
const std::basic_string<CharT>& s, CharT marker) {
if ((s.size() >= 2) && (s[0] == '\t') && (s[1] != 'C')) {
return s;
}
void replace_char_inplace(char* a, char f, char r);
std::basic_string<CharT> ret;
ret.push_back('\t');
ret.push_back(marker);
ret += s;
return ret;
}
void add_color(StringWriter& w, const char* src, size_t max_input_chars);
std::string add_color(const std::string& s);
template <typename CharT, size_t Count>
std::basic_string<CharT> add_language_marker(
const ptext<CharT, Count>& s, CharT marker) {
if ((s.items[0] == '\t') && (s.items[1] != 'C')) {
return s;
}
std::basic_string<CharT> ret;
ret.push_back('\t');
ret.push_back(marker);
ret += s;
return ret;
}
template <typename CharT, size_t Count>
void add_language_marker_inplace(ptext<CharT, Count>& s, char16_t marker) {
static_assert(Count >= 2, "cannot use add_language_marker_inplace on ptext with fewer than 2 characters");
if ((s.items[0] == '\t') && (s.items[1] != 'C')) {
return;
}
size_t end_offset = std::min<size_t>(s.len() + 2, Count);
for (size_t z = end_offset; z > 2; z--) {
s[z - 1] = s[z - 3];
}
s[0] = '\t';
s[1] = marker;
}
template <typename CharT>
const CharT* remove_language_marker(const CharT* s) {
if ((s[0] != '\t') || (s[1] == 'C')) {
return s;
}
return s + 2;
}
template <typename CharT, size_t Count>
std::basic_string<CharT> remove_language_marker(const ptext<CharT, Count>& s) {
if ((s.items[0] != '\t') || (s.items[1] == L'C')) {
return s;
}
return &s.items[2];
}
template <typename CharT>
std::basic_string<CharT> remove_language_marker(
const std::basic_string<CharT>& s) {
if ((s.size() < 2) || (s[0] != L'\t') || (s[1] == L'C')) {
return s;
}
return s.substr(2);
}
template <typename CharT, size_t Count>
void remove_language_marker_inplace(ptext<CharT, Count>& a) {
if ((a.items[0] == '\t') && (a.items[1] != 'C')) {
text_strnzcpy_t(a.items, Count, &a.items[2], Count);
a.items[text_strlen_t(a.items) + 1] = 0;
}
}
template <typename T>
void replace_char_inplace(T* a, T f, T r) {
while (*a) {
if (*a == f) {
*a = r;
}
a++;
}
}
template <typename T>
size_t add_color_inplace(T* a, size_t max_chars) {
T* d = a;
T* orig_d = d;
for (size_t x = 0; (x < max_chars) && *a; x++) {
if (*a == '$') {
*(d++) = '\t';
} else if (*a == '#') {
*(d++) = '\n';
} else if (*a == '%') {
a++;
x++;
if (*a == 's') {
*(d++) = '$';
} else if (*a == '%') {
*(d++) = '%';
} else if (*a == 'n') {
*(d++) = '#';
} else if (*a == '\0') {
break;
} else {
*(d++) = *a;
}
} else {
*(d++) = *a;
}
a++;
}
*d = 0;
// TODO: we should clear the chars after the null if the new string is shorter
// than the original
return d - orig_d;
}
template <typename T>
void add_color_inplace(std::basic_string<T>& s) {
size_t new_size = add_color_inplace(s.data(), s.size());
s.resize(new_size);
}
template <typename T>
void add_color(StringWriter& w, const T* src, size_t max_input_chars) {
for (size_t x = 0; (x < max_input_chars) && *src; x++) {
if (*src == '$') {
w.put<T>('\t');
} else if (*src == '#') {
w.put<T>('\n');
} else if (*src == '%') {
src++;
x++;
if (*src == 's') {
w.put<T>('$');
} else if (*src == '%') {
w.put<T>('%');
} else if (*src == 'n') {
w.put<T>('#');
} else if (*src == '\0') {
break;
} else {
w.put<T>(*src);
}
} else {
w.put<T>(*src);
}
src++;
}
w.put<T>(0);
}
template <typename CharT, size_t Count>
void add_color_inplace(ptext<CharT, Count>& t) {
size_t sx = 0;
size_t dx = 0;
for (; (sx < Count - 1) && t.items[sx]; sx++) {
if (t.items[sx] == '$') {
t.items[dx] = '\t';
} else if (t.items[sx] == '#') {
t.items[dx] = '\n';
} else if (t.items[sx] == '%') {
sx++;
if ((sx == Count - 1) || (t.items[sx] == '\0')) {
break;
} else if (t.items[sx] == 's') {
t.items[dx] = '$';
} else if (t.items[sx] == '%') {
t.items[dx] = '%';
} else if (t.items[sx] == 'n') {
t.items[dx] = '#';
} else {
t.items[dx] = t.items[sx];
}
} else {
t.items[dx] = t.items[sx];
}
dx++;
}
for (; dx < Count; dx++) {
t.items[dx] = 0;
}
}
size_t add_color_inplace(char* a, size_t max_chars);
void add_color_inplace(std::string& s);
+8 -6
View File
@@ -8,20 +8,21 @@
using namespace std;
vector<u16string> parse_unicode_text_set(const string& prs_data) {
vector<string> parse_unicode_text_set(const string& prs_data) {
string data = prs_decompress(prs_data);
StringReader r(data);
r.skip(4);
uint32_t count = r.get_u32l();
vector<u16string> ret;
vector<string> ret;
while (ret.size() < count) {
ret.emplace_back(&r.pget<char16_t>(r.get_u32l()));
u16string s(&r.pget<char16_t>(r.get_u32l()));
ret.emplace_back(tt_utf16_to_utf8(s.data(), s.size() * 2));
}
return ret;
}
string serialize_unicode_text_set(const vector<u16string>& strings) {
string serialize_unicode_text_set(const vector<string>& strings) {
StringWriter w;
w.put_u32l(strings.size());
size_t string_offset = (strings.size() * 4) + 4; // Header size
@@ -30,8 +31,9 @@ string serialize_unicode_text_set(const vector<u16string>& strings) {
string_offset = (((s.size() + 1) << 1) + 3) & (~3);
}
for (const auto& s : strings) {
u16string uni_s = decode_sjis(s);
w.write(uni_s.c_str(), (uni_s.size() + 1) * 2);
string s_utf16 = tt_utf8_to_utf16(s);
w.write(s_utf16.data(), s_utf16.size());
w.put_u16(0);
while (w.size() & 3) {
w.put_u8(0);
}
+2 -2
View File
@@ -3,5 +3,5 @@
#include <string>
#include <vector>
std::vector<std::u16string> parse_unicode_text_set(const std::string& prs_data);
std::string serialize_unicode_text_set(const std::vector<std::u16string>& strings);
std::vector<std::string> parse_unicode_text_set(const std::string& prs_data);
std::string serialize_unicode_text_set(const std::vector<std::string>& strings);