centralize command formats; fix a few range bugs

This commit is contained in:
Martin Michelsen
2022-03-30 23:28:14 -07:00
parent db099ed2dd
commit 7dce8b6c2c
19 changed files with 2226 additions and 1937 deletions
-1
View File
@@ -30,7 +30,6 @@ Current known issues / missing features:
- The trade window isn't implemented yet.
- PSO PC and PSOBB are essentially entirely untested. Only GC is fairly well-tested.
- Add all the chat commands that khyller used to have. (Most, but not all, currently exist in newserv.)
- The command structures are defined in multiple places. Centralize them.
## Usage
+16 -2
View File
@@ -524,6 +524,19 @@ static void command_lobby_type(shared_ptr<ServerState>, shared_ptr<Lobby> l,
////////////////////////////////////////////////////////////////////////////////
// Game commands
static void command_secid(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const char16_t* args) {
check_is_game(l, false);
if (!args[0]) {
c->override_section_id = -1;
send_text_message(l, u"$C6Override section ID\nremoved");
} else {
c->override_section_id = section_id_for_name(args);
send_text_message(l, u"$C6Override section ID\nset");
}
}
static void command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const char16_t* args) {
check_is_game(l, true);
@@ -534,7 +547,7 @@ static void command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l,
send_text_message(l, u"$C6Game unlocked");
} else {
char16cpy(l->password, args, 0x10);
strncpy_t(l->password, args, countof(l->password));
auto encoded = encode_sjis(l->password);
send_text_message_printf(l, "$C6Game password:\n%s",
encoded.c_str());
@@ -578,7 +591,7 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
check_version(c, GameVersion::BB);
string encoded_args = encode_sjis(args);
vector<string> tokens = split(encoded_args, L' ');
vector<string> tokens = split(encoded_args, ' ');
if (tokens.size() < 3) {
send_text_message(c, u"$C6Not enough arguments");
@@ -909,6 +922,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
{u"maxlevel" , {command_max_level , u"Usage:\nmax_level <level>"}},
{u"minlevel" , {command_min_level , u"Usage:\nmin_level <level>"}},
{u"password" , {command_password , u"Usage:\nlock [password]\nomit password to\nunlock game"}},
{u"secid" , {command_secid , u"Usage:\nsecid [section ID]\nomit section ID to\nrevert to normal"}},
{u"silence" , {command_silence , u"Usage:\nsilence <name-or-number>"}},
{u"song" , {command_song , u"Usage:\nsong <song-number>"}},
{u"type" , {command_lobby_type , u"Usage:\ntype <name>"}},
+1
View File
@@ -38,6 +38,7 @@ Client::Client(
lobby_client_id(0),
lobby_arrow_color(0),
next_exp_value(0),
override_section_id(-1),
infinite_hp(false),
infinite_tp(false),
can_chat(true) {
+4 -3
View File
@@ -61,20 +61,21 @@ struct Client {
uint32_t proxy_destination_address;
uint16_t proxy_destination_port;
// timing & menus
// Timing & menus
uint64_t play_time_begin; // time of connection (used for incrementing play time on BB)
uint64_t last_recv_time; // time of last data received
uint64_t last_send_time; // time of last data sent
// lobby/positioning
// Lobby/positioning
uint32_t area; // which area is the client in?
uint32_t lobby_id; // which lobby is this person in?
uint8_t lobby_client_id; // which client number is this person?
uint8_t lobby_arrow_color; // lobby arrow color ID
Player player;
// miscellaneous (used by chat commands)
// Miscellaneous (used by chat commands)
uint32_t next_exp_value; // next EXP value to give
int16_t override_section_id; // valid if >= 0
bool infinite_hp; // cheats enabled
bool infinite_tp; // cheats enabled
bool can_chat;
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -152,7 +152,7 @@ shared_ptr<Client> Lobby::find_client(const char16_t* identifier,
(this->clients[x]->license->serial_number == serial_number)) {
return this->clients[x];
}
if (identifier && !char16cmp(this->clients[x]->player.disp.name, identifier, 0x10)) {
if (identifier && !char16ncmp(this->clients[x]->player.disp.name, identifier, 0x10)) {
return this->clients[x];
}
}
+42 -63
View File
@@ -16,11 +16,29 @@ using namespace std;
// originally there was going to be a language-based header, but then I decided against it.
// these strings were already in use for that parser, so I didn't bother changing them.
#define PLAYER_FILE_SIGNATURE "newserv player file format; 10 sections present; sequential;"
#define ACCOUNT_FILE_SIGNATURE "newserv account file format; 7 sections present; sequential;"
#define PLAYER_FILE_SIGNATURE "newserv player file format; 10 sections present; sequential;"
#define ACCOUNT_FILE_SIGNATURE "newserv account file format; 7 sections present; sequential;"
void PlayerDispDataPCGC::enforce_pc_limits() {
// PC has fewer classes, so we'll substitute some here
if (this->char_class == 11) {
this->char_class = 0; // fomar -> humar
} else if (this->char_class == 10) {
this->char_class = 1; // ramarl -> hunewearl
} else if (this->char_class == 9) {
this->char_class = 5; // hucaseal -> racaseal
}
// if the player is still not a valid class, make them appear as the "ninja" NPC
if (this->char_class > 8) {
this->extra_model = 0;
this->v2_flags |= 2;
}
this->version = 2;
}
// converts PC/GC player data to BB format
PlayerDispDataBB PlayerDispDataPCGC::to_bb() const {
PlayerDispDataBB bb;
@@ -38,7 +56,7 @@ PlayerDispDataBB PlayerDispDataPCGC::to_bb() const {
bb.experience = this->experience;
bb.meseta = this->meseta;
memset(bb.guild_card, 0, sizeof(bb.guild_card));
strcpy(bb.guild_card, " 0");
strncpy(bb.guild_card, " 0", 0x10);
bb.unknown3[0] = this->unknown3[0];
bb.unknown3[1] = this->unknown3[1];
bb.name_color = this->name_color;
@@ -119,7 +137,7 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
pre.level = this->level;
pre.experience = this->experience;
memset(pre.guild_card, 0, sizeof(pre.guild_card));
strcpy(pre.guild_card, this->guild_card);
strncpy(pre.guild_card, this->guild_card, 0x10);
pre.unknown3[0] = this->unknown3[0];
pre.unknown3[1] = this->unknown3[1];
pre.name_color = this->name_color;
@@ -142,7 +160,7 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
pre.proportion_x = this->proportion_x;
pre.proportion_y = this->proportion_y;
memset(pre.name, 0, sizeof(pre.name));
char16cpy(pre.name, this->name, 16);
char16ncpy(pre.name, this->name, 16);
pre.play_time = this->play_time;
return pre;
}
@@ -151,7 +169,7 @@ void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
this->level = pre.level;
this->experience = pre.experience;
memset(this->guild_card, 0, sizeof(this->guild_card));
strcpy(this->guild_card, pre.guild_card);
strncpy(this->guild_card, pre.guild_card, 0x10);
this->unknown3[0] = pre.unknown3[0];
this->unknown3[1] = pre.unknown3[1];
this->name_color = pre.name_color;
@@ -174,7 +192,7 @@ void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
this->proportion_x = pre.proportion_x;
this->proportion_y = pre.proportion_y;
memset(this->name, 0, sizeof(this->name));
char16cpy(this->name, pre.name, 16);
char16ncpy(this->name, pre.name, 0x10);
this->play_time = 0;
}
@@ -226,55 +244,16 @@ void Player::import(const PSOPlayerDataBB& bb) {
// note: we don't copy the inventory and disp here because we already have
// it (we sent the player data to the client in the first place)
memset(this->info_board, 0, sizeof(this->info_board));
char16cpy(this->info_board, bb.info_board, 0xAC);
char16ncpy(this->info_board, bb.info_board, 0xAC);
memcpy(&this->blocked, bb.blocked, sizeof(uint32_t) * 30);
memset(this->auto_reply, 0, sizeof(this->auto_reply));
if (bb.auto_reply_enabled) {
char16cpy(this->auto_reply, bb.auto_reply, 0xAC);
char16ncpy(this->auto_reply, bb.auto_reply, 0xAC);
} else {
this->auto_reply[0] = 0;
}
}
// generates data for 65/67/68 commands (joining games/lobbies)
PlayerLobbyJoinDataPCGC Player::export_lobby_data_pc() const {
PlayerLobbyJoinDataPCGC pc;
pc.inventory = this->inventory;
pc.disp = this->disp.to_pcgc();
// PC has fewer classes, so we'll substitute some here
if (pc.disp.char_class == 11) {
pc.disp.char_class = 0; // fomar -> humar
} else if (pc.disp.char_class == 10) {
pc.disp.char_class = 1; // ramarl -> hunewearl
} else if (pc.disp.char_class == 9) {
pc.disp.char_class = 5; // hucaseal -> racaseal
}
// if the player is still not a valid class, make them appear as the "ninja" NPC
if (pc.disp.char_class > 8) {
pc.disp.extra_model = 0;
pc.disp.v2_flags |= 2;
}
pc.disp.version = 2;
return pc;
}
PlayerLobbyJoinDataPCGC Player::export_lobby_data_gc() const {
PlayerLobbyJoinDataPCGC gc;
gc.inventory = this->inventory;
gc.disp = this->disp.to_pcgc();
return gc;
}
PlayerLobbyJoinDataBB Player::export_lobby_data_bb() const {
PlayerLobbyJoinDataBB bb;
bb.inventory = this->inventory;
bb.disp = this->disp;
return bb;
}
PlayerBB Player::export_bb_player_data() const {
PlayerBB bb;
bb.inventory = this->inventory;
@@ -285,11 +264,11 @@ PlayerBB Player::export_bb_player_data() const {
bb.bank = this->bank;
bb.serial_number = this->serial_number;
memset(bb.name, 0, sizeof(bb.name));
char16cpy(bb.name, this->disp.name, 24);
char16ncpy(bb.name, this->disp.name, 24);
memset(bb.team_name, 0, sizeof(bb.team_name));
char16cpy(bb.team_name, this->team_name, 16);
char16ncpy(bb.team_name, this->team_name, 16);
memset(bb.guild_card_desc, 0, sizeof(bb.guild_card_desc));
char16cpy(bb.guild_card_desc, this->guild_card_desc, 0x58);
char16ncpy(bb.guild_card_desc, this->guild_card_desc, 0x58);
bb.reserved1 = 0;
bb.reserved2 = 0;
bb.section_id = this->disp.section_id;
@@ -298,9 +277,9 @@ PlayerBB Player::export_bb_player_data() const {
memcpy(bb.symbol_chats, this->symbol_chats, 0x04E0);
memcpy(bb.shortcuts, this->shortcuts, 0x0A40);
memset(bb.auto_reply, 0, sizeof(bb.auto_reply));
char16cpy(bb.auto_reply, this->auto_reply, 0xAC);
char16ncpy(bb.auto_reply, this->auto_reply, 0xAC);
memset(bb.info_board, 0, sizeof(bb.info_board));
char16cpy(bb.info_board, this->info_board, 0xAC);
char16ncpy(bb.info_board, this->info_board, 0xAC);
memset(bb.unknown5, 0, 0x1C);
memcpy(bb.challenge_data, this->challenge_data, 0x0140);
memcpy(bb.tech_menu_config, this->tech_menu_config, 0x0028);
@@ -344,13 +323,13 @@ void Player::load_account_data(const string& filename) {
memcpy(&this->shortcuts, &account.shortcuts, 0x0A40);
memcpy(&this->symbol_chats, &account.symbol_chats, 0x04E0);
memset(this->team_name, 0, sizeof(this->team_name));
char16cpy(this->team_name, account.team_name, 16);
char16ncpy(this->team_name, account.team_name, 16);
}
void Player::save_account_data(const string& filename) const {
SavedAccountBB account;
strcpy(account.signature, ACCOUNT_FILE_SIGNATURE);
strncpy(account.signature, ACCOUNT_FILE_SIGNATURE, sizeof(account.signature));
memcpy(&account.blocked, &this->blocked, sizeof(uint32_t) * 30);
account.guild_cards = this->guild_cards;
account.key_config = this->key_config;
@@ -358,7 +337,7 @@ void Player::save_account_data(const string& filename) const {
memcpy(&account.shortcuts, &this->shortcuts, 0x0A40);
memcpy(&account.symbol_chats, &this->symbol_chats, 0x04E0);
memset(account.team_name, 0, sizeof(account.team_name));
char16cpy(account.team_name, this->team_name, 16);
char16ncpy(account.team_name, this->team_name, 16);
save_file(filename, &account, sizeof(account));
}
@@ -371,14 +350,14 @@ void Player::load_player_data(const string& filename) {
}
memset(this->auto_reply, 0, sizeof(this->auto_reply));
char16cpy(this->auto_reply, player.auto_reply, 0xAC);
char16ncpy(this->auto_reply, player.auto_reply, 0xAC);
this->bank = player.bank;
memcpy(&this->challenge_data, &player.challenge_data, 0x0140);
this->disp = player.disp;
memset(this->guild_card_desc, 0, sizeof(this->guild_card_desc));
char16cpy(this->guild_card_desc, player.guild_card_desc, 0x58);
char16ncpy(this->guild_card_desc, player.guild_card_desc, 0x58);
memset(this->info_board, 0, sizeof(this->info_board));
char16cpy(this->info_board, player.info_board, 0xAC);
char16ncpy(this->info_board, player.info_board, 0xAC);
this->inventory = player.inventory;
memcpy(&this->quest_data1, &player.quest_data1, 0x0208);
memcpy(&this->quest_data2, &player.quest_data2, 0x0058);
@@ -388,17 +367,17 @@ void Player::load_player_data(const string& filename) {
void Player::save_player_data(const string& filename) const {
SavedPlayerBB player;
strcpy(player.signature, PLAYER_FILE_SIGNATURE);
strncpy(player.signature, PLAYER_FILE_SIGNATURE, sizeof(player.signature));
player.preview = this->disp.to_preview();
memset(player.auto_reply, 0, sizeof(player.auto_reply));
char16cpy(player.auto_reply, this->auto_reply, 0xAC);
char16ncpy(player.auto_reply, this->auto_reply, 0xAC);
player.bank = this->bank;
memcpy(&player.challenge_data, &this->challenge_data, 0x0140);
player.disp = this->disp;
memset(player.guild_card_desc, 0, sizeof(player.guild_card_desc));
char16cpy(player.guild_card_desc,this->guild_card_desc, 0x58);
char16ncpy(player.guild_card_desc,this->guild_card_desc, 0x58);
memset(player.info_board, 0, sizeof(player.info_board));
char16cpy(player.info_board, this->info_board, 0xAC);
char16ncpy(player.info_board, this->info_board, 0xAC);
player.inventory = this->inventory;
memcpy(&player.quest_data1, &this->quest_data1, 0x0208);
memcpy(&player.quest_data2, &this->quest_data2, 0x0058);
+37 -17
View File
@@ -123,6 +123,7 @@ struct PlayerDispDataPCGC { // 0xD0 in size
uint8_t config[0x48];
uint8_t technique_levels[0x14];
void enforce_pc_limits();
PlayerDispDataBB to_bb() const;
} __attribute__((packed));
@@ -163,7 +164,7 @@ struct PlayerDispDataBB {
uint32_t level;
uint32_t experience;
uint32_t meseta;
char guild_card[16];
char guild_card[0x10];
uint32_t unknown3[2];
uint32_t name_color;
uint8_t extra_model;
@@ -189,6 +190,7 @@ struct PlayerDispDataBB {
uint8_t config[0xE8];
uint8_t technique_levels[0x14];
inline void enforce_pc_limits() { }
PlayerDispDataPCGC to_pcgc() const;
PlayerDispDataBBPreview to_preview() const;
void apply_preview(const PlayerDispDataBBPreview&);
@@ -277,7 +279,8 @@ struct PlayerLobbyDataGC {
struct PlayerLobbyDataBB {
uint32_t player_tag;
uint32_t guild_card;
uint32_t unknown1[5];
be_uint32_t ip_address; // Guess - the official builds didn't use this, but all other versions have it
uint32_t unknown1[4];
uint32_t client_id;
char16_t name[16];
uint32_t unknown2;
@@ -310,18 +313,6 @@ struct PSOPlayerDataBB { // for command 0x61
char16_t auto_reply[0];
} __attribute__((packed));
// PC/GC lobby player data (used in lobby/game join commands)
struct PlayerLobbyJoinDataPCGC {
PlayerInventory inventory;
PlayerDispDataPCGC disp;
} __attribute__((packed));
// BB lobby player data (used in lobby/game join commands)
struct PlayerLobbyJoinDataBB {
PlayerInventory inventory;
PlayerDispDataBB disp;
} __attribute__((packed));
// complete BB player data format (used in E7 command)
struct PlayerBB {
PlayerInventory inventory; // 0000 // player
@@ -416,9 +407,6 @@ struct Player {
void import(const PSOPlayerDataPC& pd);
void import(const PSOPlayerDataGC& pd);
void import(const PSOPlayerDataBB& pd);
PlayerLobbyJoinDataPCGC export_lobby_data_pc() const;
PlayerLobbyJoinDataPCGC export_lobby_data_gc() const;
PlayerLobbyJoinDataBB export_lobby_data_bb() const;
PlayerBB export_bb_player_data() const;
void add_item(const PlayerInventoryItem& item);
@@ -434,3 +422,35 @@ std::string filename_for_player_bb(const std::string& username, uint8_t player_i
std::string filename_for_bank_bb(const std::string& username, const char* bank_name);
std::string filename_for_class_template_bb(uint8_t char_class);
std::string filename_for_account_bb(const std::string& username);
template <typename DestT, typename SrcT = DestT>
DestT convert_player_disp_data(const SrcT&) {
static_assert(always_false<DestT, SrcT>::v,
"unspecialized strcpy_t should never be called");
}
template <>
inline PlayerDispDataPCGC convert_player_disp_data<PlayerDispDataPCGC>(
const PlayerDispDataPCGC& src) {
return src;
}
template <>
inline PlayerDispDataPCGC convert_player_disp_data<PlayerDispDataPCGC, PlayerDispDataBB>(
const PlayerDispDataBB& src) {
return src.to_pcgc();
}
template <>
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB, PlayerDispDataPCGC>(
const PlayerDispDataPCGC& src) {
return src.to_bb();
}
template <>
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB>(
const PlayerDispDataBB& src) {
return src;
}
+36 -80
View File
@@ -137,9 +137,11 @@ void ProxyServer::on_client_connect(
case GameVersion::GC: {
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
string data = prepare_server_init_contents_dc_pc_gc(false, server_key, client_key);
send_command(session->bev.get(), session->version, session->crypt_out.get(), 0x02,
0, data.data(), data.size(), "unlinked proxy client");
auto cmd = prepare_server_init_contents_dc_pc_gc(
false, server_key, client_key);
send_command(session->bev.get(), session->version,
session->crypt_out.get(), 0x02, 0, &cmd, sizeof(cmd),
"unlinked proxy client");
session->crypt_out.reset(new PSOGCEncryption(server_key));
session->crypt_in.reset(new PSOGCEncryption(client_key));
break;
@@ -191,11 +193,11 @@ void ProxyServer::UnlinkedSession::on_client_input() {
if (command != 0x9E) {
log(ERROR, "[ProxyServer] Received unexpected command %02hX", command);
should_close_unlinked_session = true;
} else if (data.size() < sizeof(LoginCommand_GC_9E) - 0x64) {
} else if (data.size() < sizeof(C_Login_PC_GC_9D_9E) - 0x64) {
log(ERROR, "[ProxyServer] Login command is too small");
should_close_unlinked_session = true;
} else {
const auto* cmd = reinterpret_cast<const LoginCommand_GC_9E*>(data.data());
const auto* cmd = reinterpret_cast<const C_Login_PC_GC_9D_9E*>(data.data());
uint32_t serial_number = strtoul(cmd->serial_number, nullptr, 16);
try {
license = this->server->state->license_manager->verify_gc(
@@ -234,9 +236,6 @@ void ProxyServer::UnlinkedSession::on_client_input() {
log(ERROR, "[ProxyServer/%08" PRIX32 "] Client configuration is invalid; cannot open session",
license->serial_number);
} else {
// If the client goes back to newserv, we need to set the welcome
// message flag so the server will know what to do
client_config.flags |= ClientFlag::AT_WELCOME_MESSAGE;
session.reset(new LinkedSession(
this->server,
this->local_port,
@@ -506,11 +505,7 @@ void ProxyServer::LinkedSession::on_client_input() {
}
uint8_t leaving_id = x;
uint8_t leader_id = this->lobby_client_id;
struct {
uint8_t client_id;
uint8_t leader_id;
uint16_t unused;
} __attribute__((packed)) cmd = {leaving_id, leader_id, 0};
S_LeaveLobby_66_69 cmd = {leaving_id, leader_id, 0};
send_command(this->client_bev.get(), this->version,
this->client_output_crypt.get(), 0x69, leaving_id, &cmd,
sizeof(cmd), name.c_str());
@@ -519,11 +514,7 @@ void ProxyServer::LinkedSession::on_client_input() {
// Restore the newserv client config, so the client gets its newserv
// guild card number back and the login server knows e.g. not to show
// the welcome message (if the appropriate flag is set)
struct {
uint32_t player_tag;
uint32_t serial_number;
ClientConfig config;
} __attribute__((packed)) update_client_config_cmd = {
S_UpdateClientConfig_DC_PC_GC_04 update_client_config_cmd = {
0x00010000,
this->license->serial_number,
this->newserv_client_config,
@@ -538,7 +529,7 @@ void ProxyServer::LinkedSession::on_client_input() {
const auto& port_name = version_to_port_name.at(static_cast<size_t>(
this->version));
ReconnectCommand_19 reconnect_cmd = {
S_Reconnect_19 reconnect_cmd = {
0, this->server->state->named_port_configuration.at(port_name).port, 0};
// If the client is on a virtual connection, we can use any address
@@ -607,11 +598,11 @@ void ProxyServer::LinkedSession::on_server_input() {
}
// Most servers don't include after_message or have a shorter
// after_message than newserv does, so don't require it
if (data.size() < offsetof(ServerInitCommand_GC_02_17, after_message)) {
if (data.size() < offsetof(S_ServerInit_DC_GC_02_17, after_message)) {
throw std::runtime_error("init encryption command is too small");
}
const auto* cmd = reinterpret_cast<const ServerInitCommand_GC_02_17*>(
const auto* cmd = reinterpret_cast<const S_ServerInit_DC_GC_02_17*>(
data.data());
// This doesn't get forwarded to the client, so don't recreate the
@@ -632,7 +623,7 @@ void ProxyServer::LinkedSession::on_server_input() {
// We don't let the client do this because it believes it already
// did (when it was in an unlinked session).
if (command == 0x17) {
VerifyLicenseCommand_GC_DB cmd;
C_VerifyLicense_GC_DB cmd;
memset(&cmd, 0, sizeof(cmd));
snprintf(cmd.serial_number, sizeof(cmd.serial_number), "%08" PRIX32 "",
this->license->serial_number);
@@ -654,7 +645,7 @@ void ProxyServer::LinkedSession::on_server_input() {
case 0x9A: {
should_forward = false;
LoginCommand_GC_9E cmd;
C_Login_PC_GC_9D_9E cmd;
memset(&cmd, 0, sizeof(cmd));
if (this->guild_card_number == 0) {
@@ -684,25 +675,21 @@ void ProxyServer::LinkedSession::on_server_input() {
0x9E,
0x01,
&cmd,
this->guild_card_number ? (offsetof(LoginCommand_GC_9E, cfg) + 0x20) : sizeof(cmd),
this->guild_card_number ? (offsetof(C_Login_PC_GC_9D_9E, cfg) + 0x20) : sizeof(cmd),
name.c_str());
break;
}
case 0x04: {
struct Contents {
uint32_t player_tag;
uint32_t guild_card_number;
uint8_t client_config[0];
} __attribute__((packed));
if (data.size() < sizeof(Contents)) {
// Some servers send a short 04 command if they don't use all of the
// 0x20 bytes available. We should be prepared to handle that.
if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_GC_04, cfg)) {
throw std::runtime_error("set security data command is too small");
}
bool had_guild_card_number = (this->guild_card_number != 0);
const auto* cmd = reinterpret_cast<const Contents*>(data.data());
const auto* cmd = reinterpret_cast<const S_UpdateClientConfig_DC_PC_GC_04*>(data.data());
this->guild_card_number = cmd->guild_card_number;
log(INFO, "[ProxyServer/%08" PRIX32 "] Guild card number set to %" PRIX32,
this->license->serial_number, this->guild_card_number);
@@ -720,7 +707,8 @@ void ProxyServer::LinkedSession::on_server_input() {
? "t Lobby Server. Copyright SEGA E"
: "t Port Map. Copyright SEGA Enter",
0x20);
memcpy(this->remote_client_config_data, cmd->client_config, min<size_t>(data.size() - sizeof(Contents), 0x20));
memcpy(this->remote_client_config_data, &cmd->cfg,
min<size_t>(data.size() - sizeof(S_UpdateClientConfig_DC_PC_GC_04), 0x20));
// If the guild card number was not set, pretend (to the server)
// that this is the first 04 command the client has received. The
@@ -739,11 +727,11 @@ void ProxyServer::LinkedSession::on_server_input() {
}
case 0x19: {
if (data.size() < sizeof(ReconnectCommand_19)) {
if (data.size() < sizeof(S_Reconnect_19)) {
throw std::runtime_error("reconnect command is too small");
}
auto* args = reinterpret_cast<ReconnectCommand_19*>(data.data());
auto* args = reinterpret_cast<S_Reconnect_19*>(data.data());
memset(&this->next_destination, 0, sizeof(this->next_destination));
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(
&this->next_destination);
@@ -802,19 +790,12 @@ void ProxyServer::LinkedSession::on_server_input() {
bool is_download_quest = (command == 0xA6);
struct OpenFileCommand {
char name[0x20];
uint16_t unused;
uint16_t flags;
char filename[0x10];
uint32_t file_size;
};
if (data.size() < sizeof(OpenFileCommand)) {
if (data.size() < sizeof(S_OpenFile_PC_GC_44_A6)) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Open file command is too small; skipping file",
this->license->serial_number);
break;
}
const auto* cmd = reinterpret_cast<const OpenFileCommand*>(data.data());
const auto* cmd = reinterpret_cast<const S_OpenFile_PC_GC_44_A6*>(data.data());
string output_filename = string_printf("%s.%s.%" PRIu64,
cmd->filename, is_download_quest ? "download" : "online", now());
@@ -840,17 +821,12 @@ void ProxyServer::LinkedSession::on_server_input() {
break;
}
struct WriteFileCommand {
char filename[0x10];
uint8_t data[0x400];
uint32_t data_size;
};
if (data.size() < sizeof(WriteFileCommand)) {
if (data.size() < sizeof(S_WriteFile_13_A7)) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Write file command is too small",
this->license->serial_number);
break;
}
const auto* cmd = reinterpret_cast<const WriteFileCommand*>(data.data());
const auto* cmd = reinterpret_cast<const S_WriteFile_13_A7*>(data.data());
SavingFile* sf = nullptr;
try {
@@ -933,27 +909,12 @@ void ProxyServer::LinkedSession::on_server_input() {
case 0x65: // other player joined game
case 0x68: { // other player joined lobby
struct Command {
uint8_t client_id;
uint8_t leader_id;
uint8_t disable_udp;
uint8_t lobby_number;
uint16_t block_number;
uint16_t event;
uint32_t unused;
struct Entry {
PlayerLobbyDataGC lobby_data;
PlayerLobbyJoinDataPCGC data;
} __attribute__((packed));
Entry entries[0];
} __attribute__((packed));
size_t expected_size = sizeof(Command) + sizeof(Command::Entry) * flag;
size_t expected_size = offsetof(S_JoinLobby_GC_65_67_68, entries) + sizeof(S_JoinLobby_GC_65_67_68::Entry) * flag;
if (data.size() < expected_size) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)",
this->license->serial_number, expected_size, data.size());
} else {
const auto* cmd = reinterpret_cast<const Command*>(data.data());
const auto* cmd = reinterpret_cast<const S_JoinLobby_GC_65_67_68*>(data.data());
this->lobby_client_id = cmd->client_id;
@@ -964,7 +925,7 @@ void ProxyServer::LinkedSession::on_server_input() {
this->license->serial_number, index, x);
} else {
this->lobby_players[index].guild_card_number = cmd->entries[x].lobby_data.guild_card;
this->lobby_players[index].name = cmd->entries[x].data.disp.name;
this->lobby_players[index].name = cmd->entries[x].disp.name;
log(INFO, "[ProxyServer/%08" PRIX32 "] Added lobby player: (%zu) %" PRIu32 " %s",
this->license->serial_number, index,
this->lobby_players[index].guild_card_number,
@@ -982,20 +943,20 @@ void ProxyServer::LinkedSession::on_server_input() {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Cleared lobby players",
this->license->serial_number);
const size_t expected_size = offsetof(JoinGameCommand_GC_64, player);
const size_t ep3_expected_size = sizeof(JoinGameCommand_GC_64);
const size_t expected_size = offsetof(S_JoinGame_GC_64, players_ep3);
const size_t ep3_expected_size = sizeof(S_JoinGame_GC_64);
if (data.size() < expected_size) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Game join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)",
this->license->serial_number, expected_size, data.size());
} else {
const auto* cmd = reinterpret_cast<const JoinGameCommand_GC_64*>(data.data());
const auto* cmd = reinterpret_cast<const S_JoinGame_GC_64*>(data.data());
this->lobby_client_id = cmd->client_id;
for (size_t x = 0; x < flag; x++) {
this->lobby_players[x].guild_card_number = cmd->lobby_data[x].guild_card;
if (data.size() >= ep3_expected_size) {
this->lobby_players[x].name = cmd->player[x].disp.name;
this->lobby_players[x].name = cmd->players_ep3[x].disp.name;
} else {
this->lobby_players[x].name.clear();
}
@@ -1010,16 +971,11 @@ void ProxyServer::LinkedSession::on_server_input() {
case 0x66:
case 0x69: {
struct Command {
uint8_t client_id;
uint8_t leader_id;
uint16_t unused;
} __attribute__((packed));
if (data.size() < sizeof(Command)) {
if (data.size() < sizeof(S_LeaveLobby_66_69)) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby leave command is incorrect size",
this->license->serial_number);
} else {
const auto* cmd = reinterpret_cast<const Command*>(data.data());
const auto* cmd = reinterpret_cast<const S_LeaveLobby_66_69*>(data.data());
size_t index = cmd->client_id;
if (index >= this->lobby_players.size()) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby leave command references missing position",
+313 -468
View File
File diff suppressed because it is too large Load Diff
+1 -35
View File
@@ -6,42 +6,8 @@
// These commands' structures are defined here because they're used by both the
// game server and proxy server
struct VerifyLicenseCommand_GC_DB {
char unused[0x20];
char serial_number[0x10];
char access_key[0x10];
char unused2[0x08];
uint32_t sub_version;
char serial_number2[0x30];
char access_key2[0x30];
char password[0x30];
} __attribute__((packed));
struct LoginCommand_GC_9E {
uint32_t player_tag; // 00 00 01 00 if guild card is set (via 04)
uint32_t guild_card_number; // FF FF FF FF if not set
uint32_t unused1[2];
uint32_t sub_version;
uint8_t unused2[0x24]; // 00 01 00 00 ... (rest is 00)
char serial_number[0x10];
char access_key[0x10];
char serial_number2[0x30];
char access_key2[0x30];
char name[0x10];
// Note: there are 8 bytes at the end of cfg that are technically not
// included in the client config on GC, but the field after it is
// sufficiently large and unused anyway
ClientConfig cfg;
uint8_t unused4[0x5C];
} __attribute__((packed));
void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
void process_disconnect(std::shared_ptr<ServerState> s,
std::shared_ptr<Client> c);
void process_command(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
uint16_t command, uint32_t flag, uint16_t size, const void* data);
uint16_t command, uint32_t flag, const std::string& data);
+5 -5
View File
@@ -34,7 +34,7 @@ struct ItemSubcommand {
void check_size(uint16_t size, uint16_t min_size, uint16_t max_size) {
if (size < min_size) {
throw runtime_error(string_printf(
"command too small (expected at least 0x%hX bytes, got 0x%hX bytes)",
"command too small (expected at least 0x%hX bytes, received 0x%hX bytes)",
min_size, size));
}
if (max_size == 0) {
@@ -42,7 +42,7 @@ void check_size(uint16_t size, uint16_t min_size, uint16_t max_size) {
}
if (size > max_size) {
throw runtime_error(string_printf(
"command too large (expected at most 0x%hX bytes, got 0x%hX bytes)",
"command too large (expected at most 0x%hX bytes, received 0x%hX bytes)",
max_size, size));
}
}
@@ -928,9 +928,9 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 08 */ process_subcommand_unimplemented,
/* 09 */ process_subcommand_unimplemented,
/* 0A */ process_subcommand_monster_hit,
/* 0B */ process_subcommand_forward_check_size_game, // Box destroyed
/* 0C */ process_subcommand_forward_check_size_game,
/* 0D */ process_subcommand_forward_check_size,
/* 0B */ process_subcommand_forward_check_size_game,
/* 0C */ process_subcommand_forward_check_size_game, // Add condition (poison/slow/etc.)
/* 0D */ process_subcommand_forward_check_size_game, // Remove condition (poison/slow/etc.)
/* 0E */ process_subcommand_unimplemented,
/* 0F */ process_subcommand_unimplemented,
/* 10 */ process_subcommand_unimplemented,
+359 -1181
View File
File diff suppressed because it is too large Load Diff
+23 -51
View File
@@ -13,6 +13,7 @@
#include "Menu.hh"
#include "Quest.hh"
#include "Text.hh"
#include "CommandFormats.hh"
@@ -33,54 +34,41 @@ void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag = 0,
void send_command(std::shared_ptr<ServerState> s, uint16_t command,
uint32_t flag = 0, const void* data = nullptr, size_t size = 0);
template <typename TARGET, typename STRUCT>
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
const STRUCT& data) {
template <typename TargetT, typename StructT>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
const StructT& data) {
send_command(c, command, flag, &data, sizeof(data));
}
template <typename TARGET>
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
template <typename TargetT>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
const std::string& data) {
send_command(c, command, flag, data.data(), data.size());
}
template <typename TARGET, typename STRUCT>
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
const std::vector<STRUCT>& data) {
send_command(c, command, flag, data.data(), data.size() * sizeof(STRUCT));
template <typename TargetT, typename StructT>
void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
const std::vector<StructT>& data) {
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
}
template <typename TARGET, typename STRUCT, typename ENTRY>
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
const STRUCT& data, const std::vector<ENTRY>& array_data) {
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(STRUCT));
template <typename TargetT, typename StructT, typename EntryT>
void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
const StructT& data, const std::vector<EntryT>& array_data) {
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
all_data.append(reinterpret_cast<const char*>(array_data.data()),
array_data.size() * sizeof(ENTRY));
array_data.size() * sizeof(EntryT));
send_command(c, command, flag, all_data.data(), all_data.size());
}
struct ServerInitCommand_GC_02_17 {
char copyright[0x40];
uint32_t server_key;
uint32_t client_key;
char after_message[200];
} __attribute__((packed));
std::string prepare_server_init_contents_dc_pc_gc(
S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
bool initial_connection, uint32_t server_key, uint32_t client_key);
void send_server_init(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
bool initial_connection);
void send_update_client_config(std::shared_ptr<Client> c);
struct ReconnectCommand_19 {
be_uint32_t address;
uint16_t port;
uint16_t unused;
} __attribute__((packed));
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
void send_pc_gc_split_reconnect(std::shared_ptr<Client> c, uint32_t address,
uint16_t pc_port, uint16_t gc_port);
@@ -111,9 +99,9 @@ void send_chat_message(std::shared_ptr<Client> c, uint32_t from_serial_number,
void send_simple_mail(std::shared_ptr<Client> c, uint32_t from_serial_number,
const char16_t* from_name, const char16_t* text);
template <typename TARGET>
template <typename TargetT>
__attribute__((format(printf, 2, 3))) void send_text_message_printf(
std::shared_ptr<TARGET> t, const char* format, ...) {
std::shared_ptr<TargetT> t, const char* format, ...) {
va_list va;
va_start(va, format);
std::string buf = string_vprintf(format, va);
@@ -124,8 +112,11 @@ __attribute__((format(printf, 2, 3))) void send_text_message_printf(
void send_info_board(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
void send_card_search_result(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
std::shared_ptr<Client> result, std::shared_ptr<Lobby> result_lobby);
void send_card_search_result(
std::shared_ptr<ServerState> s,
std::shared_ptr<Client> c,
std::shared_ptr<Client> result,
std::shared_ptr<Lobby> result_lobby);
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
void send_menu(std::shared_ptr<Client> c, const char16_t* menu_name,
@@ -137,25 +128,6 @@ void send_quest_menu(std::shared_ptr<Client> c, uint32_t menu_id,
const std::vector<MenuItem>& items, bool is_download_menu);
void send_lobby_list(std::shared_ptr<Client> c, std::shared_ptr<ServerState> s);
struct JoinGameCommand_GC_64 {
uint32_t variations[0x20];
PlayerLobbyDataGC lobby_data[4];
uint8_t client_id;
uint8_t leader_id;
uint8_t disable_udp; // guess; putting 0 here causes no movement messages to be sent
uint8_t difficulty;
uint8_t battle_mode;
uint8_t event;
uint8_t section_id;
uint8_t challenge_mode;
uint32_t rare_seed;
uint32_t episode; // for PSOPC, this must be 0x00000100
struct {
PlayerInventory inventory;
PlayerDispDataPCGC disp;
} __attribute__((packed)) player[4]; // only used on ep3
} __attribute__((packed));
void send_join_lobby(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
void send_player_join_notification(std::shared_ptr<Client> c,
std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
+1 -1
View File
@@ -211,7 +211,7 @@ void Server::receive_and_process_commands(shared_ptr<Client> c) {
for_each_received_command(c->bev, c->version, c->crypt_in.get(),
[this, c](uint16_t command, uint16_t flag, const std::string& data) {
try {
process_command(this->state, c, command, flag, data.size(), data.data());
process_command(this->state, c, command, flag, data);
} catch (const exception& e) {
log(INFO, "[Server] Error in client stream: %s", e.what());
c->should_disconnect = true;
+30 -11
View File
@@ -22,37 +22,56 @@ ServerState::ServerState()
ep3_menu_song(-1) {
memset(&this->default_key_file, 0, sizeof(this->default_key_file));
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));
bool is_ep3_only = (x > 14);
shared_ptr<Lobby> l(new Lobby());
l->flags |= LobbyFlag::PUBLIC | LobbyFlag::DEFAULT | LobbyFlag::PERSISTENT |
((x > 14) ? LobbyFlag::EPISODE_3 : 0);
(is_ep3_only ? LobbyFlag::EPISODE_3 : 0);
l->block = x + 1;
l->type = x;
char16cpy(l->name, lobby_name.c_str(), 0x24);
char16ncpy(l->name, lobby_name.c_str(), 0x24);
l->max_clients = 12;
this->add_lobby(l);
if (!is_ep3_only) {
this->public_lobby_search_order.emplace_back(l);
} else {
ep3_only_lobbies.emplace_back(l);
}
}
this->public_lobby_search_order_ep3 = this->public_lobby_search_order;
this->public_lobby_search_order_ep3.insert(
this->public_lobby_search_order_ep3.begin(),
ep3_only_lobbies.begin(),
ep3_only_lobbies.end());
}
void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
auto it = this->id_to_lobby.lower_bound(0);
for (; it != this->id_to_lobby.end(); it++) {
if (!(it->second->flags & LobbyFlag::PUBLIC)) {
continue;
}
const auto& search_order = (c->flags & ClientFlag::EPISODE_3_GAMES)
? this->public_lobby_search_order_ep3
: this->public_lobby_search_order;
shared_ptr<Lobby> added_to_lobby;
for (const auto& l : search_order) {
try {
it->second->add_client(c);
l->add_client(c);
added_to_lobby = l;
break;
} catch (const out_of_range&) { }
}
if (it == this->id_to_lobby.end()) {
if (!added_to_lobby) {
// TODO: Add the user to a dynamically-created private lobby instead
throw out_of_range("all lobbies full");
}
// send a join message to the joining player, and notifications to all others
this->send_lobby_join_notifications(it->second, c);
// Send a join message to the joining player, and notifications to all others
this->send_lobby_join_notifications(added_to_lobby, c);
}
void ServerState::remove_client_from_lobby(shared_ptr<Client> c) {
+2
View File
@@ -59,6 +59,8 @@ struct ServerState {
std::u16string welcome_message;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order_ep3;
std::atomic<int32_t> next_lobby_id;
uint8_t pre_lobby_event;
int32_t ep3_menu_song;
+24 -13
View File
@@ -13,7 +13,7 @@ using namespace std;
int char16cmp(const char16_t* s1, const char16_t* s2, size_t count) {
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]) {
@@ -30,7 +30,7 @@ int char16cmp(const char16_t* s1, const char16_t* s2, size_t count) {
return 0;
}
void char16cpy(char16_t* dest, const char16_t* src, size_t count) {
void char16ncpy(char16_t* dest, const char16_t* src, size_t count) {
size_t x;
for (x = 0; x < count && src[x] != 0; x++) {
dest[x] = src[x];
@@ -85,6 +85,9 @@ static const vector<char16_t>& unicode_to_sjis_table() {
return unicode_to_sjis_table_data;
}
// TODO: It looks like these functions are probably wrong. Specifically, we
// don't write the high byte when encoding non-ASCII chars, do we?
void encode_sjis(char* dest, const char16_t* source, size_t max) {
const auto& table = unicode_to_sjis_table();
while (*source && (--max)) {
@@ -108,25 +111,30 @@ void decode_sjis(char16_t* dest, const char* source, size_t max) {
*dest = 0;
}
std::string encode_sjis(const char16_t* source) {
std::string encode_sjis(const char16_t* src, size_t src_count) {
const auto& table = unicode_to_sjis_table();
string ret;
while (*source) {
ret.push_back(table[*(source++)]);
for (; *src && (src_count > 0); src_count--) {
ret.push_back(table[*(src++)]);
};
return ret;
}
std::u16string decode_sjis(const char* source) {
std::u16string decode_sjis(const char* src, size_t src_count) {
const auto& table = sjis_to_unicode_table();
u16string ret;
while (*source) {
char16_t src_char = *(source++);
while (*src && (src_count > 0)) {
char16_t src_char = *(src++);
src_count--;
if (src_char & 0x80) {
src_char = (src_char << 8) | *(source++);
if (src_count == 0) {
return ret;
}
src_char = (src_char << 8) | *(src++);
if ((src_char & 0xFF) == 0) {
return ret;
}
src_count--;
}
ret.push_back(table[src_char]);
};
@@ -145,10 +153,13 @@ std::string encode_sjis(const std::u16string& source) {
std::u16string decode_sjis(const std::string& source) {
const auto& table = sjis_to_unicode_table();
u16string ret;
for (size_t x = 0; x < source.size(); x++) {
char16_t src_char = source[x];
for (size_t x = 0; x < source.size();) {
char16_t src_char = source[x++];
if (src_char & 0x80) {
src_char = (src_char << 8) | source[++x];
if (x == source.size()) {
return ret;
}
src_char = (src_char << 8) | source[x++];
if ((src_char & 0xFF) == 0) {
return ret;
}
@@ -198,7 +209,7 @@ void remove_language_marker_inplace(char* a) {
void remove_language_marker_inplace(char16_t* a) {
if ((a[0] == '\t') && (a[1] != 'C')) {
char16cpy(a, &a[2], char16len(a) - 2);
char16ncpy(a, &a[2], char16len(a) - 2);
}
}
+72 -5
View File
@@ -4,21 +4,56 @@
#include <stddef.h>
#include <string>
#include <phosg/Encoding.hh>
#include <phosg/Strings.hh>
int char16cmp(const char16_t* s1, const char16_t* s2, size_t count);
void char16cpy(char16_t* dest, const char16_t* src, size_t count);
#define countof(F) (sizeof(F) / sizeof(F[0]))
int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count);
void char16ncpy(char16_t* dest, const char16_t* src, size_t count);
size_t char16len(const char16_t* s);
void encode_sjis(char* dest, const char16_t* source, size_t dest_count);
void decode_sjis(char16_t* dest, const char* source, size_t dest_count);
std::string encode_sjis(const char16_t* source);
std::u16string decode_sjis(const char* source);
std::string encode_sjis(const char16_t* source, size_t src_count);
std::u16string decode_sjis(const char* source, size_t src_count);
std::string encode_sjis(const std::u16string& source);
std::u16string decode_sjis(const std::string& source);
template <typename DestT, typename SrcT = DestT>
void strncpy_t(DestT*, const SrcT*, size_t) {
static_assert(always_false<DestT, SrcT>::v,
"unspecialized strcpy_t should never be called");
}
template <>
inline void strncpy_t<char>(char* dest, const char* src, size_t count) {
strncpy(dest, src, count);
}
template <>
inline void strncpy_t<char, char16_t>(char* dest, const char16_t* src, size_t count) {
encode_sjis(dest, src, count);
}
template <>
inline void strncpy_t<char16_t, char>(char16_t* dest, const char* src, size_t count) {
decode_sjis(dest, src, count);
}
template <>
inline void strncpy_t<char16_t>(char16_t* dest, const char16_t* src, size_t count) {
char16ncpy(dest, src, count);
}
void add_language_marker_inplace(char* s, char marker, size_t dest_count);
void add_language_marker_inplace(char16_t* s, char16_t marker, size_t dest_count);
void remove_language_marker_inplace(char* s);
@@ -29,6 +64,7 @@ std::string remove_language_marker(const std::string& s);
std::u16string remove_language_marker(const std::u16string& s);
template <typename T>
void replace_char_inplace(T* a, T f, T r) {
while (*a) {
@@ -58,6 +94,8 @@ size_t add_color_inplace(T* a, size_t max_chars) {
*(d++) = '%';
} else if (*a == 'n') {
*(d++) = '#';
} else if (*a == '\0') {
break;
} else {
*(d++) = *a;
}
@@ -70,3 +108,32 @@ size_t add_color_inplace(T* a, size_t max_chars) {
return d - orig_d;
}
template <typename T>
void add_color(StringWriter& w, const T* src, size_t max_input_chars = SIZE_T_MAX) {
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);
}