rewrite text encoding to handle non-English properly
This commit is contained in:
+3
-2
@@ -33,6 +33,7 @@ set (LIBEVENT_LIBRARIES
|
||||
${LIBEVENT_CORE})
|
||||
|
||||
find_package(phosg REQUIRED)
|
||||
find_package(Iconv REQUIRED)
|
||||
find_package(resource_file QUIET)
|
||||
|
||||
|
||||
@@ -109,8 +110,8 @@ add_executable(newserv
|
||||
src/Version.cc
|
||||
src/WordSelectTable.cc
|
||||
)
|
||||
target_include_directories(newserv PUBLIC ${LIBEVENT_INCLUDE_DIR})
|
||||
target_link_libraries(newserv phosg ${LIBEVENT_LIBRARIES} pthread)
|
||||
target_include_directories(newserv PUBLIC ${LIBEVENT_INCLUDE_DIR} ${Iconv_INCLUDE_DIRS})
|
||||
target_link_libraries(newserv phosg ${LIBEVENT_LIBRARIES} ${Iconv_LIBRARIES} pthread)
|
||||
|
||||
if(resource_file_FOUND)
|
||||
target_compile_definitions(newserv PUBLIC HAVE_RESOURCE_FILE)
|
||||
|
||||
@@ -88,8 +88,8 @@ There is a fairly recent macOS ARM64 release on the newserv GitHub repository. Y
|
||||
There is a fairly recent Windows release on the newserv GitHub repository also. It's built with Cygwin, and all the necessary DLL files should be included. That said, I've only tested it on my own machine and there is no CI for Windows builds like there is for macOS and Linux, so if it doesn't work for you, please open a GitHub issue to let me know.
|
||||
|
||||
If you're not using a release from the GitHub repository, do this to build newserv:
|
||||
1. If you're on Windows, install Cygwin. While doing so, install the `cmake`, `gcc-core`, `gcc-g++`, `git`, `libevent2.1_7`, `make`, and `zlib` packages. Do the rest of these steps inside a Cygwin shell (not a Windows cmd shell or PowerShell).
|
||||
2. Make sure you have CMake and libevent installed. (On macOS, `brew install cmake libevent`; on most Linuxes, `sudo apt-get install cmake libevent-dev`; on Windows, you already did this in step 1.)
|
||||
1. If you're on Windows, install Cygwin. While doing so, install the `cmake`, `gcc-core`, `gcc-g++`, `git`, `libevent2.1_7`, `make`, `libiconv`, and `zlib` packages. Do the rest of these steps inside a Cygwin shell (not a Windows cmd shell or PowerShell).
|
||||
2. Make sure you have CMake, libevent, and libiconv installed. (On macOS, `brew install cmake libevent libiconv`; on most Linuxes, `sudo apt-get install cmake libevent-dev`; on Windows, you already did this in step 1.)
|
||||
3. Build and install phosg (https://github.com/fuzziqersoftware/phosg).
|
||||
4. Optionally, install resource_dasm (https://github.com/fuzziqersoftware/resource_dasm). This will enable newserv to send memory patches and load DOL files on PSO GC clients. PSO GC clients can play PSO normally on newserv without this.
|
||||
5. Run `cmake . && make` in the newserv directory.
|
||||
@@ -395,7 +395,6 @@ newserv has many CLI options, which can be used to access functionality other th
|
||||
* Convert a PSO GC or Episode 3 snapshot file to a BMP image (`decode-gci-snapshot`)
|
||||
* Find the likely round1 or round2 seed for a corrupt save file (`salvage-gci`)
|
||||
* Run a brute-force search for a decryption seed (`find-decryption-seed`)
|
||||
* Decode Shift-JIS text to UTF-16 (`decode-sjis`)
|
||||
* Convert quests in .gci, .vms, .dlq, or .qst format to .bin/.dat format (`decode-gci`, `decode-vms`, `decode-dlq`, `decode-qst`)
|
||||
* Convert quests in .bin/.dat to .qst format (`encode-qst`)
|
||||
* Convert text archives (e.g. TextEnglish.pr2) to JSON and vice versa (`decode-text-archive`, `encode-text-archive`)
|
||||
|
||||
+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);
|
||||
|
||||
@@ -660,7 +660,7 @@ I 40992 2023-05-26 10:53:41 - [Commands] Sending to C-2 (Tali) (version=DC comma
|
||||
I 40992 2023-05-26 10:53:42 - [Commands] Received from C-2 (Tali) (version=DC command=8A flag=00)
|
||||
0000 | 8A 00 04 00 |
|
||||
I 40992 2023-05-26 10:53:42 - [Commands] Sending to C-2 (Tali) (version=DC command=8A flag=00)
|
||||
0000 | 8A 00 0C 00 09 45 41 41 41 41 00 00 | EAAAA
|
||||
0000 | 8A 00 0C 00 41 41 41 41 00 00 00 00 | AAAA
|
||||
I 40992 2023-05-26 10:53:53 - [Commands] Received from C-2 (Tali) (version=DC command=60 flag=00)
|
||||
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 C0 00 00 00 00 | ` ?
|
||||
0010 | CE FE 64 43 00 00 00 00 B8 FF 7D 43 | dC }C
|
||||
|
||||
@@ -9431,7 +9431,7 @@ I 17097 2023-09-19 21:53:58 - [Commands] Received from C-4 (Tali) (version=GC co
|
||||
I 17097 2023-09-19 21:53:58 - [Commands] Sending to C-4 (Tali) (version=GC command=08 flag=01)
|
||||
0000 | 08 01 3C 00 44 00 00 44 00 00 00 00 00 00 41 6C | < D D Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 04 | exandria
|
||||
0020 | 44 00 00 44 15 00 00 00 0A 01 09 45 31 31 31 31 | D D E1111
|
||||
0020 | 44 00 00 44 15 00 00 00 0A 01 31 31 31 31 00 00 | D D 1111
|
||||
0030 | 00 00 00 00 00 00 00 00 00 00 00 04 |
|
||||
I 17097 2023-09-19 21:54:07 - [Commands] Received from C-4 (Tali) (version=GC command=E7 flag=00)
|
||||
0000 | E7 00 30 00 44 00 00 44 15 00 00 00 09 4A 35 36 | 0 D D J56
|
||||
@@ -9466,9 +9466,9 @@ I 17097 2023-09-19 21:54:07 - [Commands] Sending to C-2 (Tali) (version=GC comma
|
||||
0110 | 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:54:07 - [Commands] Sending to C-4 (Tali) (version=GC command=C9 flag=00)
|
||||
0000 | C9 00 14 01 B4 44 00 00 52 00 00 00 01 00 01 00 | D R
|
||||
0010 | 00 00 1D 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt
|
||||
0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 09 45 31 31 31 | le in game E111
|
||||
0030 | 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1
|
||||
0010 | 00 00 1B 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt
|
||||
0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 31 31 31 31 00 | le in game 1111
|
||||
0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -9806,7 +9806,7 @@ I 17097 2023-09-19 21:54:07 - [Commands] Sending to C-4 (Tali) (version=GC comma
|
||||
13F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
1400 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
1410 | 00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF |
|
||||
1420 | 00 00 00 00 09 4A 35 36 37 38 00 00 00 00 00 00 | J5678
|
||||
1420 | 00 00 00 00 35 36 37 38 00 00 00 00 00 00 00 00 | 5678
|
||||
1430 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
1440 | 00 00 00 00 00 00 01 00 22 22 22 22 7F 00 00 01 | """"
|
||||
1450 | 04 00 00 00 54 61 6C 69 00 00 00 00 00 00 00 00 | Tali
|
||||
@@ -10445,9 +10445,9 @@ I 17097 2023-09-19 21:54:57 - [Commands] Sending to C-2 (Tali) (version=GC comma
|
||||
0110 | 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:54:57 - [Commands] Sending to C-4 (Tali) (version=GC command=C9 flag=00)
|
||||
0000 | C9 00 14 01 B4 44 00 00 52 00 00 00 01 00 01 00 | D R
|
||||
0010 | 00 00 1D 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt
|
||||
0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 09 45 31 31 31 | le in game E111
|
||||
0030 | 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1
|
||||
0010 | 00 00 1B 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt
|
||||
0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 31 31 31 31 00 | le in game 1111
|
||||
0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -10680,9 +10680,9 @@ I 17097 2023-09-19 21:54:57 - [Commands] Sending to C-2 (Tali) (version=GC comma
|
||||
0110 | 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:54:57 - [Commands] Sending to C-4 (Tali) (version=GC command=C9 flag=00)
|
||||
0000 | C9 00 14 01 B4 44 00 00 52 00 00 00 01 00 01 00 | D R
|
||||
0010 | 00 00 1D 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt
|
||||
0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 09 45 31 31 31 | le in game E111
|
||||
0030 | 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1
|
||||
0010 | 00 00 1B 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt
|
||||
0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 31 31 31 31 00 | le in game 1111
|
||||
0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -11735,9 +11735,9 @@ I 17097 2023-09-19 21:54:58 - [Commands] Sending to C-2 (Tali) (version=GC comma
|
||||
0110 | 00 00 00 00 |
|
||||
I 17097 2023-09-19 21:54:58 - [Commands] Sending to C-4 (Tali) (version=GC command=C9 flag=00)
|
||||
0000 | C9 00 14 01 B4 44 00 00 52 00 00 00 01 00 01 00 | D R
|
||||
0010 | 00 00 1D 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt
|
||||
0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 09 45 31 31 31 | le in game E111
|
||||
0030 | 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 1
|
||||
0010 | 00 00 1B 00 56 69 65 77 69 6E 67 20 62 61 74 74 | Viewing batt
|
||||
0020 | 6C 65 20 69 6E 20 67 61 6D 65 20 31 31 31 31 00 | le in game 1111
|
||||
0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -14390,7 +14390,7 @@ I 17097 2023-09-19 21:56:42 - [Commands] Received from C-2 (Tali) (version=GC co
|
||||
0010 | 31 31 31 00 00 00 00 00 | 111
|
||||
I 17097 2023-09-19 21:56:42 - [Commands] Sending to C-2 (Tali) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 1C 00 00 00 00 00 11 11 11 11 54 61 6C 69 | Tali
|
||||
0010 | 09 4E 09 4A 31 31 31 31 00 00 00 00 | N J1111
|
||||
0010 | 09 4E 09 45 31 31 31 31 00 00 00 00 | N E1111
|
||||
I 17097 2023-09-19 21:56:42 - [Commands] Sending to C-4 (Tali) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 1C 00 00 00 00 00 11 11 11 11 54 61 6C 69 | Tali
|
||||
0010 | 09 4E 09 4A 31 31 31 31 00 00 00 00 | N J1111
|
||||
@@ -34116,7 +34116,7 @@ I 17097 2023-09-19 22:01:39 - [Commands] Received from C-2 (Tali) (version=GC co
|
||||
0010 | 32 33 34 35 36 37 38 39 30 2E 2C 27 22 00 00 00 | 234567890.,'"
|
||||
I 17097 2023-09-19 22:01:39 - [Commands] Sending to C-2 (Tali) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 24 00 00 00 00 00 11 11 11 11 54 61 6C 69 | $ Tali
|
||||
0010 | 09 4E 09 4A 31 32 33 34 35 36 37 38 39 30 2E 2C | N J1234567890.,
|
||||
0010 | 09 4E 09 45 31 32 33 34 35 36 37 38 39 30 2E 2C | N E1234567890.,
|
||||
0020 | 27 22 00 00 | '"
|
||||
I 17097 2023-09-19 22:01:39 - [Commands] Sending to C-4 (Tali) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 24 00 00 00 00 00 11 11 11 11 54 61 6C 69 | $ Tali
|
||||
@@ -35546,7 +35546,7 @@ I 17097 2023-09-19 22:01:52 - [Commands] Received from C-2 (Tali) (version=GC co
|
||||
0010 | 23 23 23 23 23 23 23 23 00 00 00 00 | ########
|
||||
I 17097 2023-09-19 22:01:52 - [Commands] Sending to C-2 (Tali) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 20 00 00 00 00 00 11 11 11 11 54 61 6C 69 | Tali
|
||||
0010 | 09 4E 09 4A 23 23 23 23 23 23 23 23 23 00 00 00 | N J#########
|
||||
0010 | 09 4E 09 45 23 23 23 23 23 23 23 23 23 00 00 00 | N E#########
|
||||
I 17097 2023-09-19 22:01:52 - [Commands] Sending to C-4 (Tali) (version=GC command=06 flag=00)
|
||||
0000 | 06 00 20 00 00 00 00 00 11 11 11 11 54 61 6C 69 | Tali
|
||||
0010 | 09 4E 09 4A 23 23 23 23 23 23 23 23 23 00 00 00 | N J#########
|
||||
|
||||
@@ -657,7 +657,7 @@ I 49108 2023-05-26 16:18:32 - [Commands] Sending to C-2 (Jess) (version=GC comma
|
||||
I 49108 2023-05-26 16:18:33 - [Commands] Received from C-2 (Jess) (version=GC command=8A flag=00)
|
||||
0000 | 8A 00 04 00 |
|
||||
I 49108 2023-05-26 16:18:33 - [Commands] Sending to C-2 (Jess) (version=GC command=8A flag=00)
|
||||
0000 | 8A 00 0C 00 09 45 31 31 31 31 00 00 | E1111
|
||||
0000 | 8A 00 0C 00 31 31 31 31 00 00 00 00 | 1111
|
||||
I 49108 2023-05-26 16:18:34 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00)
|
||||
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 C0 0F 00 00 00 | ` ?
|
||||
0010 | CE FE 64 43 00 00 00 00 B8 FF 7D 43 | dC }C
|
||||
@@ -10015,11 +10015,11 @@ I 49108 2023-05-26 16:28:20 - [Commands] Received from C-2 (Jess) (version=GC co
|
||||
0000 | D8 00 04 00 |
|
||||
I 49108 2023-05-26 16:28:20 - [Commands] Sending to C-2 (Jess) (version=GC command=D8 flag=01)
|
||||
0000 | D8 01 C0 00 4A 65 73 73 00 00 00 00 00 00 00 00 | Jess
|
||||
0010 | 00 00 00 00 09 45 09 43 30 30 20 09 43 31 31 20 | E C00 C11
|
||||
0020 | 09 43 32 32 20 09 43 33 33 20 09 43 34 34 20 09 | C22 C33 C44
|
||||
0030 | 43 35 35 0A 09 43 36 36 20 09 43 37 37 20 09 43 | C55 C66 C77 C
|
||||
0040 | 38 38 20 09 43 39 39 20 09 43 47 47 20 09 43 61 | 88 C99 CGG Ca
|
||||
0050 | 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | a
|
||||
0010 | 00 00 00 00 09 43 30 30 20 09 43 31 31 20 09 43 | C00 C11 C
|
||||
0020 | 32 32 20 09 43 33 33 20 09 43 34 34 20 09 43 35 | 22 C33 C44 C5
|
||||
0030 | 35 0A 09 43 36 36 20 09 43 37 37 20 09 43 38 38 | 5 C66 C77 C88
|
||||
0040 | 20 09 43 39 39 20 09 43 47 47 20 09 43 61 61 00 | C99 CGG Caa
|
||||
0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
|
||||
@@ -752,7 +752,7 @@ I 49484 2023-05-26 16:36:40 - [Commands] Received from C-3 (Tali) (version=PC co
|
||||
0010 | 20 00 00 00 |
|
||||
I 49484 2023-05-26 16:36:40 - [Commands] Sending to C-3 (Tali) (version=PC command=06 flag=00)
|
||||
0000 | 20 00 06 00 00 00 00 00 11 11 11 11 54 00 61 00 | T a
|
||||
0010 | 6C 00 69 00 09 00 09 00 4A 00 20 00 00 00 00 00 | l i J
|
||||
0010 | 6C 00 69 00 09 00 09 00 45 00 20 00 00 00 00 00 | l i E
|
||||
I 49484 2023-05-26 16:36:40 - [Commands] Received from C-3 (Tali) (version=PC command=62 flag=00)
|
||||
0000 | 10 00 62 00 5A 03 00 00 05 00 01 00 01 00 00 00 | b Z
|
||||
I 49484 2023-05-26 16:36:40 - [Commands] Sending to C-3 (Tali) (version=PC command=62 flag=00)
|
||||
|
||||
Reference in New Issue
Block a user