use safe packed string types
This commit is contained in:
+9
-7
@@ -402,7 +402,8 @@ static void command_lobby_info(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
||||
level_string = string_printf("Levels: %d-%d", l->min_level + 1, l->max_level + 1);
|
||||
}
|
||||
|
||||
send_text_message_printf(c, "$C6Game ID: %08X\n%s\nSection ID: %s\nCheat mode: %s",
|
||||
send_text_message_printf(c,
|
||||
"$C6Game ID: %08X\n%s\nSection ID: %s\nCheat mode: %s",
|
||||
l->lobby_id, level_string.c_str(),
|
||||
name_for_section_id(l->section_id).c_str(),
|
||||
(l->flags & LobbyFlag::CHEATS_ENABLED) ? "on" : "off");
|
||||
@@ -547,7 +548,7 @@ static void command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
||||
send_text_message(l, u"$C6Game unlocked");
|
||||
|
||||
} else {
|
||||
strncpy_t(l->password, args, countof(l->password));
|
||||
l->password = args;
|
||||
auto encoded = encode_sjis(l->password);
|
||||
send_text_message_printf(l, "$C6Game password:\n%s",
|
||||
encoded.c_str());
|
||||
@@ -619,7 +620,9 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
||||
} else if (tokens[0] == "level") {
|
||||
c->player.disp.level = stoul(tokens[1]) - 1;
|
||||
} else if (tokens[0] == "namecolor") {
|
||||
sscanf(tokens[1].c_str(), "%8X", &c->player.disp.name_color);
|
||||
uint32_t new_color;
|
||||
sscanf(tokens[1].c_str(), "%8X", &new_color);
|
||||
c->player.disp.name_color = new_color;
|
||||
} else if (tokens[0] == "secid") {
|
||||
uint8_t secid = section_id_for_name(decode_sjis(tokens[1]));
|
||||
if (secid == 0xFF) {
|
||||
@@ -629,8 +632,7 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
||||
c->player.disp.section_id = secid;
|
||||
}
|
||||
} else if (tokens[0] == "name") {
|
||||
decode_sjis(c->player.disp.name, tokens[1].c_str(), 0x10);
|
||||
add_language_marker_inplace(c->player.disp.name, u'J', 0x10);
|
||||
c->player.disp.name = add_language_marker(tokens[1], 'J');
|
||||
} else if (tokens[0] == "npc") {
|
||||
if (tokens[1] == "none") {
|
||||
c->player.disp.extra_model = 0;
|
||||
@@ -648,7 +650,7 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
||||
uint8_t level = stoul(tokens[2]) - 1;
|
||||
if (tokens[1] == "all") {
|
||||
for (size_t x = 0; x < 0x14; x++) {
|
||||
c->player.disp.technique_levels[x] = level;
|
||||
c->player.disp.technique_levels.data()[x] = level;
|
||||
}
|
||||
} else {
|
||||
uint8_t tech_id = technique_for_name(decode_sjis(tokens[1]));
|
||||
@@ -656,7 +658,7 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
||||
send_text_message(c, u"$C6No such technique.");
|
||||
return;
|
||||
}
|
||||
c->player.disp.technique_levels[tech_id] = level;
|
||||
c->player.disp.technique_levels.data()[tech_id] = level;
|
||||
}
|
||||
} else {
|
||||
send_text_message(c, u"$C6Unknown field.");
|
||||
|
||||
+2
-2
@@ -63,8 +63,8 @@ ClientConfig Client::export_config() const {
|
||||
cc.flags = this->flags;
|
||||
cc.proxy_destination_address = this->proxy_destination_address;
|
||||
cc.proxy_destination_port = this->proxy_destination_port;
|
||||
memset(cc.unused, 0xFF, sizeof(cc.unused));
|
||||
memset(cc.unused_bb_only, 0xFF, sizeof(cc.unused_bb_only));
|
||||
cc.unused.clear(0xFF);
|
||||
cc.unused_bb_only.clear(0xFF);
|
||||
return cc;
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -8,6 +8,8 @@
|
||||
#include "Player.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
|
||||
|
||||
extern const uint64_t CLIENT_CONFIG_MAGIC;
|
||||
@@ -30,8 +32,8 @@ struct ClientConfig {
|
||||
uint16_t flags;
|
||||
uint32_t proxy_destination_address;
|
||||
uint16_t proxy_destination_port;
|
||||
uint8_t unused[0x0E];
|
||||
uint8_t unused_bb_only[0x08];
|
||||
parray<uint8_t, 0x0E> unused;
|
||||
parray<uint8_t, 0x08> unused_bb_only;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Client {
|
||||
|
||||
+88
-86
@@ -33,14 +33,14 @@ struct SC_TextHeader_01_06_11 {
|
||||
// Client will respond with an (encrypted) 9A, 9D, or 9E command
|
||||
|
||||
struct S_ServerInit_DC_GC_02_17 {
|
||||
char copyright[0x40];
|
||||
ptext<char, 0x40> copyright;
|
||||
le_uint32_t server_key; // Key for data sent by server
|
||||
le_uint32_t client_key; // Key for data sent by client
|
||||
char after_message[200];
|
||||
ptext<char, 0xBC> after_message;
|
||||
};
|
||||
|
||||
struct S_ServerInit_Patch_02 {
|
||||
char copyright[0x40];
|
||||
ptext<char, 0x40> copyright;
|
||||
le_uint32_t server_key;
|
||||
le_uint32_t client_key;
|
||||
// BB rejects the command if it's not exactly this size, so we can't add the
|
||||
@@ -51,10 +51,10 @@ struct S_ServerInit_Patch_02 {
|
||||
// Client will respond with a 93 command
|
||||
|
||||
struct S_ServerInit_BB_03 {
|
||||
char copyright[0x60];
|
||||
uint8_t server_key[0x30];
|
||||
uint8_t client_key[0x30];
|
||||
char after_message[200];
|
||||
ptext<char, 0x60> copyright;
|
||||
parray<uint8_t, 0x30> server_key;
|
||||
parray<uint8_t, 0x30> client_key;
|
||||
ptext<char, 0xBC> after_message;
|
||||
};
|
||||
|
||||
// 04 (S->C): Set guild card number and update client config ("security data")
|
||||
@@ -74,9 +74,9 @@ struct S_UpdateClientConfig_DC_PC_GC_04 {
|
||||
// 04 (C->S): Log in (patch server)
|
||||
|
||||
struct C_Login_Patch_04 {
|
||||
le_uint32_t unused[3];
|
||||
char username[0x10];
|
||||
char password[0x10];
|
||||
parray<le_uint32_t, 3> unused;
|
||||
ptext<char, 0x10> username;
|
||||
ptext<char, 0x10> password;
|
||||
};
|
||||
|
||||
// 05: Disconnect
|
||||
@@ -89,7 +89,7 @@ struct C_Login_Patch_04 {
|
||||
// The guild_card_number field is used only in server->client commands
|
||||
|
||||
struct C_Chat_06 {
|
||||
le_uint32_t unused[2];
|
||||
uint64_t unused;
|
||||
union {
|
||||
char dcgc[0];
|
||||
char16_t pcbb[0];
|
||||
@@ -107,7 +107,7 @@ struct S_MenuEntry {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
le_uint16_t flags; // should be 0x0F04
|
||||
CharT text[EntryLength];
|
||||
ptext<CharT, EntryLength> text;
|
||||
};
|
||||
struct S_MenuEntry_PC_BB_07 : S_MenuEntry<char16_t, 17> { };
|
||||
struct S_MenuEntry_DC_GC_07 : S_MenuEntry<char, 18> { };
|
||||
@@ -126,7 +126,7 @@ struct S_GameMenuEntry {
|
||||
le_uint32_t game_id;
|
||||
uint8_t difficulty_tag; // 0x0A = Ep3; else difficulty + 0x22 (so 0x25 = Ult)
|
||||
uint8_t num_players;
|
||||
CharT name[0x10];
|
||||
ptext<CharT, 0x10> name;
|
||||
uint8_t episode; // 40 = Ep1, 41 = Ep2, 43 = Ep4. Ignored on Ep3
|
||||
uint8_t flags; // 02 = locked, 04 = disabled (BB), 10 = battle, 20 = challenge
|
||||
};
|
||||
@@ -136,7 +136,7 @@ struct S_GameMenuEntry_GC_08 : S_GameMenuEntry<char> { };
|
||||
// 09 (S->C): Check directory (patch server)
|
||||
|
||||
struct S_CheckDirectory_Patch_09 {
|
||||
char name[0x40];
|
||||
ptext<char, 0x40> name;
|
||||
};
|
||||
|
||||
// 09 (C->S): Menu item info request
|
||||
@@ -189,7 +189,7 @@ struct C_MenuSelection {
|
||||
|
||||
// Header flag = file chunk index
|
||||
struct S_WriteFile_13_A7 {
|
||||
char filename[0x10];
|
||||
ptext<char, 0x10> filename;
|
||||
uint8_t data[0x400];
|
||||
le_uint32_t data_size;
|
||||
};
|
||||
@@ -220,13 +220,13 @@ struct S_Reconnect_19 {
|
||||
struct S_ReconnectSplit_19 {
|
||||
be_uint32_t pc_address;
|
||||
le_uint16_t pc_port;
|
||||
uint8_t unused1[0x0F];
|
||||
parray<uint8_t, 0x0F> unused1;
|
||||
uint8_t gc_command;
|
||||
uint8_t gc_flag;
|
||||
le_uint16_t gc_size;
|
||||
be_uint32_t gc_address;
|
||||
le_uint16_t gc_port;
|
||||
uint8_t unused2[0xB0 - 0x23];
|
||||
parray<uint8_t, 0xB0 - 0x23> unused2;
|
||||
};
|
||||
|
||||
// 1A: Large message box
|
||||
@@ -256,7 +256,7 @@ struct S_ReconnectSplit_19 {
|
||||
// command had no payload). The latest version uses this 16-byte challenge.
|
||||
|
||||
struct SC_GameCardCheck_BB_22 {
|
||||
uint8_t data[0x10];
|
||||
parray<uint8_t, 0x10> data;
|
||||
};
|
||||
|
||||
// 23: Invalid command
|
||||
@@ -304,11 +304,11 @@ struct S_GuildCardSearchResult {
|
||||
le_uint32_t result_serial_number;
|
||||
HeaderT reconnect_command_header;
|
||||
S_Reconnect_19 reconnect_command;
|
||||
char location_string[0x44];
|
||||
ptext<char, 0x44> location_string;
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t lobby_id;
|
||||
char unused[0x3C];
|
||||
CharT name[0x20];
|
||||
ptext<char, 0x3C> unused;
|
||||
ptext<CharT, 0x20> name;
|
||||
};
|
||||
struct S_GuildCardSearchResult_PC_40 : S_GuildCardSearchResult<PSOCommandHeaderPC, char16_t> { };
|
||||
struct S_GuildCardSearchResult_DC_GC_40 : S_GuildCardSearchResult<PSOCommandHeaderDCGC, char> { };
|
||||
@@ -326,19 +326,19 @@ struct S_GuildCardSearchResult_BB_40 : S_GuildCardSearchResult<PSOCommandHeaderB
|
||||
// Used for downloading online quests
|
||||
|
||||
struct S_OpenFile_PC_GC_44_A6 {
|
||||
char name[0x20];
|
||||
ptext<char, 0x20> name;
|
||||
le_uint16_t unused;
|
||||
le_uint16_t flags;
|
||||
char filename[0x10];
|
||||
ptext<char, 0x10> filename;
|
||||
le_uint32_t file_size;
|
||||
};
|
||||
|
||||
struct S_OpenFile_BB_44_A6 {
|
||||
uint8_t unused[0x22];
|
||||
parray<uint8_t, 0x22> unused;
|
||||
le_uint16_t flags;
|
||||
char filename[0x10];
|
||||
ptext<char, 0x10> filename;
|
||||
le_uint32_t file_size;
|
||||
char name[0x18];
|
||||
ptext<char, 0x18> name;
|
||||
};
|
||||
|
||||
// 45: Invalid command
|
||||
@@ -391,7 +391,7 @@ struct S_OpenFile_BB_44_A6 {
|
||||
// Header flag = entry count
|
||||
template <typename LobbyDataT, typename DispDataT>
|
||||
struct S_JoinGame {
|
||||
le_uint32_t variations[0x20];
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
// Unlike lobby join commands, these are filled in in their slot positions.
|
||||
// That is, if there's one player in a game with ID 2, then the first two of
|
||||
// these are blank and the player's data is in the third entry here.
|
||||
@@ -514,9 +514,9 @@ struct S_LeaveLobby_66_69 {
|
||||
struct SC_SimpleMail_GC_81 {
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t from_serial_number;
|
||||
char from_name[0x10];
|
||||
ptext<char, 0x10> from_name;
|
||||
le_uint32_t to_serial_number;
|
||||
char text[0x200];
|
||||
ptext<char, 0x200> text;
|
||||
};
|
||||
|
||||
// 82: Invalid command
|
||||
@@ -574,11 +574,11 @@ struct S_ArrowUpdateEntry_88 {
|
||||
// 93 (C->S): Log in (BB)
|
||||
|
||||
struct C_Login_BB_93 {
|
||||
char unused[0x14];
|
||||
char username[0x10];
|
||||
char unused2[0x20];
|
||||
char password[0x10];
|
||||
char unused3[0x30];
|
||||
ptext<char, 0x14> unused;
|
||||
ptext<char, 0x10> username;
|
||||
ptext<char, 0x20> unused2;
|
||||
ptext<char, 0x10> password;
|
||||
ptext<char, 0x30> unused3;
|
||||
ClientConfig cfg;
|
||||
};
|
||||
|
||||
@@ -611,9 +611,9 @@ struct C_ClientChecksum_GC_96 {
|
||||
// 9A (C->S): Initial login (no password or client config)
|
||||
|
||||
struct C_Login_DC_PC_GC_9A {
|
||||
char unused[0x20];
|
||||
char serial_number[0x10];
|
||||
char access_key[0x10];
|
||||
ptext<char, 0x20> unused;
|
||||
ptext<char, 0x10> serial_number;
|
||||
ptext<char, 0x10> access_key;
|
||||
};
|
||||
|
||||
// 9B: Invalid command
|
||||
@@ -625,12 +625,12 @@ struct C_Login_DC_PC_GC_9A {
|
||||
// 9C (C->S): Register
|
||||
|
||||
struct C_Register_DC_PC_GC_9C {
|
||||
char unused[8];
|
||||
ptext<char, 8> unused;
|
||||
le_uint32_t sub_version;
|
||||
le_uint32_t unused2;
|
||||
char serial_number[0x30];
|
||||
char access_key[0x30];
|
||||
char password[0x30];
|
||||
ptext<char, 0x30> serial_number;
|
||||
ptext<char, 0x30> access_key;
|
||||
ptext<char, 0x30> password;
|
||||
};
|
||||
|
||||
// 9D (C->S): Log in with client config
|
||||
@@ -641,19 +641,19 @@ struct C_Register_DC_PC_GC_9C {
|
||||
struct C_Login_PC_GC_9D_9E {
|
||||
le_uint32_t player_tag; // 00 00 01 00 if guild card is set (via 04)
|
||||
le_uint32_t guild_card_number; // FF FF FF FF if not set
|
||||
le_uint32_t unused1[2];
|
||||
le_uint64_t unused;
|
||||
le_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];
|
||||
parray<uint8_t, 0x24> unused2; // 00 01 00 00 ... (rest is 00)
|
||||
ptext<char, 0x10> serial_number;
|
||||
ptext<char, 0x10> access_key;
|
||||
ptext<char, 0x30> serial_number2;
|
||||
ptext<char, 0x30> access_key2;
|
||||
ptext<char, 0x10> name;
|
||||
// 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];
|
||||
parray<uint8_t, 0x5C> unused4;
|
||||
};
|
||||
|
||||
// 9F: Invalid command
|
||||
@@ -679,8 +679,8 @@ template <typename CharT>
|
||||
struct S_QuestMenuEntry {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
CharT name[0x20];
|
||||
CharT short_desc[0x70];
|
||||
ptext<CharT, 0x20> name;
|
||||
ptext<CharT, 0x70> short_desc;
|
||||
};
|
||||
struct S_QuestMenuEntry_PC_A2_A4 : S_QuestMenuEntry<char16_t> { };
|
||||
struct S_QuestMenuEntry_GC_A2_A4 : S_QuestMenuEntry<char> { };
|
||||
@@ -688,8 +688,9 @@ struct S_QuestMenuEntry_GC_A2_A4 : S_QuestMenuEntry<char> { };
|
||||
struct S_QuestMenuEntry_BB_A2_A4 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
char16_t name[0x20];
|
||||
char16_t short_desc[0x7A]; // Why is this not the same as PC? Mysteries of SEGA's engineering...
|
||||
ptext<char16_t, 0x20> name;
|
||||
// Why is this 10 characters longer than on other versions...?
|
||||
ptext<char16_t, 0x7A> short_desc;
|
||||
};
|
||||
|
||||
// A3 (S->C): Quest information
|
||||
@@ -755,7 +756,7 @@ struct S_QuestMenuEntry_BB_A2_A4 {
|
||||
|
||||
struct S_RankUpdate_GC_Ep3_B7 {
|
||||
le_uint32_t rank;
|
||||
char rank_text[0x0C];
|
||||
ptext<char, 0x0C> rank_text;
|
||||
le_uint32_t meseta;
|
||||
le_uint32_t max_meseta;
|
||||
le_uint32_t jukebox_songs_unlocked;
|
||||
@@ -799,7 +800,7 @@ struct S_ChoiceSearchEntry {
|
||||
// be set by the user at any time; otherwise it can't.
|
||||
le_uint16_t parent_category_id; // 0 for top-level categories
|
||||
le_uint16_t category_id;
|
||||
CharT text[0x1C];
|
||||
ptext<CharT, 0x1C> text;
|
||||
};
|
||||
struct S_ChoiceSearchEntry_DC_GC_C0 : S_ChoiceSearchEntry<char> { };
|
||||
struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry<char16_t> { };
|
||||
@@ -821,9 +822,9 @@ struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry<char16_t> { };
|
||||
|
||||
template <typename CharT>
|
||||
struct C_CreateGame {
|
||||
le_uint32_t unused[2];
|
||||
CharT name[0x10];
|
||||
CharT password[0x10];
|
||||
le_uint64_t unused;
|
||||
ptext<CharT, 0x10> name;
|
||||
ptext<CharT, 0x10> password;
|
||||
uint8_t difficulty;
|
||||
uint8_t battle_mode;
|
||||
uint8_t challenge_mode;
|
||||
@@ -831,6 +832,7 @@ struct C_CreateGame {
|
||||
};
|
||||
struct C_CreateGame_DC_GC_C1_EC : C_CreateGame<char> { };
|
||||
struct C_CreateGame_PC_C1 : C_CreateGame<char16_t> { };
|
||||
|
||||
struct C_CreateGame_BB_C1 : C_CreateGame<char16_t> {
|
||||
uint8_t solo_mode;
|
||||
uint8_t unused2[3];
|
||||
@@ -864,12 +866,12 @@ struct C_ExecuteChoiceSearch_C3 {
|
||||
// Command is a list of these; header.flag is the entry count
|
||||
struct S_ChoiceSearchResultEntry_GC_C4 {
|
||||
le_uint32_t guild_card_number;
|
||||
char name[0x10]; // No language marker, as usual on GC
|
||||
char info_string[0x20]; // Usually something like "<class> Lvl <level>"
|
||||
ptext<char, 0x10> name; // No language marker, as usual on GC
|
||||
ptext<char, 0x20> info_string; // Usually something like "<class> Lvl <level>"
|
||||
// Format is stricter here; this is "LOBBYNAME,BLOCKNUM,SHIPNAME"
|
||||
// If target is in game, for example, "Game Name,BLOCK01,Alexandria"
|
||||
// If target is in lobby, for example, "BLOCK01-1,BLOCK01,Alexandria"
|
||||
char locator_string[0x34];
|
||||
ptext<char, 0x34> locator_string;
|
||||
// Server IP and port for "meet user" option
|
||||
le_uint32_t server_ip;
|
||||
le_uint16_t server_port;
|
||||
@@ -877,7 +879,7 @@ struct S_ChoiceSearchResultEntry_GC_C4 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t lobby_id; // These two are guesses
|
||||
le_uint32_t game_id; // Zero if target is in a lobby rather than a game
|
||||
uint8_t unused2[0x58];
|
||||
parray<uint8_t, 0x58> unused2;
|
||||
};
|
||||
|
||||
// C5 (S->C): Challenge rank update
|
||||
@@ -886,7 +888,7 @@ struct S_ChoiceSearchResultEntry_GC_C4 {
|
||||
// C6 (C->S): Set blocked senders list
|
||||
|
||||
struct C_SetBlockedSenders_C6 {
|
||||
le_uint32_t blocked_senders[30];
|
||||
parray<le_uint32_t, 30> blocked_senders;
|
||||
};
|
||||
|
||||
// C7 (C->S): Enable simple mail auto-reply
|
||||
@@ -936,7 +938,7 @@ struct C_SetBlockedSenders_C6 {
|
||||
// D7 (C->S): Request GBA game file
|
||||
|
||||
struct C_GBAGameRequest_GC_D7 {
|
||||
char filename[0x10];
|
||||
ptext<char, 0x10> filename;
|
||||
};
|
||||
|
||||
// D8 (S->C): Info board
|
||||
@@ -944,8 +946,8 @@ struct C_GBAGameRequest_GC_D7 {
|
||||
// Command is a list of these; header.flag is the entry count
|
||||
template <typename CharT>
|
||||
struct S_InfoBoardEntry_D8 {
|
||||
CharT name[0x10];
|
||||
CharT message[0xAC];
|
||||
ptext<CharT, 0x10> name;
|
||||
ptext<CharT, 0xAC> message;
|
||||
};
|
||||
struct S_InfoBoardEntry_PC_BB_D8 : S_InfoBoardEntry_D8<char16_t> { };
|
||||
struct S_InfoBoardEntry_DC_GC_D8 : S_InfoBoardEntry_D8<char> { };
|
||||
@@ -959,14 +961,14 @@ struct S_InfoBoardEntry_DC_GC_D8 : S_InfoBoardEntry_D8<char> { };
|
||||
// DB (S->C): Verify license (GC)
|
||||
|
||||
struct C_VerifyLicense_GC_DB {
|
||||
char unused[0x20];
|
||||
char serial_number[0x10];
|
||||
char access_key[0x10];
|
||||
char unused2[0x08];
|
||||
ptext<char, 0x20> unused;
|
||||
ptext<char, 0x10> serial_number;
|
||||
ptext<char, 0x10> access_key;
|
||||
ptext<char, 0x08> unused2;
|
||||
le_uint32_t sub_version;
|
||||
char serial_number2[0x30];
|
||||
char access_key2[0x30];
|
||||
char password[0x30];
|
||||
ptext<char, 0x30> serial_number2;
|
||||
ptext<char, 0x30> access_key2;
|
||||
ptext<char, 0x30> password;
|
||||
};
|
||||
|
||||
// DC: Player menu state (Episode 3)
|
||||
@@ -1002,7 +1004,7 @@ struct S_GuildCardHeader_BB_01DC {
|
||||
struct S_TournamentEntry_GC_Ep3_E0 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
uint8_t unknown[0x30];
|
||||
parray<uint8_t, 0x30> unknown;
|
||||
};
|
||||
|
||||
// E0 (C->S): Request team and key config (BB)
|
||||
@@ -1104,7 +1106,7 @@ struct S_StreamFileIndexEntry_BB_01EB {
|
||||
le_uint32_t size;
|
||||
le_uint32_t checksum;
|
||||
le_uint32_t offset;
|
||||
char filename[0x40];
|
||||
ptext<char, 0x40> filename;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct S_StreamFileChunk_BB_02EB {
|
||||
@@ -1123,12 +1125,12 @@ struct S_StreamFileChunk_BB_02EB {
|
||||
|
||||
union C_UpdateAccountData_BB_ED {
|
||||
le_uint32_t option; // 01ED
|
||||
uint8_t symbol_chats[0x4E0]; // 02ED
|
||||
uint8_t chat_shortcuts[0xA40]; // 03ED
|
||||
uint8_t key_config[0x16C]; // 04ED
|
||||
uint8_t pad_config[0x38]; // 05ED
|
||||
uint8_t tech_menu[0x28]; // 06ED
|
||||
uint8_t customize[0xE8]; // 07ED
|
||||
parray<uint8_t, 0x4E0> symbol_chats; // 02ED
|
||||
parray<uint8_t, 0xA40> chat_shortcuts; // 03ED
|
||||
parray<uint8_t, 0x16C> key_config; // 04ED
|
||||
parray<uint8_t, 0x38> pad_config; // 05ED
|
||||
parray<uint8_t, 0x28> tech_menu; // 06ED
|
||||
parray<uint8_t, 0xE8> customize; // 07ED
|
||||
} __attribute__((packed));
|
||||
|
||||
// EE: Scrolling message (BB)
|
||||
@@ -1162,8 +1164,8 @@ struct S_SendGuildCard_GC {
|
||||
le_uint16_t unused;
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t serial_number;
|
||||
char name[0x18];
|
||||
char desc[0x6C];
|
||||
ptext<char, 0x18> name;
|
||||
ptext<char, 0x6C> desc;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint8_t section_id;
|
||||
@@ -1175,9 +1177,9 @@ struct S_SendGuildCard_BB {
|
||||
uint8_t subsize;
|
||||
le_uint16_t unused;
|
||||
le_uint32_t serial_number;
|
||||
char16_t name[0x18];
|
||||
char16_t team_name[0x10];
|
||||
char16_t desc[0x58];
|
||||
ptext<char16_t, 0x18> name;
|
||||
ptext<char16_t, 0x10> team_name;
|
||||
ptext<char16_t, 0x58> desc;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
uint8_t section_id;
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
|
||||
auto& item = c->player.inventory.items[item_index];
|
||||
if (item.data.item_data1w[0] == 0x0203) { // technique disk
|
||||
c->player.disp.technique_levels[item.data.item_data1[4]] = item.data.item_data1[2];
|
||||
c->player.disp.technique_levels.data()[item.data.item_data1[4]] = item.data.item_data1[2];
|
||||
|
||||
} else if (item.data.item_data1w[0] == 0x0A03) { // grinder
|
||||
if (equipped_weapon < 0) {
|
||||
|
||||
+18
-28
@@ -11,31 +11,23 @@ using namespace std;
|
||||
|
||||
|
||||
|
||||
License::License() {
|
||||
memset(this->username, 0, 20);
|
||||
memset(this->bb_password, 0, 20);
|
||||
this->serial_number = 0;
|
||||
memset(this->access_key, 0, 16);
|
||||
memset(this->gc_password, 0, 12);
|
||||
this->privileges = 0;
|
||||
this->ban_end_time = 0;
|
||||
}
|
||||
License::License() : serial_number(0), privileges(0), ban_end_time(0) { }
|
||||
|
||||
string License::str() const {
|
||||
string ret = string_printf("License(serial_number=%" PRIu32, this->serial_number);
|
||||
if (this->username[0]) {
|
||||
if (!this->username.empty()) {
|
||||
ret += ", username=";
|
||||
ret += this->username;
|
||||
}
|
||||
if (this->bb_password[0]) {
|
||||
if (!this->bb_password.empty()) {
|
||||
ret += ", bb-password=";
|
||||
ret += this->bb_password;
|
||||
}
|
||||
if (this->access_key[0]) {
|
||||
if (!this->access_key.empty()) {
|
||||
ret += ", access-key=";
|
||||
ret += this->access_key;
|
||||
}
|
||||
if (this->gc_password[0]) {
|
||||
if (!this->gc_password.empty()) {
|
||||
ret += ", gc-password=";
|
||||
ret += this->gc_password;
|
||||
}
|
||||
@@ -83,10 +75,10 @@ void LicenseManager::save() const {
|
||||
shared_ptr<const License> LicenseManager::verify_pc(uint32_t serial_number,
|
||||
const char* access_key, const char* password) const {
|
||||
auto& license = this->serial_number_to_license.at(serial_number);
|
||||
if (strncmp(license->access_key, access_key, 8)) {
|
||||
if (!license->access_key.eq_n(access_key, 8)) {
|
||||
throw invalid_argument("incorrect access key");
|
||||
}
|
||||
if (password && (strcmp(license->gc_password, password))) {
|
||||
if (password && (license->gc_password != password)) {
|
||||
throw invalid_argument("incorrect password");
|
||||
}
|
||||
|
||||
@@ -99,10 +91,10 @@ shared_ptr<const License> LicenseManager::verify_pc(uint32_t serial_number,
|
||||
shared_ptr<const License> LicenseManager::verify_gc(uint32_t serial_number,
|
||||
const char* access_key, const char* password) const {
|
||||
auto& license = this->serial_number_to_license.at(serial_number);
|
||||
if (strncmp(license->access_key, access_key, 12)) {
|
||||
if (!license->access_key.eq_n(access_key, 12)) {
|
||||
throw invalid_argument("incorrect access key");
|
||||
}
|
||||
if (password && (strcmp(license->gc_password, password))) {
|
||||
if (password && (license->gc_password != password)) {
|
||||
throw invalid_argument("incorrect password");
|
||||
}
|
||||
|
||||
@@ -115,7 +107,7 @@ shared_ptr<const License> LicenseManager::verify_gc(uint32_t serial_number,
|
||||
shared_ptr<const License> LicenseManager::verify_bb(const char* username,
|
||||
const char* password) const {
|
||||
auto& license = this->bb_username_to_license.at(username);
|
||||
if (password && strcmp(license->bb_password, password)) {
|
||||
if (password && (license->bb_password != password)) {
|
||||
throw invalid_argument("incorrect password");
|
||||
}
|
||||
|
||||
@@ -137,20 +129,18 @@ void LicenseManager::ban_until(uint32_t serial_number, uint64_t end_time) {
|
||||
void LicenseManager::add(shared_ptr<License> l) {
|
||||
uint32_t serial_number = l->serial_number;
|
||||
this->serial_number_to_license.emplace(serial_number, l);
|
||||
if (l->username[0]) {
|
||||
if (!l->username.empty()) {
|
||||
this->bb_username_to_license.emplace(l->username, l);
|
||||
}
|
||||
|
||||
this->save();
|
||||
}
|
||||
|
||||
void LicenseManager::remove(uint32_t serial_number) {
|
||||
auto l = this->serial_number_to_license.at(serial_number);
|
||||
this->serial_number_to_license.erase(l->serial_number);
|
||||
if (l->username[0]) {
|
||||
if (!l->username.empty()) {
|
||||
this->bb_username_to_license.erase(l->username);
|
||||
}
|
||||
|
||||
this->save();
|
||||
}
|
||||
|
||||
@@ -168,9 +158,9 @@ shared_ptr<License> LicenseManager::create_license_pc(
|
||||
uint32_t serial_number,const char* access_key, const char* password, bool temporary) {
|
||||
shared_ptr<License> l(new License());
|
||||
l->serial_number = serial_number;
|
||||
strncpy(l->access_key, access_key, 8);
|
||||
l->access_key = access_key;
|
||||
if (password) {
|
||||
strncpy(l->gc_password, password, 8);
|
||||
l->gc_password = password;
|
||||
}
|
||||
if (temporary) {
|
||||
l->privileges |= Privilege::TEMPORARY;
|
||||
@@ -182,9 +172,9 @@ shared_ptr<License> LicenseManager::create_license_gc(
|
||||
uint32_t serial_number, const char* access_key, const char* password, bool temporary) {
|
||||
shared_ptr<License> l(new License());
|
||||
l->serial_number = serial_number;
|
||||
strncpy(l->access_key, access_key, 12);
|
||||
l->access_key = access_key;
|
||||
if (password) {
|
||||
strncpy(l->gc_password, password, 8);
|
||||
l->gc_password = password;
|
||||
}
|
||||
if (temporary) {
|
||||
l->privileges |= Privilege::TEMPORARY;
|
||||
@@ -196,8 +186,8 @@ shared_ptr<License> LicenseManager::create_license_bb(
|
||||
uint32_t serial_number, const char* username, const char* password, bool temporary) {
|
||||
shared_ptr<License> l(new License());
|
||||
l->serial_number = serial_number;
|
||||
strncpy(l->username, username, 19);
|
||||
strncpy(l->bb_password, password, 19);
|
||||
l->username = username;
|
||||
l->bb_password = password;
|
||||
if (temporary) {
|
||||
l->privileges |= Privilege::TEMPORARY;
|
||||
}
|
||||
|
||||
+6
-4
@@ -5,6 +5,8 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
enum Privilege {
|
||||
KICK_USER = 0x00000001,
|
||||
BAN_USER = 0x00000002,
|
||||
@@ -30,11 +32,11 @@ enum LicenseVerifyAction {
|
||||
};
|
||||
|
||||
struct License {
|
||||
char username[20]; // BB username (max. 16 chars; should technically be Unicode)
|
||||
char bb_password[20]; // BB password (max. 16 chars)
|
||||
ptext<char, 0x14> username; // BB username (max. 16 chars; should technically be Unicode)
|
||||
ptext<char, 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.
|
||||
char access_key[16]; // PC/GC access key. (to log in using PC on a GC license, just enter the first 8 characters of the GC access key)
|
||||
char gc_password[12]; // GC password
|
||||
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
|
||||
uint32_t privileges; // privilege level
|
||||
uint64_t ban_end_time; // end time of ban (zero = not banned)
|
||||
|
||||
|
||||
+1
-4
@@ -21,9 +21,6 @@ Lobby::Lobby() : lobby_id(0), min_level(0), max_level(0xFFFFFFFF),
|
||||
this->next_item_id[x] = 0;
|
||||
}
|
||||
memset(&this->next_drop_item, 0, sizeof(this->next_drop_item));
|
||||
memset(this->variations, 0, 0x20 * sizeof(this->variations[0]));
|
||||
memset(this->password, 0, 36 * sizeof(this->password[0]));
|
||||
memset(this->name, 0, 36 * sizeof(this->name[0]));
|
||||
}
|
||||
|
||||
bool Lobby::is_game() const {
|
||||
@@ -152,7 +149,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 && !char16ncmp(this->clients[x]->player.disp.name, identifier, 0x10)) {
|
||||
if (identifier && (this->clients[x]->player.disp.name == identifier)) {
|
||||
return this->clients[x];
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -33,7 +33,7 @@ struct Lobby {
|
||||
uint32_t next_game_item_id;
|
||||
PlayerInventoryItem next_drop_item;
|
||||
std::unordered_map<uint32_t, PlayerInventoryItem> item_id_to_floor_item;
|
||||
uint32_t variations[0x20];
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
|
||||
// game config
|
||||
GameVersion version;
|
||||
@@ -41,8 +41,8 @@ struct Lobby {
|
||||
uint8_t episode;
|
||||
uint8_t difficulty;
|
||||
uint8_t mode;
|
||||
char16_t password[0x24];
|
||||
char16_t name[0x24];
|
||||
std::u16string password;
|
||||
std::u16string name;
|
||||
uint32_t rare_seed;
|
||||
|
||||
//EP3_GAME_CONFIG* ep3; // only present if this is an Episode 3 game
|
||||
|
||||
+2
-2
@@ -10,11 +10,11 @@ struct BattleParams {
|
||||
uint16_t atp; // attack power
|
||||
uint16_t psv; // perseverance (intelligence?)
|
||||
uint16_t evp; // evasion
|
||||
uint16_t hp; // hit points
|
||||
uint16_t hp; // hit points
|
||||
uint16_t dfp; // defense
|
||||
uint16_t ata; // accuracy
|
||||
uint16_t lck; // luck
|
||||
uint8_t unknown[14];
|
||||
uint8_t unknown_a1[0x0E];
|
||||
uint32_t experience;
|
||||
uint32_t difficulty;
|
||||
} __attribute__((packed));
|
||||
|
||||
+77
-126
@@ -49,19 +49,15 @@ PlayerDispDataBB PlayerDispDataPCGC::to_bb() const {
|
||||
bb.stats.dfp = this->stats.dfp;
|
||||
bb.stats.ata = this->stats.ata;
|
||||
bb.stats.lck = this->stats.lck;
|
||||
bb.unknown1 = this->unknown1;
|
||||
bb.unknown2[0] = this->unknown2[0];
|
||||
bb.unknown2[1] = this->unknown2[1];
|
||||
bb.unknown_a1 = this->unknown_a1;
|
||||
bb.level = this->level;
|
||||
bb.experience = this->experience;
|
||||
bb.meseta = this->meseta;
|
||||
memset(bb.guild_card, 0, sizeof(bb.guild_card));
|
||||
strncpy(bb.guild_card, " 0", 0x10);
|
||||
bb.unknown3[0] = this->unknown3[0];
|
||||
bb.unknown3[1] = this->unknown3[1];
|
||||
bb.guild_card = " 0";
|
||||
bb.unknown_a2 = this->unknown_a2;
|
||||
bb.name_color = this->name_color;
|
||||
bb.extra_model = this->extra_model;
|
||||
memcpy(&bb.unused, &this->unused, 15);
|
||||
bb.unused = this->unused;
|
||||
bb.name_color_checksum = this->name_color_checksum;
|
||||
bb.section_id = this->section_id;
|
||||
bb.char_class = this->char_class;
|
||||
@@ -78,11 +74,9 @@ PlayerDispDataBB PlayerDispDataPCGC::to_bb() const {
|
||||
bb.hair_b = this->hair_b;
|
||||
bb.proportion_x = this->proportion_x;
|
||||
bb.proportion_y = this->proportion_y;
|
||||
memset(bb.name, 0, sizeof(bb.name));
|
||||
decode_sjis(bb.name, this->name, 0x10);
|
||||
add_language_marker_inplace(bb.name, 'J', 0x10);
|
||||
memcpy(&bb.config, &this->config, 0x48);
|
||||
memcpy(&bb.technique_levels, &this->technique_levels, 0x14);
|
||||
bb.name = add_language_marker(this->name, 'J');
|
||||
bb.config = this->config;
|
||||
bb.technique_levels = this->technique_levels;
|
||||
return bb;
|
||||
}
|
||||
|
||||
@@ -96,17 +90,14 @@ PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const {
|
||||
pcgc.stats.dfp = this->stats.dfp;
|
||||
pcgc.stats.ata = this->stats.ata;
|
||||
pcgc.stats.lck = this->stats.lck;
|
||||
pcgc.unknown1 = this->unknown1;
|
||||
pcgc.unknown2[0] = this->unknown2[0];
|
||||
pcgc.unknown2[1] = this->unknown2[1];
|
||||
pcgc.unknown_a1 = this->unknown_a1;
|
||||
pcgc.level = this->level;
|
||||
pcgc.experience = this->experience;
|
||||
pcgc.meseta = this->meseta;
|
||||
pcgc.unknown3[0] = this->unknown3[0];
|
||||
pcgc.unknown3[1] = this->unknown3[1];
|
||||
pcgc.unknown_a2 = this->unknown_a2;
|
||||
pcgc.name_color = this->name_color;
|
||||
pcgc.extra_model = this->extra_model;
|
||||
memcpy(&pcgc.unused, &this->unused, 15);
|
||||
pcgc.unused = this->unused;
|
||||
pcgc.name_color_checksum = this->name_color_checksum;
|
||||
pcgc.section_id = this->section_id;
|
||||
pcgc.char_class = this->char_class;
|
||||
@@ -123,11 +114,9 @@ PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const {
|
||||
pcgc.hair_b = this->hair_b;
|
||||
pcgc.proportion_x = this->proportion_x;
|
||||
pcgc.proportion_y = this->proportion_y;
|
||||
memset(pcgc.name, 0, sizeof(pcgc.name));
|
||||
encode_sjis(pcgc.name, this->name, 0x10);
|
||||
remove_language_marker_inplace(pcgc.name);
|
||||
memcpy(&pcgc.config, &this->config, 0x48);
|
||||
memcpy(&pcgc.technique_levels, &this->technique_levels, 0x14);
|
||||
pcgc.name = remove_language_marker(this->name);
|
||||
pcgc.config = this->config;
|
||||
pcgc.technique_levels = this->technique_levels;
|
||||
return pcgc;
|
||||
}
|
||||
|
||||
@@ -136,13 +125,11 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
|
||||
PlayerDispDataBBPreview pre;
|
||||
pre.level = this->level;
|
||||
pre.experience = this->experience;
|
||||
memset(pre.guild_card, 0, sizeof(pre.guild_card));
|
||||
strncpy(pre.guild_card, this->guild_card, 0x10);
|
||||
pre.unknown3[0] = this->unknown3[0];
|
||||
pre.unknown3[1] = this->unknown3[1];
|
||||
pre.guild_card = this->guild_card;
|
||||
pre.unknown_a2 = this->unknown_a2;
|
||||
pre.name_color = this->name_color;
|
||||
pre.extra_model = this->extra_model;
|
||||
memcpy(&pre.unused, &this->unused, 11);
|
||||
pre.unused = this->unused;
|
||||
pre.name_color_checksum = this->name_color_checksum;
|
||||
pre.section_id = this->section_id;
|
||||
pre.char_class = this->char_class;
|
||||
@@ -159,22 +146,19 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
|
||||
pre.hair_b = this->hair_b;
|
||||
pre.proportion_x = this->proportion_x;
|
||||
pre.proportion_y = this->proportion_y;
|
||||
memset(pre.name, 0, sizeof(pre.name));
|
||||
strcpy_z(pre.name, this->name, 16);
|
||||
pre.play_time = this->play_time;
|
||||
pre.name = this->name;
|
||||
pre.play_time = 0; // TODO: Store this somewhere and return it here
|
||||
return pre;
|
||||
}
|
||||
|
||||
void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
|
||||
this->level = pre.level;
|
||||
this->experience = pre.experience;
|
||||
memset(this->guild_card, 0, sizeof(this->guild_card));
|
||||
strncpy(this->guild_card, pre.guild_card, 0x10);
|
||||
this->unknown3[0] = pre.unknown3[0];
|
||||
this->unknown3[1] = pre.unknown3[1];
|
||||
this->guild_card = pre.guild_card;
|
||||
this->unknown_a2 = pre.unknown_a2;
|
||||
this->name_color = pre.name_color;
|
||||
this->extra_model = pre.extra_model;
|
||||
memcpy(&this->unused, &pre.unused, 11);
|
||||
this->unused = pre.unused;
|
||||
this->name_color_checksum = pre.name_color_checksum;
|
||||
this->section_id = pre.section_id;
|
||||
this->char_class = pre.char_class;
|
||||
@@ -191,9 +175,7 @@ void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
|
||||
this->hair_b = pre.hair_b;
|
||||
this->proportion_x = pre.proportion_x;
|
||||
this->proportion_y = pre.proportion_y;
|
||||
memset(this->name, 0, sizeof(this->name));
|
||||
strcpy_z(this->name, pre.name, 0x10);
|
||||
this->play_time = 0;
|
||||
this->name = pre.name;
|
||||
}
|
||||
|
||||
|
||||
@@ -214,43 +196,33 @@ void PlayerBank::save(const string& filename) const {
|
||||
void Player::import(const PSOPlayerDataPC& pc) {
|
||||
this->inventory = pc.inventory;
|
||||
this->disp = pc.disp.to_bb();
|
||||
/* TODO: fix and re-enable this functionality
|
||||
memset(this->info_board, 0, sizeof(this->info_board));
|
||||
decode_sjis(this->info_board, pc->info_board);
|
||||
memcpy(&this->blocked, pc->blocked, sizeof(uint32_t) * 30);
|
||||
memset(this->auto_reply, 0, sizeof(this->auto_reply));
|
||||
if (pc->auto_reply_enabled) {
|
||||
decode_sjis(this->auto_reply, pc->auto_reply);
|
||||
} else {*/
|
||||
this->auto_reply[0] = 0;
|
||||
//}
|
||||
// TODO: Add these fields to the existing structure so we can parse them
|
||||
// this->info_board = pc.info_board;
|
||||
// this->blocked_senders = pc.blocked_senders;
|
||||
// this->auto_reply = pc.auto_reply;
|
||||
}
|
||||
|
||||
void Player::import(const PSOPlayerDataGC& gc) {
|
||||
this->inventory = gc.inventory;
|
||||
this->disp = gc.disp.to_bb();
|
||||
memset(this->info_board, 0, sizeof(this->info_board));
|
||||
decode_sjis(this->info_board, gc.info_board, 0xAC);
|
||||
memcpy(&this->blocked, gc.blocked, sizeof(uint32_t) * 30);
|
||||
memset(this->auto_reply, 0, sizeof(this->auto_reply));
|
||||
this->info_board = gc.info_board;
|
||||
this->blocked_senders = gc.blocked_senders;
|
||||
if (gc.auto_reply_enabled) {
|
||||
decode_sjis(this->auto_reply, gc.auto_reply, 0xAC);
|
||||
this->auto_reply = gc.auto_reply;
|
||||
} else {
|
||||
this->auto_reply[0] = 0;
|
||||
this->auto_reply.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Player::import(const PSOPlayerDataBB& bb) {
|
||||
// note: we don't copy the inventory and disp here because we already have
|
||||
// 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));
|
||||
strcpy_z(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));
|
||||
this->info_board = bb.info_board;
|
||||
this->blocked_senders = bb.blocked_senders;
|
||||
if (bb.auto_reply_enabled) {
|
||||
strcpy_z(this->auto_reply, bb.auto_reply, 0xAC);
|
||||
this->auto_reply = bb.auto_reply;
|
||||
} else {
|
||||
this->auto_reply[0] = 0;
|
||||
this->auto_reply.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,33 +230,28 @@ PlayerBB Player::export_bb_player_data() const {
|
||||
PlayerBB bb;
|
||||
bb.inventory = this->inventory;
|
||||
bb.disp = this->disp;
|
||||
memset(bb.unknown, 0, 0x10);
|
||||
bb.unknown.clear();
|
||||
bb.option_flags = this->option_flags;
|
||||
memcpy(bb.quest_data1, &this->quest_data1, 0x0208);
|
||||
bb.quest_data1 = this->quest_data1;
|
||||
bb.bank = this->bank;
|
||||
bb.serial_number = this->serial_number;
|
||||
memset(bb.name, 0, sizeof(bb.name));
|
||||
strcpy_z(bb.name, this->disp.name, 24);
|
||||
memset(bb.team_name, 0, sizeof(bb.team_name));
|
||||
strcpy_z(bb.team_name, this->team_name, 16);
|
||||
memset(bb.guild_card_desc, 0, sizeof(bb.guild_card_desc));
|
||||
strcpy_z(bb.guild_card_desc, this->guild_card_desc, 0x58);
|
||||
bb.name = this->disp.name;
|
||||
bb.team_name = this->team_name;
|
||||
bb.guild_card_desc = this->guild_card_desc;
|
||||
bb.reserved1 = 0;
|
||||
bb.reserved2 = 0;
|
||||
bb.section_id = this->disp.section_id;
|
||||
bb.char_class = this->disp.char_class;
|
||||
bb.unknown3 = 0;
|
||||
memcpy(bb.symbol_chats, this->symbol_chats, 0x04E0);
|
||||
memcpy(bb.shortcuts, this->shortcuts, 0x0A40);
|
||||
memset(bb.auto_reply, 0, sizeof(bb.auto_reply));
|
||||
strcpy_z(bb.auto_reply, this->auto_reply, 0xAC);
|
||||
memset(bb.info_board, 0, sizeof(bb.info_board));
|
||||
strcpy_z(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);
|
||||
memset(bb.unknown6, 0, 0x2C);
|
||||
memcpy(bb.quest_data2, &this->quest_data2, 0x0058);
|
||||
bb.symbol_chats = this->symbol_chats;
|
||||
bb.shortcuts = this->shortcuts;
|
||||
bb.auto_reply = this->auto_reply;
|
||||
bb.info_board = this->info_board;
|
||||
bb.unknown5.clear();
|
||||
bb.challenge_data = this->challenge_data;
|
||||
bb.tech_menu_config = this->tech_menu_config;
|
||||
bb.unknown6.clear();
|
||||
bb.quest_data2 = this->quest_data2;
|
||||
bb.key_config = this->key_config;
|
||||
return bb;
|
||||
}
|
||||
@@ -311,78 +278,62 @@ uint32_t compute_guild_card_checksum(const void* vdata, size_t size) {
|
||||
|
||||
void Player::load_account_data(const string& filename) {
|
||||
SavedAccountBB account = load_object_file<SavedAccountBB>(filename);
|
||||
|
||||
if (strcmp(account.signature, ACCOUNT_FILE_SIGNATURE)) {
|
||||
if (account.signature != ACCOUNT_FILE_SIGNATURE) {
|
||||
throw runtime_error("account data header is incorrect");
|
||||
}
|
||||
|
||||
memcpy(&this->blocked, &account.blocked, sizeof(uint32_t) * 30);
|
||||
this->blocked_senders = account.blocked_senders;
|
||||
this->guild_cards = account.guild_cards;
|
||||
this->key_config = account.key_config;
|
||||
this->option_flags = account.option_flags;
|
||||
memcpy(&this->shortcuts, &account.shortcuts, 0x0A40);
|
||||
memcpy(&this->symbol_chats, &account.symbol_chats, 0x04E0);
|
||||
memset(this->team_name, 0, sizeof(this->team_name));
|
||||
strcpy_z(this->team_name, account.team_name, 16);
|
||||
this->shortcuts = account.shortcuts;
|
||||
this->symbol_chats = account.symbol_chats;
|
||||
this->team_name = account.team_name;
|
||||
}
|
||||
|
||||
void Player::save_account_data(const string& filename) const {
|
||||
SavedAccountBB account;
|
||||
|
||||
strncpy(account.signature, ACCOUNT_FILE_SIGNATURE, sizeof(account.signature));
|
||||
memcpy(&account.blocked, &this->blocked, sizeof(uint32_t) * 30);
|
||||
account.signature = ACCOUNT_FILE_SIGNATURE;
|
||||
account.blocked_senders = this->blocked_senders;
|
||||
account.guild_cards = this->guild_cards;
|
||||
account.key_config = this->key_config;
|
||||
account.option_flags = this->option_flags;
|
||||
memcpy(&account.shortcuts, &this->shortcuts, 0x0A40);
|
||||
memcpy(&account.symbol_chats, &this->symbol_chats, 0x04E0);
|
||||
memset(account.team_name, 0, sizeof(account.team_name));
|
||||
strcpy_z(account.team_name, this->team_name, 16);
|
||||
|
||||
account.shortcuts = this->shortcuts;
|
||||
account.symbol_chats = this->symbol_chats;
|
||||
account.team_name = this->team_name;
|
||||
save_file(filename, &account, sizeof(account));
|
||||
}
|
||||
|
||||
void Player::load_player_data(const string& filename) {
|
||||
SavedPlayerBB player = load_object_file<SavedPlayerBB>(filename);
|
||||
|
||||
if (strcmp(player.signature, PLAYER_FILE_SIGNATURE)) {
|
||||
if (player.signature != PLAYER_FILE_SIGNATURE) {
|
||||
throw runtime_error("account data header is incorrect");
|
||||
}
|
||||
|
||||
memset(this->auto_reply, 0, sizeof(this->auto_reply));
|
||||
strcpy_z(this->auto_reply, player.auto_reply, 0xAC);
|
||||
this->auto_reply = player.auto_reply;
|
||||
this->bank = player.bank;
|
||||
memcpy(&this->challenge_data, &player.challenge_data, 0x0140);
|
||||
this->challenge_data = player.challenge_data;
|
||||
this->disp = player.disp;
|
||||
memset(this->guild_card_desc, 0, sizeof(this->guild_card_desc));
|
||||
strcpy_z(this->guild_card_desc, player.guild_card_desc, 0x58);
|
||||
memset(this->info_board, 0, sizeof(this->info_board));
|
||||
strcpy_z(this->info_board, player.info_board, 0xAC);
|
||||
this->guild_card_desc = player.guild_card_desc;
|
||||
this->info_board = player.info_board;
|
||||
this->inventory = player.inventory;
|
||||
memcpy(&this->quest_data1, &player.quest_data1, 0x0208);
|
||||
memcpy(&this->quest_data2, &player.quest_data2, 0x0058);
|
||||
memcpy(&this->tech_menu_config, &player.tech_menu_config, 0x0028);
|
||||
this->quest_data1 = player.quest_data1;
|
||||
this->quest_data2 = player.quest_data2;
|
||||
this->tech_menu_config = player.tech_menu_config;
|
||||
}
|
||||
|
||||
void Player::save_player_data(const string& filename) const {
|
||||
SavedPlayerBB player;
|
||||
|
||||
strncpy(player.signature, PLAYER_FILE_SIGNATURE, sizeof(player.signature));
|
||||
player.signature = PLAYER_FILE_SIGNATURE;
|
||||
player.preview = this->disp.to_preview();
|
||||
memset(player.auto_reply, 0, sizeof(player.auto_reply));
|
||||
strcpy_z(player.auto_reply, this->auto_reply, 0xAC);
|
||||
player.auto_reply = this->auto_reply;
|
||||
player.bank = this->bank;
|
||||
memcpy(&player.challenge_data, &this->challenge_data, 0x0140);
|
||||
player.challenge_data = this->challenge_data;
|
||||
player.disp = this->disp;
|
||||
memset(player.guild_card_desc, 0, sizeof(player.guild_card_desc));
|
||||
strcpy_z(player.guild_card_desc,this->guild_card_desc, 0x58);
|
||||
memset(player.info_board, 0, sizeof(player.info_board));
|
||||
strcpy_z(player.info_board, this->info_board, 0xAC);
|
||||
player.guild_card_desc = this->guild_card_desc;
|
||||
player.info_board = this->info_board;
|
||||
player.inventory = this->inventory;
|
||||
memcpy(&player.quest_data1, &this->quest_data1, 0x0208);
|
||||
memcpy(&player.quest_data2, &this->quest_data2, 0x0058);
|
||||
memcpy(&player.tech_menu_config, &this->tech_menu_config, 0x0028);
|
||||
|
||||
player.quest_data1 = this->quest_data1;
|
||||
player.quest_data2 = this->quest_data2;
|
||||
player.tech_menu_config = this->tech_menu_config;
|
||||
save_file(filename, &player, sizeof(player));
|
||||
}
|
||||
|
||||
@@ -639,9 +590,9 @@ string filename_for_player_bb(const string& username, uint8_t player_index) {
|
||||
static_cast<uint8_t>(player_index + 1));
|
||||
}
|
||||
|
||||
string filename_for_bank_bb(const string& username, const char* bank_name) {
|
||||
string filename_for_bank_bb(const string& username, const std::string& bank_name) {
|
||||
return string_printf("system/players/bank_%s_%s.nsb", username.c_str(),
|
||||
bank_name);
|
||||
bank_name.c_str());
|
||||
}
|
||||
|
||||
string filename_for_class_template_bb(uint8_t char_class) {
|
||||
|
||||
+200
-203
@@ -8,21 +8,23 @@
|
||||
#include <phosg/Encoding.hh>
|
||||
|
||||
#include "Version.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
|
||||
|
||||
// raw item data
|
||||
// TODO: use parray for the fields here
|
||||
struct ItemData {
|
||||
union {
|
||||
uint8_t item_data1[12];
|
||||
uint16_t item_data1w[6];
|
||||
uint32_t item_data1d[3];
|
||||
le_uint16_t item_data1w[6];
|
||||
le_uint32_t item_data1d[3];
|
||||
} __attribute__((packed));
|
||||
uint32_t item_id;
|
||||
union {
|
||||
uint8_t item_data2[4];
|
||||
uint16_t item_data2w[2];
|
||||
uint32_t item_data2d;
|
||||
le_uint16_t item_data2w[2];
|
||||
le_uint32_t item_data2d;
|
||||
} __attribute__((packed));
|
||||
|
||||
uint32_t primary_identifier() const;
|
||||
@@ -32,9 +34,9 @@ struct PlayerBankItem;
|
||||
|
||||
// an item in a player's inventory
|
||||
struct PlayerInventoryItem {
|
||||
uint16_t equip_flags;
|
||||
uint16_t tech_flag;
|
||||
uint32_t game_flags;
|
||||
le_uint16_t equip_flags;
|
||||
le_uint16_t tech_flag;
|
||||
le_uint32_t game_flags;
|
||||
ItemData data;
|
||||
|
||||
PlayerBankItem to_bank_item() const;
|
||||
@@ -43,8 +45,8 @@ struct PlayerInventoryItem {
|
||||
// an item in a player's bank
|
||||
struct PlayerBankItem {
|
||||
ItemData data;
|
||||
uint16_t amount;
|
||||
uint16_t show_flags;
|
||||
le_uint16_t amount;
|
||||
le_uint16_t show_flags;
|
||||
|
||||
PlayerInventoryItem to_inventory_item() const;
|
||||
} __attribute__((packed));
|
||||
@@ -62,8 +64,8 @@ struct PlayerInventory {
|
||||
|
||||
// a player's bank
|
||||
struct PlayerBank {
|
||||
uint32_t num_items;
|
||||
uint32_t meseta;
|
||||
le_uint32_t num_items;
|
||||
le_uint32_t meseta;
|
||||
PlayerBankItem items[200];
|
||||
|
||||
void load(const std::string& filename);
|
||||
@@ -80,13 +82,13 @@ struct PlayerBank {
|
||||
|
||||
// simple player stats
|
||||
struct PlayerStats {
|
||||
uint16_t atp;
|
||||
uint16_t mst;
|
||||
uint16_t evp;
|
||||
uint16_t hp;
|
||||
uint16_t dfp;
|
||||
uint16_t ata;
|
||||
uint16_t lck;
|
||||
le_uint16_t atp;
|
||||
le_uint16_t mst;
|
||||
le_uint16_t evp;
|
||||
le_uint16_t hp;
|
||||
le_uint16_t dfp;
|
||||
le_uint16_t ata;
|
||||
le_uint16_t lck;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PlayerDispDataBB;
|
||||
@@ -94,34 +96,33 @@ struct PlayerDispDataBB;
|
||||
// PC/GC player appearance and stats data
|
||||
struct PlayerDispDataPCGC { // 0xD0 in size
|
||||
PlayerStats stats;
|
||||
uint16_t unknown1;
|
||||
uint32_t unknown2[2];
|
||||
uint32_t level;
|
||||
uint32_t experience;
|
||||
uint32_t meseta;
|
||||
char name[16];
|
||||
uint32_t unknown3[2];
|
||||
uint32_t name_color;
|
||||
parray<uint8_t, 0x0A> unknown_a1;
|
||||
le_uint32_t level;
|
||||
le_uint32_t experience;
|
||||
le_uint32_t meseta;
|
||||
ptext<char, 0x10> name;
|
||||
uint64_t unknown_a2;
|
||||
le_uint32_t name_color;
|
||||
uint8_t extra_model;
|
||||
uint8_t unused[15];
|
||||
uint32_t name_color_checksum;
|
||||
parray<uint8_t, 0x0F> unused;
|
||||
le_uint32_t name_color_checksum;
|
||||
uint8_t section_id;
|
||||
uint8_t char_class;
|
||||
uint8_t v2_flags;
|
||||
uint8_t version;
|
||||
uint32_t v1_flags;
|
||||
uint16_t costume;
|
||||
uint16_t skin;
|
||||
uint16_t face;
|
||||
uint16_t head;
|
||||
uint16_t hair;
|
||||
uint16_t hair_r;
|
||||
uint16_t hair_g;
|
||||
uint16_t hair_b;
|
||||
float proportion_x;
|
||||
float proportion_y;
|
||||
uint8_t config[0x48];
|
||||
uint8_t technique_levels[0x14];
|
||||
le_uint32_t v1_flags;
|
||||
le_uint16_t costume;
|
||||
le_uint16_t skin;
|
||||
le_uint16_t face;
|
||||
le_uint16_t head;
|
||||
le_uint16_t hair;
|
||||
le_uint16_t hair_r;
|
||||
le_uint16_t hair_g;
|
||||
le_uint16_t hair_b;
|
||||
le_float proportion_x;
|
||||
le_float proportion_y;
|
||||
parray<uint8_t, 0x48> config;
|
||||
parray<uint8_t, 0x14> technique_levels;
|
||||
|
||||
void enforce_pc_limits();
|
||||
PlayerDispDataBB to_bb() const;
|
||||
@@ -129,66 +130,64 @@ struct PlayerDispDataPCGC { // 0xD0 in size
|
||||
|
||||
// BB player preview format
|
||||
struct PlayerDispDataBBPreview {
|
||||
uint32_t experience;
|
||||
uint32_t level;
|
||||
char guild_card[16];
|
||||
uint32_t unknown3[2];
|
||||
uint32_t name_color;
|
||||
le_uint32_t experience;
|
||||
le_uint32_t level;
|
||||
ptext<char, 0x10> guild_card;
|
||||
uint64_t unknown_a2;
|
||||
le_uint32_t name_color;
|
||||
uint8_t extra_model;
|
||||
uint8_t unused[15];
|
||||
uint32_t name_color_checksum;
|
||||
parray<uint8_t, 0x0F> unused;
|
||||
le_uint32_t name_color_checksum;
|
||||
uint8_t section_id;
|
||||
uint8_t char_class;
|
||||
uint8_t v2_flags;
|
||||
uint8_t version;
|
||||
uint32_t v1_flags;
|
||||
uint16_t costume;
|
||||
uint16_t skin;
|
||||
uint16_t face;
|
||||
uint16_t head;
|
||||
uint16_t hair;
|
||||
uint16_t hair_r;
|
||||
uint16_t hair_g;
|
||||
uint16_t hair_b;
|
||||
float proportion_x;
|
||||
float proportion_y;
|
||||
char16_t name[16];
|
||||
le_uint32_t v1_flags;
|
||||
le_uint16_t costume;
|
||||
le_uint16_t skin;
|
||||
le_uint16_t face;
|
||||
le_uint16_t head;
|
||||
le_uint16_t hair;
|
||||
le_uint16_t hair_r;
|
||||
le_uint16_t hair_g;
|
||||
le_uint16_t hair_b;
|
||||
le_float proportion_x;
|
||||
le_float proportion_y;
|
||||
ptext<char16_t, 0x10> name;
|
||||
uint32_t play_time;
|
||||
} __attribute__((packed));
|
||||
|
||||
// BB player appearance and stats data
|
||||
struct PlayerDispDataBB {
|
||||
PlayerStats stats;
|
||||
uint16_t unknown1;
|
||||
uint32_t unknown2[2];
|
||||
uint32_t level;
|
||||
uint32_t experience;
|
||||
uint32_t meseta;
|
||||
char guild_card[0x10];
|
||||
uint32_t unknown3[2];
|
||||
uint32_t name_color;
|
||||
parray<uint8_t, 0x0A> unknown_a1;
|
||||
le_uint32_t level;
|
||||
le_uint32_t experience;
|
||||
le_uint32_t meseta;
|
||||
ptext<char, 0x10> guild_card;
|
||||
uint64_t unknown_a2;
|
||||
le_uint32_t name_color;
|
||||
uint8_t extra_model;
|
||||
uint8_t unused[11];
|
||||
uint32_t play_time; // not actually a game field; used only by my server
|
||||
uint32_t name_color_checksum;
|
||||
parray<uint8_t, 0x0F> unused;
|
||||
le_uint32_t name_color_checksum;
|
||||
uint8_t section_id;
|
||||
uint8_t char_class;
|
||||
uint8_t v2_flags;
|
||||
uint8_t version;
|
||||
uint32_t v1_flags;
|
||||
uint16_t costume;
|
||||
uint16_t skin;
|
||||
uint16_t face;
|
||||
uint16_t head;
|
||||
uint16_t hair;
|
||||
uint16_t hair_r;
|
||||
uint16_t hair_g;
|
||||
uint16_t hair_b;
|
||||
float proportion_x;
|
||||
float proportion_y;
|
||||
char16_t name[0x10];
|
||||
uint8_t config[0xE8];
|
||||
uint8_t technique_levels[0x14];
|
||||
le_uint32_t v1_flags;
|
||||
le_uint16_t costume;
|
||||
le_uint16_t skin;
|
||||
le_uint16_t face;
|
||||
le_uint16_t head;
|
||||
le_uint16_t hair;
|
||||
le_uint16_t hair_r;
|
||||
le_uint16_t hair_g;
|
||||
le_uint16_t hair_b;
|
||||
le_float proportion_x;
|
||||
le_float proportion_y;
|
||||
ptext<char16_t, 0x10> name;
|
||||
parray<uint8_t, 0xE8> config;
|
||||
parray<uint8_t, 0x14> technique_levels;
|
||||
|
||||
inline void enforce_pc_limits() { }
|
||||
PlayerDispDataPCGC to_pcgc() const;
|
||||
@@ -199,10 +198,10 @@ struct PlayerDispDataBB {
|
||||
|
||||
|
||||
struct GuildCardGC {
|
||||
uint32_t player_tag;
|
||||
uint32_t serial_number;
|
||||
char name[0x18];
|
||||
char desc[0x6C];
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t serial_number;
|
||||
ptext<char, 0x18> name;
|
||||
ptext<char, 0x6C> desc;
|
||||
uint8_t reserved1; // should be 1
|
||||
uint8_t reserved2; // should be 1
|
||||
uint8_t section_id;
|
||||
@@ -211,10 +210,10 @@ struct GuildCardGC {
|
||||
|
||||
// BB guild card format
|
||||
struct GuildCardBB {
|
||||
uint32_t serial_number;
|
||||
char16_t name[0x18];
|
||||
char16_t teamname[0x10];
|
||||
char16_t desc[0x58];
|
||||
le_uint32_t serial_number;
|
||||
ptext<char16_t, 0x18> name;
|
||||
ptext<char16_t, 0x10> teamname;
|
||||
ptext<char16_t, 0x58> desc;
|
||||
uint8_t reserved1; // should be 1
|
||||
uint8_t reserved2; // should be 1
|
||||
uint8_t section_id;
|
||||
@@ -224,66 +223,66 @@ struct GuildCardBB {
|
||||
// an entry in the BB guild card file
|
||||
struct GuildCardEntryBB {
|
||||
GuildCardBB data;
|
||||
uint8_t unknown[0xB4];
|
||||
parray<uint8_t, 0xB4> unknown;
|
||||
} __attribute__((packed));
|
||||
|
||||
// the format of the BB guild card file
|
||||
struct GuildCardFileBB {
|
||||
uint8_t unknown[0x1F84];
|
||||
parray<uint8_t, 0x1F84> unknown_a1;
|
||||
GuildCardEntryBB entry[0x0068]; // that's 104 of them in decimal
|
||||
uint8_t unknown2[0x01AC];
|
||||
parray<uint8_t, 0x01AC> unknown_a2;
|
||||
} __attribute__((packed));
|
||||
|
||||
// PSOBB key config and team info
|
||||
struct KeyAndTeamConfigBB {
|
||||
uint8_t unknown[0x0114]; // 0000
|
||||
uint8_t key_config[0x016C]; // 0114
|
||||
uint8_t joystick_config[0x0038]; // 0280
|
||||
uint32_t serial_number; // 02B8
|
||||
uint32_t team_id; // 02BC
|
||||
uint32_t team_info[2]; // 02C0
|
||||
uint16_t team_privilege_level; // 02C8
|
||||
uint16_t reserved; // 02CA
|
||||
char16_t team_name[0x0010]; // 02CC
|
||||
uint8_t team_flag[0x0800]; // 02EC
|
||||
uint32_t team_rewards[2]; // 0AEC
|
||||
parray<uint8_t, 0x0114> unknown_a1; // 0000
|
||||
parray<uint8_t, 0x016C> key_config; // 0114
|
||||
parray<uint8_t, 0x0038> joystick_config; // 0280
|
||||
le_uint32_t serial_number; // 02B8
|
||||
le_uint32_t team_id; // 02BC
|
||||
le_uint64_t team_info; // 02C0
|
||||
le_uint16_t team_privilege_level; // 02C8
|
||||
le_uint16_t reserved; // 02CA
|
||||
ptext<char16_t, 0x0010> team_name; // 02CC
|
||||
parray<uint8_t, 0x0800> team_flag; // 02EC
|
||||
le_uint64_t team_rewards; // 0AEC
|
||||
} __attribute__((packed));
|
||||
|
||||
// BB account data
|
||||
struct PlayerAccountDataBB {
|
||||
uint8_t symbol_chats[0x04E0];
|
||||
parray<uint8_t, 0x04E0> symbol_chats;
|
||||
KeyAndTeamConfigBB key_config;
|
||||
GuildCardFileBB guild_cards;
|
||||
uint32_t options;
|
||||
uint8_t shortcuts[0x0A40]; // chat shortcuts (@1FB4 in E7 command)
|
||||
le_uint32_t options;
|
||||
parray<uint8_t, 0x0A40> shortcuts; // chat shortcuts (@1FB4 in E7 command)
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
|
||||
struct PlayerLobbyDataPC {
|
||||
uint32_t player_tag;
|
||||
uint32_t guild_card;
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card;
|
||||
be_uint32_t ip_address;
|
||||
uint32_t client_id;
|
||||
char16_t name[16];
|
||||
le_uint32_t client_id;
|
||||
ptext<char16_t, 0x10> name;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PlayerLobbyDataGC {
|
||||
uint32_t player_tag;
|
||||
uint32_t guild_card;
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card;
|
||||
be_uint32_t ip_address;
|
||||
uint32_t client_id;
|
||||
char name[16];
|
||||
le_uint32_t client_id;
|
||||
ptext<char, 0x10> name;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PlayerLobbyDataBB {
|
||||
uint32_t player_tag;
|
||||
uint32_t guild_card;
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card;
|
||||
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;
|
||||
parray<uint8_t, 0x10> unknown_a1;
|
||||
le_uint32_t client_id;
|
||||
ptext<char16_t, 0x10> name;
|
||||
le_uint32_t unknown2;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
@@ -296,107 +295,105 @@ struct PSOPlayerDataPC { // for command 0x61
|
||||
struct PSOPlayerDataGC { // for command 0x61
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCGC disp;
|
||||
char unknown[0x134];
|
||||
char info_board[0xAC];
|
||||
uint32_t blocked[0x1E];
|
||||
uint32_t auto_reply_enabled;
|
||||
parray<uint8_t, 0x134> unknown;
|
||||
ptext<char, 0xAC> info_board;
|
||||
parray<le_uint32_t, 0x1E> blocked_senders;
|
||||
le_uint32_t auto_reply_enabled;
|
||||
char auto_reply[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PSOPlayerDataBB { // for command 0x61
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataBB disp;
|
||||
char unused[0x174];
|
||||
char16_t info_board[0xAC];
|
||||
uint32_t blocked[0x1E];
|
||||
uint32_t auto_reply_enabled;
|
||||
ptext<char, 0x174> unused;
|
||||
ptext<char16_t, 0xAC> info_board;
|
||||
parray<le_uint32_t, 0x1E> blocked_senders;
|
||||
le_uint32_t auto_reply_enabled;
|
||||
char16_t auto_reply[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
// complete BB player data format (used in E7 command)
|
||||
struct PlayerBB {
|
||||
PlayerInventory inventory; // 0000 // player
|
||||
PlayerDispDataBB disp; // 034C // player
|
||||
uint8_t unknown[0x0010]; // 04DC //
|
||||
uint32_t option_flags; // 04EC // account
|
||||
uint8_t quest_data1[0x0208]; // 04F0 // player
|
||||
PlayerBank bank; // 06F8 // player
|
||||
uint32_t serial_number; // 19C0 // player
|
||||
char16_t name[0x18]; // 19C4 // player
|
||||
char16_t team_name[0x10]; // 19C4 // player
|
||||
char16_t guild_card_desc[0x58]; // 1A14 // player
|
||||
uint8_t reserved1; // 1AC4 // player
|
||||
uint8_t reserved2; // 1AC5 // player
|
||||
uint8_t section_id; // 1AC6 // player
|
||||
uint8_t char_class; // 1AC7 // player
|
||||
uint32_t unknown3; // 1AC8 //
|
||||
uint8_t symbol_chats[0x04E0]; // 1ACC // account
|
||||
uint8_t shortcuts[0x0A40]; // 1FAC // account
|
||||
char16_t auto_reply[0x00AC]; // 29EC // player
|
||||
char16_t info_board[0x00AC]; // 2B44 // player
|
||||
uint8_t unknown5[0x001C]; // 2C9C //
|
||||
uint8_t challenge_data[0x0140]; // 2CB8 // player
|
||||
uint8_t tech_menu_config[0x0028]; // 2DF8 // player
|
||||
uint8_t unknown6[0x002C]; // 2E20 //
|
||||
uint8_t quest_data2[0x0058]; // 2E4C // player
|
||||
KeyAndTeamConfigBB key_config; // 2EA4 // account
|
||||
} __attribute__((packed)); // total size: 39A0
|
||||
PlayerInventory inventory; // 0000 // player
|
||||
PlayerDispDataBB disp; // 034C // player
|
||||
parray<uint8_t, 0x0010> unknown; // 04DC //
|
||||
le_uint32_t option_flags; // 04EC // account
|
||||
parray<uint8_t, 0x0208> quest_data1; // 04F0 // player
|
||||
PlayerBank bank; // 06F8 // player
|
||||
le_uint32_t serial_number; // 19C0 // player
|
||||
ptext<char16_t, 0x18> name; // 19C4 // player
|
||||
ptext<char16_t, 0x10> team_name; // 19C4 // player
|
||||
ptext<char16_t, 0x58> guild_card_desc; // 1A14 // player
|
||||
uint8_t reserved1; // 1AC4 // player
|
||||
uint8_t reserved2; // 1AC5 // player
|
||||
uint8_t section_id; // 1AC6 // player
|
||||
uint8_t char_class; // 1AC7 // player
|
||||
le_uint32_t unknown3; // 1AC8 //
|
||||
parray<uint8_t, 0x04E0> symbol_chats; // 1ACC // account
|
||||
parray<uint8_t, 0x0A40> shortcuts; // 1FAC // account
|
||||
ptext<char16_t, 0x00AC> auto_reply; // 29EC // player
|
||||
ptext<char16_t, 0x00AC> info_board; // 2B44 // player
|
||||
parray<uint8_t, 0x001C> unknown5; // 2C9C //
|
||||
parray<uint8_t, 0x0140> challenge_data; // 2CB8 // player
|
||||
parray<uint8_t, 0x0028> tech_menu_config; // 2DF8 // player
|
||||
parray<uint8_t, 0x002C> unknown6; // 2E20 //
|
||||
parray<uint8_t, 0x0058> quest_data2; // 2E4C // player
|
||||
KeyAndTeamConfigBB key_config; // 2EA4 // account
|
||||
} __attribute__((packed)); // total size: 39A0
|
||||
|
||||
|
||||
|
||||
struct SavedPlayerBB { // .nsc file format
|
||||
char signature[0x40];
|
||||
ptext<char, 0x40> signature;
|
||||
PlayerDispDataBBPreview preview;
|
||||
|
||||
char16_t auto_reply[0x00AC];
|
||||
PlayerBank bank;
|
||||
uint8_t challenge_data[0x0140];
|
||||
PlayerDispDataBB disp;
|
||||
char16_t guild_card_desc[0x58];
|
||||
char16_t info_board[0x00AC];
|
||||
PlayerInventory inventory;
|
||||
uint8_t quest_data1[0x0208];
|
||||
uint8_t quest_data2[0x0058];
|
||||
uint8_t tech_menu_config[0x0028];
|
||||
ptext<char16_t, 0x00AC> auto_reply;
|
||||
PlayerBank bank;
|
||||
parray<uint8_t, 0x0140> challenge_data;
|
||||
PlayerDispDataBB disp;
|
||||
ptext<char16_t, 0x58> guild_card_desc;
|
||||
ptext<char16_t, 0x00AC> info_board;
|
||||
PlayerInventory inventory;
|
||||
parray<uint8_t, 0x0208> quest_data1;
|
||||
parray<uint8_t, 0x0058> quest_data2;
|
||||
parray<uint8_t, 0x0028> tech_menu_config;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct SavedAccountBB { // .nsa file format
|
||||
char signature[0x40];
|
||||
uint32_t blocked[0x001E];
|
||||
GuildCardFileBB guild_cards;
|
||||
KeyAndTeamConfigBB key_config;
|
||||
uint32_t option_flags;
|
||||
uint8_t shortcuts[0x0A40];
|
||||
uint8_t symbol_chats[0x04E0];
|
||||
char16_t team_name[0x0010];
|
||||
ptext<char, 0x40> signature;
|
||||
parray<le_uint32_t, 0x001E> blocked_senders;
|
||||
GuildCardFileBB guild_cards;
|
||||
KeyAndTeamConfigBB key_config;
|
||||
le_uint32_t option_flags;
|
||||
parray<uint8_t, 0x0A40> shortcuts;
|
||||
parray<uint8_t, 0x04E0> symbol_chats;
|
||||
ptext<char16_t, 0x0010> team_name;
|
||||
} __attribute__((packed));
|
||||
|
||||
// complete player info stored by the server
|
||||
struct Player {
|
||||
uint32_t loaded_from_shipgate_time;
|
||||
|
||||
char16_t auto_reply[0x00AC]; // player
|
||||
PlayerBank bank; // player
|
||||
char bank_name[0x20];
|
||||
uint32_t blocked[0x001E]; // account
|
||||
uint8_t challenge_data[0x0140]; // player
|
||||
PlayerDispDataBB disp; // player
|
||||
uint8_t ep3_config[0x2408];
|
||||
char16_t guild_card_desc[0x58]; // player
|
||||
GuildCardFileBB guild_cards; // account
|
||||
PlayerInventoryItem identify_result;
|
||||
char16_t info_board[0x00AC]; // player
|
||||
PlayerInventory inventory; // player
|
||||
KeyAndTeamConfigBB key_config; // account
|
||||
uint32_t option_flags; // account
|
||||
uint8_t quest_data1[0x0208]; // player
|
||||
uint8_t quest_data2[0x0058]; // player
|
||||
uint32_t serial_number;
|
||||
std::vector<ItemData> current_shop_contents;
|
||||
uint8_t shortcuts[0x0A40]; // account
|
||||
uint8_t symbol_chats[0x04E0]; // account
|
||||
char16_t team_name[0x0010]; // account
|
||||
uint8_t tech_menu_config[0x0028]; // player
|
||||
le_uint32_t loaded_from_shipgate_time;
|
||||
ptext<char16_t, 0x00AC> auto_reply; // player
|
||||
PlayerBank bank; // player
|
||||
ptext<char, 0x0020> bank_name; // not saved
|
||||
parray<le_uint32_t, 0x001E> blocked_senders; // account
|
||||
parray<uint8_t, 0x0140> challenge_data; // player
|
||||
PlayerDispDataBB disp; // player
|
||||
parray<uint8_t, 0x2408> ep3_config; // not saved
|
||||
ptext<char16_t, 0x0058> guild_card_desc; // player
|
||||
GuildCardFileBB guild_cards; // account
|
||||
PlayerInventoryItem identify_result; // not saved
|
||||
ptext<char16_t, 0x00AC> info_board; // player
|
||||
PlayerInventory inventory; // player
|
||||
KeyAndTeamConfigBB key_config; // account
|
||||
le_uint32_t option_flags; // account
|
||||
parray<uint8_t, 0x0208> quest_data1; // player
|
||||
parray<uint8_t, 0x0058> quest_data2; // player
|
||||
le_uint32_t serial_number; // account identifier
|
||||
std::vector<ItemData> current_shop_contents; // not saved
|
||||
parray<uint8_t, 0x0A40> shortcuts; // account
|
||||
parray<uint8_t, 0x04E0> symbol_chats; // account
|
||||
ptext<char16_t, 0x0010> team_name; // account
|
||||
parray<uint8_t, 0x0028> tech_menu_config; // player
|
||||
|
||||
void load_player_data(const std::string& filename);
|
||||
void save_player_data(const std::string& filename) const;
|
||||
@@ -419,7 +416,7 @@ struct Player {
|
||||
uint32_t compute_guild_card_checksum(const void* data, size_t size);
|
||||
|
||||
std::string filename_for_player_bb(const std::string& username, uint8_t player_index);
|
||||
std::string filename_for_bank_bb(const std::string& username, const char* bank_name);
|
||||
std::string filename_for_bank_bb(const std::string& username, const std::string& bank_name);
|
||||
std::string filename_for_class_template_bb(uint8_t char_class);
|
||||
std::string filename_for_account_bb(const std::string& username);
|
||||
|
||||
|
||||
+23
-25
@@ -198,14 +198,14 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
||||
should_close_unlinked_session = true;
|
||||
} else {
|
||||
const auto* cmd = reinterpret_cast<const C_Login_PC_GC_9D_9E*>(data.data());
|
||||
uint32_t serial_number = strtoul(cmd->serial_number, nullptr, 16);
|
||||
uint32_t serial_number = strtoul(cmd->serial_number.c_str(), nullptr, 16);
|
||||
try {
|
||||
license = this->server->state->license_manager->verify_gc(
|
||||
serial_number, cmd->access_key, nullptr);
|
||||
serial_number, cmd->access_key.c_str(), nullptr);
|
||||
sub_version = cmd->sub_version;
|
||||
character_name = cmd->name;
|
||||
memcpy(&client_config, &cmd->cfg, offsetof(ClientConfig, unused_bb_only));
|
||||
memset(client_config.unused_bb_only, 0xFF, sizeof(client_config.unused_bb_only));
|
||||
client_config.unused_bb_only.clear(0xFF);
|
||||
} catch (const exception& e) {
|
||||
log(ERROR, "[ProxyServer] Unlinked client has no valid license");
|
||||
should_close_unlinked_session = true;
|
||||
@@ -306,7 +306,6 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
enable_chat_filter(true),
|
||||
lobby_players(12),
|
||||
lobby_client_id(0) {
|
||||
memset(this->remote_client_config_data, 0, 0x20);
|
||||
memset(&this->next_destination, 0, sizeof(this->next_destination));
|
||||
struct sockaddr_in* dest_sin = reinterpret_cast<struct sockaddr_in*>(&this->next_destination);
|
||||
dest_sin->sin_family = AF_INET;
|
||||
@@ -625,14 +624,13 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
if (command == 0x17) {
|
||||
C_VerifyLicense_GC_DB cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
snprintf(cmd.serial_number, sizeof(cmd.serial_number), "%08" PRIX32 "",
|
||||
cmd.serial_number = string_printf("%08" PRIX32 "",
|
||||
this->license->serial_number);
|
||||
memcpy(cmd.access_key, this->license->access_key, 0x10);
|
||||
cmd.access_key = this->license->access_key;
|
||||
cmd.sub_version = this->sub_version;
|
||||
snprintf(cmd.serial_number2, sizeof(cmd.serial_number2), "%08" PRIX32 "",
|
||||
this->license->serial_number);
|
||||
memcpy(cmd.access_key2, this->license->access_key, 0x10);
|
||||
memcpy(cmd.password, this->license->gc_password, 0x0C);
|
||||
cmd.serial_number2 = cmd.serial_number;
|
||||
cmd.access_key2 = cmd.access_key;
|
||||
cmd.password = this->license->gc_password;
|
||||
send_command(this->server_bev.get(), this->version,
|
||||
this->server_output_crypt.get(), 0xDB, 0, &cmd, sizeof(cmd),
|
||||
name.c_str());
|
||||
@@ -656,15 +654,14 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
cmd.guild_card_number = this->guild_card_number;
|
||||
}
|
||||
cmd.sub_version = this->sub_version;
|
||||
cmd.unused2[1] = 1;
|
||||
snprintf(cmd.serial_number, sizeof(cmd.serial_number), "%08" PRIX32 "",
|
||||
cmd.unused2.data()[1] = 1;
|
||||
cmd.serial_number = string_printf("%08" PRIX32 "",
|
||||
this->license->serial_number);
|
||||
memcpy(cmd.access_key, this->license->access_key, 0x10);
|
||||
snprintf(cmd.serial_number2, sizeof(cmd.serial_number2), "%08" PRIX32 "",
|
||||
this->license->serial_number);
|
||||
memcpy(cmd.access_key2, this->license->access_key, 0x10);
|
||||
strncpy(cmd.name, this->character_name.c_str(), sizeof(cmd.name) - 1);
|
||||
memcpy(&cmd.cfg, this->remote_client_config_data, 0x20);
|
||||
cmd.access_key = this->license->access_key;
|
||||
cmd.serial_number2 = cmd.serial_number;
|
||||
cmd.access_key2 = cmd.access_key;
|
||||
cmd.name = this->character_name;
|
||||
memcpy(&cmd.cfg, this->remote_client_config_data.data(), 0x20);
|
||||
|
||||
// If there's a guild card number, a shorter 9E is sent that ends
|
||||
// right after the client config data
|
||||
@@ -701,14 +698,14 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
// server init command). We simulate that bug here.
|
||||
// If there was previously a guild card number, assume we got the
|
||||
// lobby server init text instead of the port map init text.
|
||||
memcpy(
|
||||
this->remote_client_config_data,
|
||||
memcpy(this->remote_client_config_data.data(),
|
||||
had_guild_card_number
|
||||
? "t Lobby Server. Copyright SEGA E"
|
||||
: "t Port Map. Copyright SEGA Enter",
|
||||
0x20);
|
||||
memcpy(this->remote_client_config_data, &cmd->cfg,
|
||||
min<size_t>(data.size() - sizeof(S_UpdateClientConfig_DC_PC_GC_04), 0x20));
|
||||
this->remote_client_config_data.bytes());
|
||||
memcpy(this->remote_client_config_data.data(), &cmd->cfg,
|
||||
min<size_t>(data.size() - sizeof(S_UpdateClientConfig_DC_PC_GC_04),
|
||||
this->remote_client_config_data.bytes()));
|
||||
|
||||
// If the guild card number was not set, pretend (to the server)
|
||||
// that this is the first 04 command the client has received. The
|
||||
@@ -798,7 +795,8 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
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());
|
||||
cmd->filename.c_str(),
|
||||
is_download_quest ? "download" : "online", now());
|
||||
for (size_t x = 0; x < output_filename.size(); x++) {
|
||||
if (output_filename[x] < 0x20 || output_filename[x] > 0x7E || output_filename[x] == '/') {
|
||||
output_filename[x] = '_';
|
||||
@@ -833,7 +831,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
sf = &this->saving_files.at(cmd->filename);
|
||||
} catch (const out_of_range&) {
|
||||
log(WARNING, "[ProxyServer/%08" PRIX32 "] Received data for non-open file %s",
|
||||
this->license->serial_number, cmd->filename);
|
||||
this->license->serial_number, cmd->filename.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public:
|
||||
std::string character_name;
|
||||
|
||||
uint32_t guild_card_number;
|
||||
uint8_t remote_client_config_data[0x20];
|
||||
parray<uint8_t, 0x20> remote_client_config_data;
|
||||
ClientConfig newserv_client_config;
|
||||
bool suppress_newserv_commands;
|
||||
bool enable_chat_filter;
|
||||
|
||||
+18
-18
@@ -77,9 +77,9 @@ struct PSOQuestHeaderDC { // same for dc v1 and v2, thankfully
|
||||
uint8_t is_download;
|
||||
uint8_t unknown1;
|
||||
uint16_t quest_number; // 0xFFFF for challenge quests
|
||||
char name[0x20];
|
||||
char short_description[0x80];
|
||||
char long_description[0x120];
|
||||
ptext<char, 0x20> name;
|
||||
ptext<char, 0x80> short_description;
|
||||
ptext<char, 0x120> long_description;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PSOQuestHeaderPC {
|
||||
@@ -90,9 +90,9 @@ struct PSOQuestHeaderPC {
|
||||
uint8_t is_download;
|
||||
uint8_t unknown1;
|
||||
uint16_t quest_number; // 0xFFFF for challenge quests
|
||||
char16_t name[0x20];
|
||||
char16_t short_description[0x80];
|
||||
char16_t long_description[0x120];
|
||||
ptext<char16_t, 0x20> name;
|
||||
ptext<char16_t, 0x80> short_description;
|
||||
ptext<char16_t, 0x120> long_description;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PSOQuestHeaderGC {
|
||||
@@ -104,21 +104,21 @@ struct PSOQuestHeaderGC {
|
||||
uint8_t unknown1;
|
||||
uint8_t quest_number;
|
||||
uint8_t episode; // 1 = ep2. apparently some quests have 0xFF here, which means ep1 (?)
|
||||
char name[0x20];
|
||||
char short_description[0x80];
|
||||
char long_description[0x120];
|
||||
ptext<char, 0x20> name;
|
||||
ptext<char, 0x80> short_description;
|
||||
ptext<char, 0x120> long_description;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PSOQuestHeaderGCEpisode3 {
|
||||
// there's actually a lot of other important stuff in here but I'm lazy. it
|
||||
// looks like map data, cutscene data, and maybe special cards used during
|
||||
// the quest
|
||||
uint8_t unused[0x1DF0];
|
||||
char name[0x14];
|
||||
char location[0x14];
|
||||
char location2[0x3C];
|
||||
char description[0x190];
|
||||
uint8_t unused2[0x3A34];
|
||||
parray<uint8_t, 0x1DF0> unknown_a1;
|
||||
ptext<char, 0x14> name;
|
||||
ptext<char, 0x14> location;
|
||||
ptext<char, 0x3C> location2;
|
||||
ptext<char, 0x190> description;
|
||||
parray<uint8_t, 0x3A34> unknown_a2;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PSOQuestHeaderBB {
|
||||
@@ -132,9 +132,9 @@ struct PSOQuestHeaderBB {
|
||||
uint8_t max_players;
|
||||
uint8_t joinable_in_progress;
|
||||
uint8_t unknown;
|
||||
char16_t name[0x20];
|
||||
char16_t short_description[0x80];
|
||||
char16_t long_description[0x120];
|
||||
ptext<char16_t, 0x20> name;
|
||||
ptext<char16_t, 0x80> short_description;
|
||||
ptext<char16_t, 0x120> long_description;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
|
||||
+81
-80
@@ -159,7 +159,7 @@ void process_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
c->player.load_account_data("system/blueburst/default.nsa");
|
||||
}
|
||||
|
||||
sprintf(c->player.bank_name, "player%d", c->bb_player_index + 1);
|
||||
c->player.bank_name = string_printf("player%d", c->bb_player_index + 1);
|
||||
|
||||
string player_filename = filename_for_player_bb(c->license->username,
|
||||
c->bb_player_index);
|
||||
@@ -198,7 +198,10 @@ void process_disconnect(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
}
|
||||
|
||||
if (c->version == GameVersion::BB) {
|
||||
c->player.disp.play_time += ((now() - c->play_time_begin) / 1000000);
|
||||
// TODO: Make a timer event for each connected player that saves their data
|
||||
// periodically, not only when they disconnect
|
||||
// TODO: Track play time somewhere
|
||||
// c->player.disp.play_time += ((now() - c->play_time_begin) / 1000000);
|
||||
string account_filename = filename_for_account_bb(c->license->username);
|
||||
string player_filename = filename_for_player_bb(c->license->username,
|
||||
c->bb_player_index);
|
||||
@@ -218,10 +221,10 @@ void process_verify_license_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // DB
|
||||
const auto& cmd = check_size_t<C_VerifyLicense_GC_DB>(data);
|
||||
|
||||
uint32_t serial_number = strtoul(cmd.serial_number, nullptr, 16);
|
||||
uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16);
|
||||
try {
|
||||
c->license = s->license_manager->verify_gc(serial_number, cmd.access_key,
|
||||
cmd.password);
|
||||
c->license = s->license_manager->verify_gc(serial_number,
|
||||
cmd.access_key.c_str(), cmd.password.c_str());
|
||||
} catch (const exception& e) {
|
||||
if (!s->allow_unregistered_users) {
|
||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||
@@ -230,7 +233,7 @@ void process_verify_license_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
return;
|
||||
} else {
|
||||
auto l = LicenseManager::create_license_gc(serial_number,
|
||||
cmd.access_key, cmd.password, true);
|
||||
cmd.access_key.c_str(), cmd.password.c_str(), true);
|
||||
s->license_manager->add(l);
|
||||
c->license = l;
|
||||
}
|
||||
@@ -244,14 +247,14 @@ void process_login_a_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 9A
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9A>(data);
|
||||
|
||||
uint32_t serial_number = strtoul(cmd.serial_number, nullptr, 16);
|
||||
uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16);
|
||||
try {
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = s->license_manager->verify_gc(serial_number, cmd.access_key,
|
||||
nullptr);
|
||||
c->license = s->license_manager->verify_gc(serial_number,
|
||||
cmd.access_key.c_str(), nullptr);
|
||||
} else {
|
||||
c->license = s->license_manager->verify_pc(serial_number, cmd.access_key,
|
||||
nullptr);
|
||||
c->license = s->license_manager->verify_pc(serial_number,
|
||||
cmd.access_key.c_str(), nullptr);
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
// The client should have sent a different command containing the password
|
||||
@@ -273,14 +276,14 @@ void process_login_c_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
c->flags |= flags_for_version(c->version, cmd.sub_version);
|
||||
|
||||
uint32_t serial_number = strtoul(cmd.serial_number, nullptr, 16);
|
||||
uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16);
|
||||
try {
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = s->license_manager->verify_gc(serial_number, cmd.access_key,
|
||||
cmd.password);
|
||||
c->license = s->license_manager->verify_gc(serial_number,
|
||||
cmd.access_key.c_str(), cmd.password.c_str());
|
||||
} else {
|
||||
c->license = s->license_manager->verify_pc(serial_number, cmd.access_key,
|
||||
cmd.password);
|
||||
c->license = s->license_manager->verify_pc(serial_number,
|
||||
cmd.access_key.c_str(), cmd.password.c_str());
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
if (!s->allow_unregistered_users) {
|
||||
@@ -292,10 +295,10 @@ void process_login_c_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
shared_ptr<License> l;
|
||||
if (c->version == GameVersion::GC) {
|
||||
l = LicenseManager::create_license_gc(serial_number,
|
||||
cmd.access_key, cmd.password, true);
|
||||
cmd.access_key.c_str(), cmd.password.c_str(), true);
|
||||
} else {
|
||||
l = LicenseManager::create_license_pc(serial_number,
|
||||
cmd.access_key, cmd.password, true);
|
||||
cmd.access_key.c_str(), cmd.password.c_str(), true);
|
||||
}
|
||||
s->license_manager->add(l);
|
||||
c->license = l;
|
||||
@@ -313,14 +316,14 @@ void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
c->flags |= flags_for_version(c->version, cmd.sub_version);
|
||||
|
||||
uint32_t serial_number = strtoul(cmd.serial_number, nullptr, 16);
|
||||
uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16);
|
||||
try {
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = s->license_manager->verify_gc(serial_number, cmd.access_key,
|
||||
nullptr);
|
||||
c->license = s->license_manager->verify_gc(serial_number,
|
||||
cmd.access_key.c_str(), nullptr);
|
||||
} else {
|
||||
c->license = s->license_manager->verify_pc(serial_number, cmd.access_key,
|
||||
nullptr);
|
||||
c->license = s->license_manager->verify_pc(serial_number,
|
||||
cmd.access_key.c_str(), nullptr);
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
// See comment in 9A handler about why we do this even if unregistered users
|
||||
@@ -357,7 +360,8 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
c->flags |= flags_for_version(c->version, 0);
|
||||
|
||||
try {
|
||||
c->license = s->license_manager->verify_bb(cmd.username, cmd.password);
|
||||
c->license = s->license_manager->verify_bb(
|
||||
cmd.username.c_str(), cmd.password.c_str());
|
||||
} catch (const exception& e) {
|
||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||
send_message_box(c, message.c_str());
|
||||
@@ -770,21 +774,18 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
|
||||
if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES)) {
|
||||
char16_t password[0x10];
|
||||
memset(password, 0, sizeof(password));
|
||||
ptext<char16_t, 0x10> password;
|
||||
if (data.size() > sizeof(C_MenuSelection)) {
|
||||
if (uses_unicode) {
|
||||
size_t max_chars = (data.size() - sizeof(C_MenuSelection)) / sizeof(char16_t);
|
||||
strcpy_z(password, cmd.password.pcbb,
|
||||
min<size_t>(max_chars, countof(password)));
|
||||
password.assign(cmd.password.pcbb, max_chars);
|
||||
} else {
|
||||
size_t max_chars = (data.size() - sizeof(C_MenuSelection)) / sizeof(char);
|
||||
decode_sjis(password, cmd.password.dcgc,
|
||||
min<size_t>(max_chars, countof(password)));
|
||||
password = decode_sjis(cmd.password.dcgc, max_chars);
|
||||
}
|
||||
}
|
||||
|
||||
if (game->password[0] && char16ncmp(game->password, password, 0x10)) {
|
||||
if (!game->password.empty() && (game->password != password)) {
|
||||
send_message_box(c, u"$C6Incorrect password.");
|
||||
break;
|
||||
}
|
||||
@@ -1036,7 +1037,7 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
switch (c->version) {
|
||||
case GameVersion::PC:
|
||||
check_size(data.size(), sizeof(PSOPlayerDataPC),
|
||||
sizeof(PSOPlayerDataPC) + sizeof(char16_t) * countof(c->player.auto_reply));
|
||||
sizeof(PSOPlayerDataPC) + sizeof(char16_t) * c->player.auto_reply.size());
|
||||
c->player.import(*reinterpret_cast<const PSOPlayerDataPC*>(data.data()));
|
||||
break;
|
||||
case GameVersion::GC:
|
||||
@@ -1045,13 +1046,13 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// TODO: import Episode 3 data somewhere
|
||||
} else {
|
||||
check_size(data.size(), sizeof(PSOPlayerDataGC),
|
||||
sizeof(PSOPlayerDataGC) + sizeof(char) * countof(c->player.auto_reply));
|
||||
sizeof(PSOPlayerDataGC) + sizeof(char) * c->player.auto_reply.size());
|
||||
}
|
||||
c->player.import(*reinterpret_cast<const PSOPlayerDataGC*>(data.data()));
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
check_size(data.size(), sizeof(PSOPlayerDataBB),
|
||||
sizeof(PSOPlayerDataBB) + sizeof(char16_t) * countof(c->player.auto_reply));
|
||||
sizeof(PSOPlayerDataBB) + sizeof(char16_t) * c->player.auto_reply.size());
|
||||
c->player.import(*reinterpret_cast<const PSOPlayerDataBB*>(data.data()));
|
||||
break;
|
||||
default:
|
||||
@@ -1148,7 +1149,7 @@ void process_chat_generic(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
continue;
|
||||
}
|
||||
send_chat_message(l->clients[x], c->license->serial_number,
|
||||
c->player.disp.name, processed_text.c_str());
|
||||
c->player.disp.name.data(), processed_text.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1157,7 +1158,7 @@ void process_chat_pc_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 06
|
||||
const auto& cmd = check_size_t<C_Chat_06>(data, sizeof(C_Chat_06), 0xFFFF);
|
||||
u16string text(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t));
|
||||
text.resize(char16len(text.c_str()));
|
||||
strip_trailing_zeroes(text);
|
||||
process_chat_generic(s, c, text);
|
||||
}
|
||||
|
||||
@@ -1248,13 +1249,13 @@ void process_create_character_bb(shared_ptr<ServerState> s, shared_ptr<Client> c
|
||||
send_message_box(c, u"$C6You are not logged in.");
|
||||
return;
|
||||
}
|
||||
if (c->player.disp.name[0]) {
|
||||
if (!c->player.disp.name.empty()) {
|
||||
send_message_box(c, u"$C6You have already loaded a character.");
|
||||
return;
|
||||
}
|
||||
|
||||
c->bb_player_index = cmd.player_index;
|
||||
snprintf(c->player.bank_name, 0x20, "player%" PRIu32, cmd.player_index + 1);
|
||||
c->player.bank_name = string_printf("player%" PRIu32, cmd.player_index + 1);
|
||||
string player_filename = filename_for_player_bb(c->license->username, cmd.player_index);
|
||||
string bank_filename = filename_for_bank_bb(c->license->username, c->player.bank_name);
|
||||
string template_filename = filename_for_class_template_bb(cmd.preview.char_class);
|
||||
@@ -1305,27 +1306,27 @@ void process_change_account_data_bb(shared_ptr<ServerState>, shared_ptr<Client>
|
||||
break;
|
||||
case 0x02ED:
|
||||
check_size(data.size(), sizeof(cmd->symbol_chats));
|
||||
memcpy(c->player.symbol_chats, cmd->symbol_chats, 0x04E0);
|
||||
c->player.symbol_chats = cmd->symbol_chats;
|
||||
break;
|
||||
case 0x03ED:
|
||||
check_size(data.size(), sizeof(cmd->chat_shortcuts));
|
||||
memcpy(c->player.shortcuts, cmd->chat_shortcuts, 0x0A40);
|
||||
c->player.shortcuts = cmd->chat_shortcuts;
|
||||
break;
|
||||
case 0x04ED:
|
||||
check_size(data.size(), sizeof(cmd->key_config));
|
||||
memcpy(&c->player.key_config.key_config, cmd->key_config, 0x016C);
|
||||
c->player.key_config.key_config = cmd->key_config;
|
||||
break;
|
||||
case 0x05ED:
|
||||
check_size(data.size(), sizeof(cmd->pad_config));
|
||||
memcpy(&c->player.key_config.joystick_config, cmd->pad_config, 0x0038);
|
||||
c->player.key_config.joystick_config = cmd->pad_config;
|
||||
break;
|
||||
case 0x06ED:
|
||||
check_size(data.size(), sizeof(cmd->tech_menu));
|
||||
memcpy(&c->player.tech_menu_config, cmd->tech_menu, 0x0028);
|
||||
c->player.tech_menu_config = cmd->tech_menu;
|
||||
break;
|
||||
case 0x07ED:
|
||||
check_size(data.size(), sizeof(cmd->customize));
|
||||
memcpy(c->player.disp.config, cmd->customize, 0xE8);
|
||||
c->player.disp.config = cmd->customize;
|
||||
break;
|
||||
default:
|
||||
throw invalid_argument("unknown account command");
|
||||
@@ -1386,22 +1387,21 @@ void process_simple_mail(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
// If the sender is blocked, don't forward the mail
|
||||
for (size_t y = 0; y < 30; y++) {
|
||||
if (target->player.blocked[y] == c->license->serial_number) {
|
||||
if (target->player.blocked_senders.data()[y] == c->license->serial_number) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the target has auto-reply enabled, send the autoreply
|
||||
if (target->player.auto_reply[0]) {
|
||||
if (!target->player.auto_reply.empty()) {
|
||||
send_simple_mail(c, target->license->serial_number,
|
||||
target->player.disp.name, target->player.auto_reply);
|
||||
target->player.disp.name.c_str(), target->player.auto_reply.c_str());
|
||||
}
|
||||
|
||||
// Forward the message
|
||||
string message(cmd.text, strnlen(cmd.text, sizeof(cmd.text) / sizeof(cmd.text[0])));
|
||||
u16string u16message = decode_sjis(message);
|
||||
send_simple_mail(target, c->license->serial_number, c->player.disp.name,
|
||||
u16message.data());
|
||||
u16string u16message = decode_sjis(cmd.text);
|
||||
send_simple_mail(target, c->license->serial_number,
|
||||
c->player.disp.name.c_str(), u16message.c_str());
|
||||
}
|
||||
|
||||
|
||||
@@ -1418,33 +1418,31 @@ void process_info_board_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
template <typename CharT>
|
||||
void process_write_info_board_t(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // D9
|
||||
check_size(data.size(), 0, countof(c->player.info_board) * sizeof(CharT));
|
||||
memset(c->player.info_board, 0, sizeof(c->player.info_board));
|
||||
strncpy_t(c->player.info_board, reinterpret_cast<const CharT*>(data.data()),
|
||||
min<size_t>(countof(c->player.info_board), data.size() / sizeof(CharT)));
|
||||
check_size(data.size(), 0, c->player.info_board.size() * sizeof(CharT));
|
||||
c->player.info_board.assign(
|
||||
reinterpret_cast<const CharT*>(data.data()),
|
||||
data.size() / sizeof(CharT));
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
void process_set_auto_reply_t(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // C7
|
||||
check_size(data.size(), 0, countof(c->player.auto_reply) * sizeof(CharT));
|
||||
memset(c->player.auto_reply, 0, sizeof(c->player.auto_reply));
|
||||
strncpy_t(c->player.auto_reply, reinterpret_cast<const CharT*>(data.data()),
|
||||
min<size_t>(countof(c->player.auto_reply), data.size() / sizeof(CharT)));
|
||||
check_size(data.size(), 0, c->player.auto_reply.size() * sizeof(CharT));
|
||||
c->player.auto_reply.assign(
|
||||
reinterpret_cast<const CharT*>(data.data()),
|
||||
data.size() / sizeof(CharT));
|
||||
}
|
||||
|
||||
void process_disable_auto_reply(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // C8
|
||||
check_size(data.size(), 0);
|
||||
memset(c->player.auto_reply, 0, sizeof(c->player.auto_reply));
|
||||
c->player.auto_reply.clear();
|
||||
}
|
||||
|
||||
void process_set_blocked_list(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
void process_set_blocked_senders_list(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // C6
|
||||
const auto& cmd = check_size_t<C_SetBlockedSenders_C6>(data);
|
||||
for (size_t x = 0; x < countof(cmd.blocked_senders); x++) {
|
||||
c->player.blocked[x] = cmd.blocked_senders[x];
|
||||
}
|
||||
c->player.blocked_senders = cmd.blocked_senders;
|
||||
}
|
||||
|
||||
|
||||
@@ -1501,8 +1499,8 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
}
|
||||
|
||||
shared_ptr<Lobby> game(new Lobby());
|
||||
strcpy_z(game->name, name, countof(game->name));
|
||||
strcpy_z(game->password, password, countof(game->name));
|
||||
game->name = name;
|
||||
game->password = password;
|
||||
game->version = c->version;
|
||||
game->section_id = c->override_section_id >= 0
|
||||
? c->override_section_id : c->player.disp.section_id;
|
||||
@@ -1548,8 +1546,9 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
try {
|
||||
auto filename = string_printf(
|
||||
"system/blueburst/map/%c%hhu%zu%" PRIu32 "%" PRIu32 ".dat",
|
||||
*type_char, game->episode, x, game->variations[x * 2],
|
||||
game->variations[(x * 2) + 1]);
|
||||
*type_char, game->episode, x,
|
||||
game->variations.data()[x * 2].load(),
|
||||
game->variations.data()[(x * 2) + 1].load());
|
||||
game->enemies = load_map(filename.c_str(), game->episode,
|
||||
game->difficulty, bp_subtable, false);
|
||||
break;
|
||||
@@ -1566,8 +1565,9 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
for (size_t x = 0; x < 0x10; x++) {
|
||||
auto filename = string_printf(
|
||||
"system/blueburst/map/m%hhu%zu%" PRIu32 "%" PRIu32 ".dat",
|
||||
game->episode, x, game->variations[x * 2],
|
||||
game->variations[(x * 2) + 1]);
|
||||
game->episode, x,
|
||||
game->variations.data()[x * 2].load(),
|
||||
game->variations.data()[(x * 2) + 1].load());
|
||||
game->enemies = load_map(filename.c_str(), game->episode,
|
||||
game->difficulty, bp_subtable, false);
|
||||
}
|
||||
@@ -1583,11 +1583,11 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
|
||||
if (variation_maxes) {
|
||||
for (size_t x = 0; x < 0x20; x++) {
|
||||
game->variations[x] = random_int(0, variation_maxes[x] - 1);
|
||||
game->variations.data()[x] = random_int(0, variation_maxes[x] - 1);
|
||||
}
|
||||
} else {
|
||||
for (size_t x = 0; x < 0x20; x++) {
|
||||
game->variations[x] = 0;
|
||||
game->variations.data()[x] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1598,7 +1598,7 @@ void process_create_game_pc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // C1
|
||||
const auto& cmd = check_size_t<C_CreateGame_PC_C1>(data);
|
||||
|
||||
auto game = create_game_generic(s, c, cmd.name, cmd.password, 1,
|
||||
auto game = create_game_generic(s, c, cmd.name.c_str(), cmd.password.c_str(), 1,
|
||||
cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, 0);
|
||||
|
||||
s->add_lobby(game);
|
||||
@@ -1639,7 +1639,7 @@ void process_create_game_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // C1
|
||||
const auto& cmd = check_size_t<C_CreateGame_BB_C1>(data);
|
||||
|
||||
auto game = create_game_generic(s, c, cmd.name, cmd.password,
|
||||
auto game = create_game_generic(s, c, cmd.name.c_str(), cmd.password.c_str(),
|
||||
cmd.episode, cmd.difficulty, cmd.battle_mode, cmd.challenge_mode,
|
||||
cmd.solo_mode);
|
||||
|
||||
@@ -1657,7 +1657,7 @@ void process_lobby_name_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
if (!l) {
|
||||
throw invalid_argument("client not in any lobby");
|
||||
}
|
||||
send_lobby_name(c, l->name);
|
||||
send_lobby_name(c, l->name.c_str());
|
||||
}
|
||||
|
||||
void process_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
@@ -1712,7 +1712,8 @@ and operated completely independently.\n\
|
||||
\n\
|
||||
License check: ";
|
||||
try {
|
||||
c->license = s->license_manager->verify_bb(cmd.username, cmd.password);
|
||||
c->license = s->license_manager->verify_bb(
|
||||
cmd.username.c_str(), cmd.password.c_str());
|
||||
message += u"OK";
|
||||
} catch (const exception& e) {
|
||||
message += decode_sjis(e.what());
|
||||
@@ -1817,7 +1818,7 @@ static process_command_t dc_handlers[0x100] = {
|
||||
|
||||
// C0
|
||||
nullptr, process_create_game_dc_gc, nullptr, nullptr,
|
||||
nullptr, nullptr, process_set_blocked_list, process_set_auto_reply_t<char>,
|
||||
nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t<char>,
|
||||
process_disable_auto_reply, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
@@ -1900,7 +1901,7 @@ static process_command_t pc_handlers[0x100] = {
|
||||
|
||||
// C0
|
||||
nullptr, process_create_game_pc, nullptr, nullptr,
|
||||
nullptr, nullptr, process_set_blocked_list, process_set_auto_reply_t<char16_t>,
|
||||
nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t<char16_t>,
|
||||
process_disable_auto_reply, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
@@ -1985,7 +1986,7 @@ static process_command_t gc_handlers[0x100] = {
|
||||
|
||||
// C0
|
||||
process_choice_search, process_create_game_dc_gc, nullptr, nullptr,
|
||||
nullptr, nullptr, process_set_blocked_list, process_set_auto_reply_t<char>,
|
||||
nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t<char>,
|
||||
process_disable_auto_reply, process_game_command, process_ep3_server_data_request, process_game_command,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
@@ -2072,7 +2073,7 @@ static process_command_t bb_handlers[0x100] = {
|
||||
|
||||
// C0
|
||||
nullptr, process_create_game_bb, nullptr, nullptr,
|
||||
nullptr, nullptr, process_set_blocked_list, process_set_auto_reply_t<char16_t>,
|
||||
nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t<char16_t>,
|
||||
process_disable_auto_reply, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ static void process_subcommand_send_guild_card(shared_ptr<ServerState>,
|
||||
if (count < 0x25) {
|
||||
return;
|
||||
}
|
||||
decode_sjis(c->player.guild_card_desc,
|
||||
c->player.guild_card_desc = decode_sjis(
|
||||
reinterpret_cast<const char*>(&p[9].byte[0]), 0x58);
|
||||
}
|
||||
|
||||
|
||||
+69
-115
@@ -156,12 +156,11 @@ S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
|
||||
uint32_t server_key,
|
||||
uint32_t client_key) {
|
||||
S_ServerInit_DC_GC_02_17 cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
|
||||
strcpy(cmd.copyright, initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright);
|
||||
cmd.copyright = initial_connection
|
||||
? dc_port_map_copyright : dc_lobby_server_copyright;
|
||||
cmd.server_key = server_key;
|
||||
cmd.client_key = client_key;
|
||||
strcpy(cmd.after_message, anti_copyright);
|
||||
cmd.after_message = anti_copyright;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@@ -194,17 +193,16 @@ void send_server_init_dc_pc_gc(shared_ptr<Client> c,
|
||||
|
||||
void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
S_ServerInit_BB_03 cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
strcpy(cmd.copyright, bb_game_server_copyright);
|
||||
random_data(cmd.server_key, 0x30);
|
||||
random_data(cmd.client_key, 0x30);
|
||||
strcpy(cmd.after_message, anti_copyright);
|
||||
cmd.copyright = bb_game_server_copyright;
|
||||
random_data(cmd.server_key.data(), cmd.server_key.bytes());
|
||||
random_data(cmd.client_key.data(), cmd.client_key.bytes());
|
||||
cmd.after_message = anti_copyright;
|
||||
send_command(c, 0x03, 0x00, cmd);
|
||||
|
||||
c->crypt_out.reset(new PSOBBEncryption(s->default_key_file, cmd.server_key,
|
||||
sizeof(cmd.server_key)));
|
||||
c->crypt_in.reset(new PSOBBEncryption(s->default_key_file, cmd.client_key,
|
||||
sizeof(cmd.client_key)));
|
||||
c->crypt_out.reset(new PSOBBEncryption(s->default_key_file,
|
||||
cmd.server_key.data(), cmd.server_key.bytes()));
|
||||
c->crypt_in.reset(new PSOBBEncryption(s->default_key_file,
|
||||
cmd.client_key.data(), cmd.client_key.bytes()));
|
||||
}
|
||||
|
||||
void send_server_init_patch(shared_ptr<Client> c) {
|
||||
@@ -212,8 +210,7 @@ void send_server_init_patch(shared_ptr<Client> c) {
|
||||
uint32_t client_key = random_object<uint32_t>();
|
||||
|
||||
S_ServerInit_Patch_02 cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
strcpy(cmd.copyright, patch_server_copyright);
|
||||
cmd.copyright = patch_server_copyright;
|
||||
cmd.server_key = server_key;
|
||||
cmd.client_key = client_key;
|
||||
send_command(c, 0x02, 0x00, cmd);
|
||||
@@ -246,7 +243,6 @@ void send_server_init(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// for non-BB clients, updates the client's guild card and security data
|
||||
void send_update_client_config(shared_ptr<Client> c) {
|
||||
S_UpdateClientConfig_DC_PC_GC_04 cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.guild_card_number = c->license->serial_number;
|
||||
cmd.cfg = c->export_config();
|
||||
@@ -265,7 +261,6 @@ void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
|
||||
void send_pc_gc_split_reconnect(shared_ptr<Client> c, uint32_t address,
|
||||
uint16_t pc_port, uint16_t gc_port) {
|
||||
S_ReconnectSplit_19 cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.pc_address = address;
|
||||
cmd.pc_port = pc_port;
|
||||
cmd.gc_command = 0x19;
|
||||
@@ -352,7 +347,7 @@ void send_stream_file_bb(shared_ptr<Client> c) {
|
||||
|
||||
uint32_t buffer_offset = 0;
|
||||
for (size_t x = 0; x < entry_count; x++) {
|
||||
auto filename = string_printf("system/blueburst/%s", entries[x].filename);
|
||||
auto filename = string_printf("system/blueburst/%s", entries[x].filename.c_str());
|
||||
auto file_data = file_cache.get(filename);
|
||||
|
||||
size_t file_data_remaining = file_data->size();
|
||||
@@ -366,11 +361,12 @@ void send_stream_file_bb(shared_ptr<Client> c) {
|
||||
}
|
||||
memcpy(&chunk_cmd.data[buffer_offset],
|
||||
file_data->data() + file_data->size() - file_data_remaining, read_size);
|
||||
// TODO: We probably should clear the rest of the buffer on the last chunk
|
||||
buffer_offset += read_size;
|
||||
file_data_remaining -= read_size;
|
||||
|
||||
if (buffer_offset == 0x6800) {
|
||||
// note: the client sends 0x03EB in response to these, but we'll just
|
||||
// Note: the client sends 0x03EB in response to these, but we'll just
|
||||
// ignore them because we don't need any of the contents
|
||||
send_command(c, 0x02EB, 0x00000000, chunk_cmd);
|
||||
buffer_offset = 0;
|
||||
@@ -399,9 +395,7 @@ void send_complete_player_bb(shared_ptr<Client> c) {
|
||||
// patch functions
|
||||
|
||||
void send_check_directory_patch(shared_ptr<Client> c, const char* dir) {
|
||||
S_CheckDirectory_Patch_09 cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
strncpy_t(cmd.name, dir, countof(cmd.name));
|
||||
S_CheckDirectory_Patch_09 cmd = {dir};
|
||||
send_command(c, 0x09, 0x00, cmd);
|
||||
}
|
||||
|
||||
@@ -416,7 +410,7 @@ void send_text(shared_ptr<Client> c, StringWriter& w, uint16_t command,
|
||||
string data = encode_sjis(text);
|
||||
add_color(w, data.c_str(), data.size());
|
||||
} else {
|
||||
add_color(w, text, char16len(text));
|
||||
add_color(w, text, text_strlen_t(text));
|
||||
}
|
||||
while (w.str().size() & 3) {
|
||||
w.put_u8(0);
|
||||
@@ -493,12 +487,11 @@ void send_chat_message(shared_ptr<Client> c, uint32_t from_serial_number,
|
||||
void send_simple_mail_gc(std::shared_ptr<Client> c, uint32_t from_serial_number,
|
||||
const char16_t* from_name, const char16_t* text) {
|
||||
SC_SimpleMail_GC_81 cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.from_serial_number = from_serial_number;
|
||||
encode_sjis(cmd.from_name, from_name, sizeof(cmd.from_name) / sizeof(cmd.from_name[0]));
|
||||
cmd.from_name = from_name;
|
||||
cmd.to_serial_number = c->license->serial_number;
|
||||
encode_sjis(cmd.text, text, sizeof(cmd.text) / sizeof(cmd.text[0]));
|
||||
cmd.text = text;
|
||||
send_command(c, 0x81, 0x00, cmd);
|
||||
}
|
||||
|
||||
@@ -523,12 +516,10 @@ void send_info_board_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
if (!c.get()) {
|
||||
continue;
|
||||
}
|
||||
entries.emplace_back();
|
||||
auto& e = entries.back();
|
||||
memset(&e, 0, sizeof(e));
|
||||
strncpy_t(e.name, c->player.disp.name, countof(e.name));
|
||||
strncpy_t(e.message, c->player.info_board, countof(e.message));
|
||||
add_color_inplace(e.message, countof(e.message));
|
||||
auto& e = entries.emplace_back();
|
||||
e.name = c->player.disp.name;
|
||||
e.message = c->player.info_board;
|
||||
add_color_inplace(e.message);
|
||||
}
|
||||
send_command(c, 0xD8, entries.size(), entries);
|
||||
}
|
||||
@@ -554,7 +545,6 @@ void send_card_search_result_t(
|
||||
shared_ptr<Client> result,
|
||||
shared_ptr<Lobby> result_lobby) {
|
||||
S_GuildCardSearchResult<CommandHeaderT, CharT> cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.searcher_serial_number = c->license->serial_number;
|
||||
cmd.result_serial_number = result->license->serial_number;
|
||||
@@ -570,18 +560,18 @@ void send_card_search_result_t(
|
||||
cmd.reconnect_command.unused = 0;
|
||||
|
||||
auto encoded_server_name = encode_sjis(s->name);
|
||||
string location_string;
|
||||
if (result_lobby->is_game()) {
|
||||
string encoded_lobby_name = encode_sjis(result_lobby->name);
|
||||
snprintf(cmd.location_string, sizeof(cmd.location_string),
|
||||
"%s,Block 00,,%s", encoded_lobby_name.c_str(), encoded_server_name.c_str());
|
||||
location_string = string_printf("%s,Block 00,,%s",
|
||||
encoded_lobby_name.c_str(), encoded_server_name.c_str());
|
||||
} else {
|
||||
snprintf(cmd.location_string, sizeof(cmd.location_string), "Block 00,,%s",
|
||||
encoded_server_name.c_str());
|
||||
location_string = string_printf("Block 00,,%s", encoded_server_name.c_str());
|
||||
}
|
||||
cmd.location_string = location_string;
|
||||
cmd.menu_id = LOBBY_MENU_ID;
|
||||
cmd.lobby_id = result->lobby_id;
|
||||
memset(cmd.unused, 0, sizeof(cmd.unused));
|
||||
strncpy_t(cmd.name, result->player.disp.name, countof(cmd.name));
|
||||
cmd.name = result->player.disp.name;
|
||||
|
||||
send_command(c, 0x41, 0x00, cmd);
|
||||
}
|
||||
@@ -605,26 +595,22 @@ void send_card_search_result(
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// CommandSendGuildCard: generates a guild card for the source player and sends it to the destination player
|
||||
|
||||
|
||||
void send_guild_card_gc(shared_ptr<Client> c, shared_ptr<Client> source) {
|
||||
S_SendGuildCard_GC cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.subcommand = 0x06;
|
||||
cmd.subsize = 0x25;
|
||||
cmd.unused = 0x0000;
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.reserved1 = 1;
|
||||
cmd.reserved2 = 1;
|
||||
|
||||
cmd.serial_number = source->license->serial_number;
|
||||
encode_sjis(cmd.name, source->player.disp.name, countof(cmd.name));
|
||||
cmd.name = source->player.disp.name;
|
||||
remove_language_marker_inplace(cmd.name);
|
||||
encode_sjis(cmd.desc, source->player.guild_card_desc, countof(cmd.desc));
|
||||
cmd.desc = source->player.guild_card_desc;
|
||||
cmd.section_id = source->player.disp.section_id;
|
||||
cmd.char_class = source->player.disp.char_class;
|
||||
|
||||
send_command(c, 0x62, c->lobby_client_id, cmd);
|
||||
}
|
||||
|
||||
@@ -636,16 +622,12 @@ void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) {
|
||||
cmd.unused = 0x0000;
|
||||
cmd.reserved1 = 1;
|
||||
cmd.reserved2 = 1;
|
||||
|
||||
cmd.serial_number = source->license->serial_number;
|
||||
strcpy_z(cmd.name, source->player.disp.name, countof(cmd.name));
|
||||
remove_language_marker_inplace(cmd.name);
|
||||
strcpy_z(cmd.team_name, source->player.team_name, countof(cmd.team_name));
|
||||
remove_language_marker_inplace(cmd.team_name);
|
||||
strcpy_z(cmd.desc, source->player.guild_card_desc, countof(cmd.desc));
|
||||
cmd.name = remove_language_marker(source->player.disp.name);
|
||||
cmd.team_name = remove_language_marker(source->player.team_name);
|
||||
cmd.desc = source->player.guild_card_desc;
|
||||
cmd.section_id = source->player.disp.section_id;
|
||||
cmd.char_class = source->player.disp.char_class;
|
||||
|
||||
send_command(c, 0x62, c->lobby_client_id, cmd);
|
||||
}
|
||||
|
||||
@@ -673,14 +655,12 @@ void send_menu_t(
|
||||
bool is_info_menu) {
|
||||
|
||||
vector<EntryT> entries;
|
||||
entries.emplace_back();
|
||||
{
|
||||
auto& e = entries.back();
|
||||
memset(&e, 0, sizeof(e));
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = menu_id;
|
||||
e.item_id = 0xFFFFFFFF;
|
||||
e.flags = 0x0004;
|
||||
strncpy_t(e.text, menu_name, countof(e.text));
|
||||
e.text = menu_name;
|
||||
}
|
||||
|
||||
for (const auto& item : items) {
|
||||
@@ -692,14 +672,11 @@ void send_menu_t(
|
||||
((item.flags & MenuItemFlag::REQUIRES_MESSAGE_BOXES) && (c->flags & ClientFlag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.emplace_back();
|
||||
auto& e = entries.back();
|
||||
memset(&e, 0, sizeof(e));
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = menu_id;
|
||||
e.item_id = item.item_id;
|
||||
e.flags = (c->version == GameVersion::BB) ? 0x0004 : 0x0F04;
|
||||
strncpy_t(e.text, item.name.c_str(), countof(e.text));
|
||||
e.text = item.name;
|
||||
}
|
||||
|
||||
send_command(c, is_info_menu ? 0x1F : 0x07, entries.size() - 1, entries);
|
||||
@@ -722,43 +699,38 @@ template <typename CharT>
|
||||
void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
vector<S_GameMenuEntry<CharT>> entries;
|
||||
{
|
||||
entries.emplace_back();
|
||||
auto& e = entries.back();
|
||||
memset(&e, 0, sizeof(e));
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = GAME_MENU_ID;
|
||||
e.game_id = 0x00000000;
|
||||
e.difficulty_tag = 0x00;
|
||||
e.num_players = 0x00;
|
||||
strncpy_t(e.name, s->name.c_str(), countof(e.name));
|
||||
e.name = s->name;
|
||||
e.episode = 0x00;
|
||||
e.flags = 0x04;
|
||||
}
|
||||
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
||||
if (!l->is_game()) {
|
||||
if (!l->is_game() || (l->version != c->version)) {
|
||||
continue;
|
||||
}
|
||||
if (l->version != c->version) {
|
||||
continue;
|
||||
}
|
||||
if (!(l->flags & LobbyFlag::EPISODE_3) != !(c->flags & ClientFlag::EPISODE_3_GAMES)) {
|
||||
bool l_is_ep3 = !!(l->flags & LobbyFlag::EPISODE_3);
|
||||
bool c_is_ep3 = !!(c->flags & ClientFlag::EPISODE_3_GAMES);
|
||||
if (l_is_ep3 != c_is_ep3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.emplace_back();
|
||||
auto& e = entries.back();
|
||||
auto& e = entries.emplace_back();
|
||||
memset(&e, 0, sizeof(e));
|
||||
e.menu_id = GAME_MENU_ID;
|
||||
e.game_id = l->lobby_id;
|
||||
e.difficulty_tag = ((l->flags & LobbyFlag::EPISODE_3)
|
||||
? 0x0A : (l->difficulty + 0x22));
|
||||
e.difficulty_tag = (l_is_ep3 ? 0x0A : (l->difficulty + 0x22));
|
||||
e.num_players = l->count_clients();
|
||||
e.episode = ((c->version == GameVersion::BB) ? (l->max_clients << 4) : 0) | l->episode;
|
||||
if (l->flags & LobbyFlag::EPISODE_3) {
|
||||
e.flags = (l->password[0] ? 2 : 0);
|
||||
e.flags = (l->password.empty() ? 0 : 2);
|
||||
} else {
|
||||
e.flags = ((l->episode << 6) | ((l->mode % 3) << 4) | (l->password[0] ? 2 : 0)) | ((l->mode == 3) ? 4 : 0);
|
||||
e.flags = ((l->episode << 6) | ((l->mode % 3) << 4) | (l->password.empty() ? 0 : 2)) | ((l->mode == 3) ? 4 : 0);
|
||||
}
|
||||
strncpy_t(e.name, l->name, countof(e.name));
|
||||
e.name = l->name;
|
||||
}
|
||||
|
||||
send_command(c, 0x08, entries.size() - 1, entries);
|
||||
@@ -782,14 +754,12 @@ void send_quest_menu_t(
|
||||
bool is_download_menu) {
|
||||
vector<EntryT> entries;
|
||||
for (const auto& quest : quests) {
|
||||
entries.emplace_back();
|
||||
auto& e = entries.back();
|
||||
memset(&e, 0, sizeof(e));
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = menu_id;
|
||||
e.item_id = quest->quest_id;
|
||||
strncpy_t(e.name, quest->name.c_str(), countof(e.name));
|
||||
strncpy_t(e.short_desc, quest->short_description.c_str(), countof(e.short_desc));
|
||||
add_color_inplace(e.short_desc, countof(e.short_desc));
|
||||
e.name = quest->name;
|
||||
e.short_desc = quest->short_description;
|
||||
add_color_inplace(e.short_desc);
|
||||
}
|
||||
send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
|
||||
}
|
||||
@@ -802,14 +772,12 @@ void send_quest_menu_t(
|
||||
bool is_download_menu) {
|
||||
vector<EntryT> entries;
|
||||
for (const auto& item : items) {
|
||||
entries.emplace_back();
|
||||
auto& e = entries.back();
|
||||
memset(&e, 0, sizeof(e));
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = menu_id;
|
||||
e.item_id = item.item_id;
|
||||
strncpy_t(e.name, item.name.c_str(), 0x20);
|
||||
strncpy_t(e.short_desc, item.description.c_str(), 0x70);
|
||||
add_color_inplace(e.short_desc, 0x70);
|
||||
e.name = item.name;
|
||||
e.short_desc = item.description;
|
||||
add_color_inplace(e.short_desc);
|
||||
}
|
||||
send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
|
||||
}
|
||||
@@ -853,9 +821,7 @@ void send_lobby_list(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
if ((l->flags & LobbyFlag::EPISODE_3) && !(c->flags & ClientFlag::EPISODE_3_GAMES)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.emplace_back();
|
||||
auto& e = entries.back();
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = LOBBY_MENU_ID;
|
||||
e.item_id = l->lobby_id;
|
||||
e.unused = 0;
|
||||
@@ -872,10 +838,10 @@ void send_lobby_list(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
template <typename LobbyDataT, typename DispDataT>
|
||||
void send_join_game_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
S_JoinGame<LobbyDataT, DispDataT> cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
|
||||
cmd.variations = l->variations;
|
||||
|
||||
size_t player_count = 0;
|
||||
memcpy(cmd.variations, l->variations, sizeof(cmd.variations));
|
||||
for (size_t x = 0; x < 4; x++) {
|
||||
if (l->clients[x]) {
|
||||
cmd.lobby_data[x].player_tag = 0x00010000;
|
||||
@@ -883,8 +849,7 @@ void send_join_game_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
// See comment in send_join_lobby_t about Episode III behavior here
|
||||
cmd.lobby_data[x].ip_address = 0x7F000001;
|
||||
cmd.lobby_data[x].client_id = c->lobby_client_id;
|
||||
strncpy_t(cmd.lobby_data[x].name, l->clients[x]->player.disp.name,
|
||||
countof(cmd.lobby_data[x].name));
|
||||
cmd.lobby_data[x].name = l->clients[x]->player.disp.name;
|
||||
if (l->flags & LobbyFlag::EPISODE_3) {
|
||||
cmd.players_ep3[x].inventory = l->clients[x]->player.inventory;
|
||||
cmd.players_ep3[x].disp = convert_player_disp_data<DispDataT>(
|
||||
@@ -927,7 +892,6 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
}
|
||||
} else {
|
||||
command = joining_client ? 0x68 : 0x67;
|
||||
|
||||
}
|
||||
|
||||
uint8_t lobby_type = (l->type > 14) ? (l->block - 1) : l->type;
|
||||
@@ -949,7 +913,6 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
}
|
||||
|
||||
S_JoinLobby<LobbyDataT, DispDataT> cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.client_id = c->lobby_client_id;
|
||||
cmd.leader_id = l->leader_id;
|
||||
cmd.disable_udp = 0x01;
|
||||
@@ -972,7 +935,6 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
size_t used_entries = 0;
|
||||
for (const auto& lc : lobby_clients) {
|
||||
auto& e = cmd.entries[used_entries++];
|
||||
memset(&e.lobby_data, 0, sizeof(e.lobby_data));
|
||||
e.lobby_data.player_tag = 0x00010000;
|
||||
e.lobby_data.guild_card = lc->license->serial_number;
|
||||
// There's a strange behavior (bug? "feature"?) in Episode 3 where the start
|
||||
@@ -981,8 +943,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
// to avoid this behavior.
|
||||
e.lobby_data.ip_address = 0x7F000001;
|
||||
e.lobby_data.client_id = lc->lobby_client_id;
|
||||
strncpy_t(e.lobby_data.name, lc->player.disp.name,
|
||||
countof(e.lobby_data.name));
|
||||
e.lobby_data.name = lc->player.disp.name;
|
||||
e.inventory = lc->player.inventory;
|
||||
e.disp = convert_player_disp_data<DispDataT>(lc->player.disp);
|
||||
if (c->version == GameVersion::PC) {
|
||||
@@ -1059,9 +1020,7 @@ void send_arrow_update(shared_ptr<Lobby> l) {
|
||||
if (!l->clients[x]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.emplace_back();
|
||||
auto& e = entries.back();
|
||||
auto& e = entries.emplace_back();
|
||||
e.player_tag = 0x00010000;
|
||||
e.serial_number = l->clients[x]->license->serial_number;
|
||||
e.arrow_color = l->clients[x]->lobby_arrow_color;
|
||||
@@ -1092,16 +1051,14 @@ void send_player_stats_change(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
vector<PSOSubcommand> subs;
|
||||
while (amount > 0) {
|
||||
{
|
||||
subs.emplace_back();
|
||||
auto& sub = subs.back();
|
||||
auto& sub = subs.emplace_back();
|
||||
sub.byte[0] = 0x9A;
|
||||
sub.byte[1] = 0x02;
|
||||
sub.byte[2] = c->lobby_client_id;
|
||||
sub.byte[3] = 0x00;
|
||||
}
|
||||
{
|
||||
subs.emplace_back();
|
||||
auto& sub = subs.back();
|
||||
auto& sub = subs.emplace_back();
|
||||
sub.byte[0] = 0x00;
|
||||
sub.byte[1] = 0x00;
|
||||
sub.byte[2] = stat;
|
||||
@@ -1288,7 +1245,6 @@ void send_ep3_card_list_update(shared_ptr<Client> c) {
|
||||
StringWriter w;
|
||||
w.put_u32l(file_data->size());
|
||||
w.write(*file_data);
|
||||
w.str().resize((w.str().size() + 3) & (~3));
|
||||
|
||||
send_command(c, 0xB8, 0x00, w.str());
|
||||
}
|
||||
@@ -1340,11 +1296,10 @@ void send_quest_open_file_t(
|
||||
bool is_download_quest,
|
||||
bool is_ep3_quest) {
|
||||
CommandT cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.flags = 2 + is_ep3_quest;
|
||||
cmd.file_size = file_size;
|
||||
strncpy_t(cmd.name, filename.c_str(), countof(cmd.name));
|
||||
strncpy_t(cmd.filename, filename.c_str(), countof(cmd.filename));
|
||||
cmd.name = filename.c_str();
|
||||
cmd.filename = filename.c_str();
|
||||
send_command(c, is_download_quest ? 0xA6 : 0x44, 0x00, cmd);
|
||||
}
|
||||
|
||||
@@ -1360,8 +1315,7 @@ void send_quest_file_chunk(
|
||||
}
|
||||
|
||||
S_WriteFile_13_A7 cmd;
|
||||
memset(cmd.filename, 0, countof(cmd.filename));
|
||||
strncpy_t(cmd.filename, filename, countof(cmd.filename));
|
||||
cmd.filename = filename;
|
||||
memcpy(cmd.data, data, size);
|
||||
if (size < 0x400) {
|
||||
memset(&cmd.data[size], 0, 0x400 - size);
|
||||
|
||||
+4
-4
@@ -149,25 +149,25 @@ Proxy commands (these will only work when exactly one client is connected):\n\
|
||||
if (token.size() >= 29) {
|
||||
throw invalid_argument("username too long");
|
||||
}
|
||||
strcpy(l->username, token.c_str() + 9);
|
||||
l->username = token.substr(9);
|
||||
|
||||
} else if (starts_with(token, "bb-password=")) {
|
||||
if (token.size() >= 32) {
|
||||
throw invalid_argument("bb-password too long");
|
||||
}
|
||||
strcpy(l->bb_password, token.c_str() + 12);
|
||||
l->bb_password = token.substr(12);
|
||||
|
||||
} else if (starts_with(token, "gc-password=")) {
|
||||
if (token.size() > 20) {
|
||||
throw invalid_argument("gc-password too long");
|
||||
}
|
||||
strcpy(l->gc_password, token.c_str() + 12);
|
||||
l->gc_password = token.substr(12);
|
||||
|
||||
} else if (starts_with(token, "access-key=")) {
|
||||
if (token.size() > 23) {
|
||||
throw invalid_argument("access-key is too long");
|
||||
}
|
||||
strcpy(l->access_key, token.c_str() + 11);
|
||||
l->access_key = token.substr(11);
|
||||
|
||||
} else if (starts_with(token, "serial=")) {
|
||||
l->serial_number = stoul(token.substr(7));
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ ServerState::ServerState()
|
||||
(is_ep3_only ? LobbyFlag::EPISODE_3 : 0);
|
||||
l->block = x + 1;
|
||||
l->type = x;
|
||||
strcpy_z(l->name, lobby_name.c_str(), 0x24);
|
||||
l->name = lobby_name;
|
||||
l->max_clients = 12;
|
||||
this->add_lobby(l);
|
||||
|
||||
|
||||
+75
-140
@@ -30,20 +30,14 @@ int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t char16len(const char16_t* s) {
|
||||
size_t x;
|
||||
for (x = 0; s[x] != 0; x++);
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static vector<char16_t> unicode_to_sjis_table_data;
|
||||
static vector<char16_t> sjis_to_unicode_table_data;
|
||||
|
||||
static void load_sjis_tables() {
|
||||
unicode_to_sjis_table_data.resize(0x10000);
|
||||
sjis_to_unicode_table_data.resize(0x10000);
|
||||
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");
|
||||
@@ -75,166 +69,107 @@ 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)) {
|
||||
*(dest++) = table[*(source++)];
|
||||
};
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
void decode_sjis(char16_t* dest, const char* source, size_t max) {
|
||||
const auto& table = sjis_to_unicode_table();
|
||||
while (*source && (--max)) {
|
||||
char16_t src_char = *(source++);
|
||||
if (src_char & 0x80) {
|
||||
src_char = (src_char << 8) | *(source++);
|
||||
if ((src_char & 0xFF) == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
*(dest++) = table[src_char];
|
||||
};
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
std::string encode_sjis(const char16_t* src, size_t src_count) {
|
||||
const auto& table = unicode_to_sjis_table();
|
||||
|
||||
const char16_t* src_end = src + src_count;
|
||||
string ret;
|
||||
for (; *src && (src_count > 0); src_count--) {
|
||||
ret.push_back(table[*(src++)]);
|
||||
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);
|
||||
}
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
void encode_sjis(
|
||||
char* dest,
|
||||
size_t dest_count,
|
||||
const char16_t* src,
|
||||
size_t src_count) {
|
||||
const auto& table = unicode_to_sjis_table();
|
||||
|
||||
if (dest_count == 0) {
|
||||
throw logic_error("cannot encode into zero-length buffer");
|
||||
}
|
||||
|
||||
const char16_t* src_end = src + src_count;
|
||||
const char* dest_end = dest + (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;
|
||||
} else {
|
||||
*(dest++) = translated_c & 0xFF;
|
||||
}
|
||||
} else {
|
||||
*(dest++) = translated_c & 0xFF;
|
||||
}
|
||||
}
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
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_count > 0)) {
|
||||
char16_t src_char = *(src++);
|
||||
src_count--;
|
||||
while ((src != src_end) && *src) {
|
||||
uint16_t src_char = *(src++);
|
||||
if (src_char & 0x80) {
|
||||
if (src_count == 0) {
|
||||
return ret;
|
||||
if (src == src_end) {
|
||||
throw runtime_error("incomplete extended character");
|
||||
}
|
||||
src_char = (src_char << 8) | *(src++);
|
||||
if ((src_char & 0xFF) == 0) {
|
||||
return ret;
|
||||
throw runtime_error("incomplete extended character");
|
||||
}
|
||||
src_count--;
|
||||
}
|
||||
ret.push_back(table[src_char]);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string encode_sjis(const std::u16string& source) {
|
||||
const auto& table = unicode_to_sjis_table();
|
||||
string ret;
|
||||
for (char16_t ch : source) {
|
||||
ret.push_back(table[ch]);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::u16string decode_sjis(const std::string& source) {
|
||||
void decode_sjis(
|
||||
char16_t* dest,
|
||||
size_t dest_count,
|
||||
const char* src,
|
||||
size_t src_count) {
|
||||
const auto& table = sjis_to_unicode_table();
|
||||
u16string ret;
|
||||
for (size_t x = 0; x < source.size();) {
|
||||
char16_t src_char = source[x++];
|
||||
|
||||
if (dest_count == 0) {
|
||||
throw logic_error("cannot decode into zero-length buffer");
|
||||
}
|
||||
|
||||
const char* src_end = src + src_count;
|
||||
const char16_t* dest_end = dest + (dest_count - 1);
|
||||
while ((dest != dest_end) && (src != src_end) && *src) {
|
||||
uint16_t src_char = *(src++);
|
||||
if (src_char & 0x80) {
|
||||
if (x == source.size()) {
|
||||
return ret;
|
||||
if (src == src_end) {
|
||||
throw runtime_error("incomplete extended character");
|
||||
}
|
||||
src_char = (src_char << 8) | source[x++];
|
||||
src_char = (src_char << 8) | *(src++);
|
||||
if ((src_char & 0xFF) == 0) {
|
||||
return ret;
|
||||
throw runtime_error("incomplete extended character");
|
||||
}
|
||||
}
|
||||
ret.push_back(table[src_char]);
|
||||
*(dest++) = table[src_char];
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void add_language_marker_inplace(char* a, char e, size_t dest_count) {
|
||||
if ((a[0] == '\t') && (a[1] != 'C')) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t existing_count = strlen(a);
|
||||
if (existing_count > dest_count - 3) {
|
||||
existing_count = dest_count - 3;
|
||||
}
|
||||
memmove(&a[2], a, (existing_count + 1) * sizeof(char));
|
||||
a[0] = '\t';
|
||||
a[1] = e;
|
||||
a[existing_count + 2] = 0;
|
||||
}
|
||||
|
||||
void add_language_marker_inplace(char16_t* a, char16_t e, size_t dest_count) {
|
||||
if ((a[0] == '\t') && (a[1] != 'C')) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t existing_count = char16len(a);
|
||||
if (existing_count > dest_count - 3) {
|
||||
existing_count = dest_count - 3;
|
||||
}
|
||||
memmove(&a[2], a, (existing_count + 1) * sizeof(char16_t));
|
||||
a[0] = '\t';
|
||||
a[1] = e;
|
||||
a[existing_count + 2] = 0;
|
||||
}
|
||||
|
||||
void remove_language_marker_inplace(char* a) {
|
||||
if ((a[0] == '\t') && (a[1] != 'C')) {
|
||||
strcpy(a, &a[2]);
|
||||
}
|
||||
}
|
||||
|
||||
void remove_language_marker_inplace(char16_t* a) {
|
||||
if ((a[0] == '\t') && (a[1] != 'C')) {
|
||||
strcpy_z(a, &a[2], char16len(a) - 2);
|
||||
}
|
||||
}
|
||||
|
||||
std::string add_language_marker(const std::string& s, char marker) {
|
||||
if ((s.size() >= 2) && (s[0] == '\t') && (s[1] != 'C')) {
|
||||
return s;
|
||||
}
|
||||
|
||||
string ret;
|
||||
ret.push_back('\t');
|
||||
ret.push_back(marker);
|
||||
return ret + s;
|
||||
}
|
||||
|
||||
std::u16string add_language_marker(const std::u16string& s, char16_t marker) {
|
||||
if ((s.size() >= 2) && (s[0] == L'\t') && (s[1] != L'C')) {
|
||||
return s;
|
||||
}
|
||||
|
||||
u16string ret;
|
||||
ret.push_back(L'\t');
|
||||
ret.push_back(marker);
|
||||
return ret + s;
|
||||
}
|
||||
|
||||
std::string remove_language_marker(const std::string& s) {
|
||||
if ((s.size() < 2) || (s[0] != '\t') || (s[1] == 'C')) {
|
||||
return s;
|
||||
}
|
||||
return s.substr(2);
|
||||
}
|
||||
|
||||
std::u16string remove_language_marker(const std::u16string& s) {
|
||||
if ((s.size() < 2) || (s[0] != L'\t') || (s[1] == L'C')) {
|
||||
return s;
|
||||
}
|
||||
return s.substr(2);
|
||||
}
|
||||
|
||||
+356
-26
@@ -10,26 +10,74 @@
|
||||
|
||||
|
||||
|
||||
#define countof(F) (sizeof(F) / sizeof(F[0]))
|
||||
// TODO: delete these if not needed
|
||||
// int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count);
|
||||
// size_t char16len(const char16_t* s);
|
||||
|
||||
|
||||
|
||||
int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count);
|
||||
size_t char16len(const char16_t* s);
|
||||
// (1a) Conversion functions
|
||||
|
||||
void encode_sjis(
|
||||
char* dest, size_t dest_count,
|
||||
const char16_t* src, size_t src_count);
|
||||
void decode_sjis(
|
||||
char16_t* dest, size_t dest_count,
|
||||
const char* src, size_t src_count);
|
||||
|
||||
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, 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);
|
||||
|
||||
inline std::string encode_sjis(const std::u16string& s) {
|
||||
return encode_sjis(s.data(), s.size());
|
||||
}
|
||||
|
||||
inline std::u16string decode_sjis(const std::string& s) {
|
||||
return decode_sjis(s.data(), s.size());
|
||||
}
|
||||
|
||||
// (1b) Type-independent utility functions
|
||||
|
||||
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_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;
|
||||
}
|
||||
|
||||
// Like strncpy, but *always* null-terminates the string, even if it has to
|
||||
// truncate it.
|
||||
template <typename T>
|
||||
void strcpy_z(T* dest, const T* src, size_t count) {
|
||||
void 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];
|
||||
@@ -39,44 +87,294 @@ void strcpy_z(T* dest, const T* src, size_t count) {
|
||||
|
||||
|
||||
|
||||
// (2) Type conversion functions
|
||||
|
||||
template <typename DestT, typename SrcT = DestT>
|
||||
void strncpy_t(DestT*, const SrcT*, size_t) {
|
||||
void text_strnzcpy_t(DestT*, size_t, const SrcT*, size_t) {
|
||||
static_assert(always_false<DestT, SrcT>::v,
|
||||
"unspecialized strcpy_t should never be called");
|
||||
"unspecialized text_strnzcpy_t should never be called");
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void strncpy_t<char>(char* dest, const char* src, size_t count) {
|
||||
strcpy_z(dest, src, count);
|
||||
inline void 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);
|
||||
text_strnzcpy_t(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);
|
||||
inline void text_strnzcpy_t<char, char16_t>(
|
||||
char* dest, size_t dest_count, const char16_t* src, size_t src_count) {
|
||||
encode_sjis(dest, dest_count, src, src_count);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void strncpy_t<char16_t, char>(char16_t* dest, const char* src, size_t count) {
|
||||
decode_sjis(dest, src, count);
|
||||
inline void text_strnzcpy_t<char16_t, char>(
|
||||
char16_t* dest, size_t dest_count, const char* src, size_t src_count) {
|
||||
decode_sjis(dest, dest_count, src, src_count);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void strncpy_t<char16_t>(char16_t* dest, const char16_t* src, size_t count) {
|
||||
strcpy_z(dest, src, count);
|
||||
inline void 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);
|
||||
text_strnzcpy_t(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);
|
||||
void remove_language_marker_inplace(char16_t* s);
|
||||
std::string add_language_marker(const std::string& s, char marker);
|
||||
std::u16string add_language_marker(const std::u16string& s, char16_t marker);
|
||||
std::string remove_language_marker(const std::string& s);
|
||||
std::u16string remove_language_marker(const std::u16string& s);
|
||||
// (3) Packed text object for use in protocol structs
|
||||
|
||||
template <typename ItemT, size_t Count>
|
||||
struct parray {
|
||||
ItemT items[Count];
|
||||
|
||||
parray() {
|
||||
this->clear();
|
||||
}
|
||||
parray(const parray& other) {
|
||||
this->operator=(other);
|
||||
}
|
||||
parray(parray&& s) = delete;
|
||||
|
||||
template <size_t OtherCount>
|
||||
parray(const parray<ItemT, OtherCount>& s) {
|
||||
this->operator=(s);
|
||||
}
|
||||
|
||||
constexpr size_t size() {
|
||||
return Count;
|
||||
}
|
||||
constexpr size_t bytes() {
|
||||
return Count * sizeof(ItemT);
|
||||
}
|
||||
ItemT* data() {
|
||||
return this->items;
|
||||
}
|
||||
const ItemT* data() const {
|
||||
return this->items;
|
||||
}
|
||||
|
||||
// TODO: These can be made faster by only clearing the unused space after the
|
||||
// strncpy_t (if any) instead of clearing all the space every time
|
||||
parray& operator=(const parray& s) {
|
||||
for (size_t x = 0; x < Count; x++) {
|
||||
this->items[x] = s.items[x];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
parray& operator=(parray&& s) = delete;
|
||||
|
||||
template <size_t OtherCount>
|
||||
parray& operator=(const parray<ItemT, OtherCount>& s) {
|
||||
if (OtherCount <= Count) {
|
||||
size_t x;
|
||||
for (x = 0; x < OtherCount; x++) {
|
||||
this->items[x] = s.items[x];
|
||||
}
|
||||
for (; x < Count; x++) {
|
||||
this->items[x] = 0;
|
||||
}
|
||||
} else {
|
||||
for (size_t x = 0; x < Count; x++) {
|
||||
this->items[x] = s.items[x];
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
parray& operator=(const ItemT* s) {
|
||||
for (size_t x = 0; x < Count; x++) {
|
||||
this->items[x] = s[x];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const parray& s) const {
|
||||
for (size_t x = 0; x < Count; x++) {
|
||||
if (this->items[x] != s.items[x]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void clear(ItemT v = 0) {
|
||||
for (size_t x = 0; x < Count; x++) {
|
||||
this->items[x] = v;
|
||||
}
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
template <typename CharT, size_t Count>
|
||||
struct ptext : parray<CharT, Count> {
|
||||
ptext() {
|
||||
this->clear();
|
||||
}
|
||||
ptext(const ptext& other) {
|
||||
this->operator=(other);
|
||||
}
|
||||
ptext(ptext&& s) = delete;
|
||||
|
||||
template <typename OtherCharT>
|
||||
ptext(const OtherCharT* s) {
|
||||
this->operator=(s);
|
||||
}
|
||||
template <typename OtherCharT>
|
||||
ptext(const OtherCharT* s, size_t count) {
|
||||
this->assign(s, count);
|
||||
}
|
||||
template <typename OtherCharT>
|
||||
ptext(const std::basic_string<OtherCharT>& s) {
|
||||
this->operator=(s);
|
||||
}
|
||||
template <typename OtherCharT, size_t OtherCount>
|
||||
ptext(const ptext<OtherCharT, OtherCount>& s) {
|
||||
this->operator=(s);
|
||||
}
|
||||
|
||||
size_t len() const {
|
||||
return text_strlen_t(this->items);
|
||||
}
|
||||
const CharT* c_str() const {
|
||||
return this->data();
|
||||
}
|
||||
|
||||
// TODO: These can be made faster by only clearing the unused space after the
|
||||
// strncpy_t (if any) instead of clearing all the space every time
|
||||
ptext& operator=(const ptext& s) {
|
||||
this->clear();
|
||||
text_strnzcpy_t(this->items, Count, s.items, Count);
|
||||
return *this;
|
||||
}
|
||||
ptext& operator=(ptext&& s) = delete;
|
||||
|
||||
template <typename OtherCharT>
|
||||
ptext& operator=(const OtherCharT* s) {
|
||||
this->clear();
|
||||
text_strnzcpy_t(this->items, Count, s, Count);
|
||||
return *this;
|
||||
}
|
||||
template <typename OtherCharT>
|
||||
ptext& assign(const OtherCharT* s, size_t s_count) {
|
||||
this->clear();
|
||||
text_strnzcpy_t(this->items, Count, s, s_count);
|
||||
return *this;
|
||||
}
|
||||
template <typename OtherCharT>
|
||||
ptext& operator=(const std::basic_string<OtherCharT>& s) {
|
||||
this->clear();
|
||||
text_strnzcpy_t(this->items, Count, s.c_str(), s.size() + 1);
|
||||
return *this;
|
||||
}
|
||||
template <typename OtherCharT, size_t OtherCount>
|
||||
ptext& operator=(const ptext<OtherCharT, OtherCount>& s) {
|
||||
this->clear();
|
||||
text_strnzcpy_t(this->items, Count, s.items, OtherCount);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename OtherCharT>
|
||||
bool operator==(const OtherCharT* s) const {
|
||||
return text_streq_t(this->items, s);
|
||||
}
|
||||
template <typename OtherCharT>
|
||||
bool operator==(const std::basic_string<OtherCharT>& s) const {
|
||||
return text_streq_t(this->items, s.c_str());
|
||||
}
|
||||
template <typename OtherCharT, size_t OtherCount>
|
||||
bool operator==(const ptext<OtherCharT, OtherCount>& s) const {
|
||||
return text_streq_t(this->items, s.items);
|
||||
}
|
||||
|
||||
template <typename OtherCharT>
|
||||
bool eq_n(const OtherCharT* s, size_t count) const {
|
||||
return text_strneq_t(this->items, s, count);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
operator std::basic_string<CharT>() const {
|
||||
std::basic_string<CharT> ret(this->items, Count);
|
||||
strip_trailing_zeroes(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return (this->items[0] == 0);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
|
||||
// (4) Markers and character replacement
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::basic_string<CharT> ret;
|
||||
ret.push_back('\t');
|
||||
ret.push_back(marker);
|
||||
ret += s;
|
||||
return ret;
|
||||
}
|
||||
|
||||
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>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void replace_char_inplace(T* a, T f, T r) {
|
||||
@@ -150,3 +448,35 @@ void add_color(StringWriter& w, const T* src, size_t max_input_chars) {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user