use safe packed string types

This commit is contained in:
Martin Michelsen
2022-03-31 23:23:02 -07:00
parent 832135a505
commit 8a9e1a2049
22 changed files with 1040 additions and 879 deletions
+9 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}