rewrite text encoding to handle non-English properly
This commit is contained in:
+2
-2
@@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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++;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+29
-28
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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 cd’s 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
@@ -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,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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user