unify menu item format

This commit is contained in:
Martin Michelsen
2025-01-08 23:35:12 -08:00
parent f6fbba5638
commit 68003b2e2f
6 changed files with 48 additions and 54 deletions
+29 -33
View File
@@ -515,50 +515,37 @@ struct S_UpdateClientConfig_BB_04 {
// there was a separate command to send the block list, but it was scrapped.
// Perhaps this was used for command A1, which is identical to 07 and A0 in all
// versions of PSO (except DC NTE).
// The menu is titles "Ship Select" unless the first menu item begins with the
// The menu is titled "Ship Select" unless the first menu item begins with the
// text "BLOCK" (all caps), in which case it is titled "Block Select".
// Command is a list of these; header.flag is the entry count. The first entry
// is not included in the count and does not appear on the client. The text of
// the first entry becomes the ship name when the client joins a lobby.
template <TextEncoding Encoding, size_t Chars>
struct S_MenuEntryT {
// is not included in the count and does not appear on the client. The first
// entry's text becomes the ship name displayed in the corner of the lobby HUD,
// unless it begins with "BLOCK", in which case it is ignored.
template <TextEncoding Encoding>
struct S_MenuItemT {
le_uint32_t menu_id = 0;
le_uint32_t item_id = 0;
le_uint16_t flags = 0x0F04; // Should be this value, apparently
pstring<Encoding, Chars> text;
} __packed__;
using S_MenuEntry_PC_BB_07_1F = S_MenuEntryT<TextEncoding::UTF16, 0x11>;
using S_MenuEntry_DC_V3_07_1F = S_MenuEntryT<TextEncoding::MARKED, 0x12>;
check_struct_size(S_MenuEntry_PC_BB_07_1F, 0x2C);
check_struct_size(S_MenuEntry_DC_V3_07_1F, 0x1C);
// 08 (C->S): Request game list
// Internal name: SndGameList
// No arguments
// 08 (S->C): Game list
// Internal name: RcvGameList
// Client responds with 09 and 10 commands (or nothing if the player cancels).
// Command is a list of these; header.flag is the entry count. The first entry
// is not included in the count and does not appear on the client.
template <TextEncoding Encoding>
struct S_GameMenuEntryT {
le_uint32_t menu_id = 0;
le_uint32_t game_id = 0;
// The following two fields are only used for the game menu; for Ship Select,
// Block Select, and Information, they are unused.
// difficulty_tag is 0x0A on Episode 3; on all other versions, it's
// difficulty + 0x22 (so 0x25 means Ultimate, for example)
// difficulty + 0x22 (so 0x25 means Ultimate, for example).
uint8_t difficulty_tag = 0;
uint8_t num_players = 0;
pstring<Encoding, 0x10> name;
// The episode field is only used for the game menu; for Ship Select, Block
// Select, and Information, it is unused.
// The episode field is used differently by different versions:
// - On DCv1, PC, and GC Episode 3, the value is ignored.
// - On DCv2, 1 means v1 players can't join the game, and 0 means they can.
// - On GC Ep1&2, 0x40 means Episode 1, and 0x41 means Episode 2.
// - On BB, 0x40/0x41 mean Episodes 1/2 as on GC, and 0x43 means Episode 4.
uint8_t episode = 0;
// Flags:
// Flags (01 and 02 are used for all menus; the rest are only used for the
// game menu):
// 01 = Send name? (client sends the name field in the 10 command if this
// item is chosen, but it's blank)
// 02 = Locked (lock icon appears in menu; player is prompted for password if
@@ -567,16 +554,25 @@ struct S_GameMenuEntryT {
// 04 = Disabled (BB; used for solo games)
// 10 = Is battle mode
// 20 = Is challenge mode
// 40 = Is v2 only (DCv2/PC); name renders in orange
// 40 = Is v2 only (DCv2/PC); game name renders in orange
// 40 = Is Episode 1 (V3/BB)
// 80 = Is Episode 2 (V3/BB)
// C0 = Is Episode 4 (BB)
uint8_t flags = 0;
} __packed__;
using S_GameMenuEntry_PC_BB_08 = S_GameMenuEntryT<TextEncoding::UTF16>;
using S_GameMenuEntry_DC_V3_08_Ep3_E6 = S_GameMenuEntryT<TextEncoding::MARKED>;
check_struct_size(S_GameMenuEntry_PC_BB_08, 0x2C);
check_struct_size(S_GameMenuEntry_DC_V3_08_Ep3_E6, 0x1C);
using S_MenuItem_PC_BB_08 = S_MenuItemT<TextEncoding::UTF16>;
using S_MenuItem_DC_V3_08_Ep3_E6 = S_MenuItemT<TextEncoding::MARKED>;
check_struct_size(S_MenuItem_PC_BB_08, 0x2C);
check_struct_size(S_MenuItem_DC_V3_08_Ep3_E6, 0x1C);
// 08 (C->S): Request game list
// Internal name: SndGameList
// No arguments
// 08 (S->C): Game list
// Internal name: RcvGameList
// Client responds with 09 and 10 commands (or nothing if the player cancels).
// Command format is the same as 07.
// 09 (C->S): Menu item info request
// Internal name: SndInfo
+3 -3
View File
@@ -484,7 +484,7 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
this->log.info("Ship Select menu:");
for (item_index = 1; item_index <= flag; item_index++) {
const auto& item = items[item_index];
auto text = strip_color(item.text.decode());
auto text = strip_color(item.name.decode());
this->log.info("%zu: (%08" PRIX32 " %08" PRIX32 ") %s", item_index, item.menu_id.load(), item.item_id.load(), text.c_str());
if (this->ship_menu_selections.count(text)) {
break;
@@ -506,9 +506,9 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
};
if (uses_utf16(this->channel.version)) {
handle_command.operator()<S_MenuEntry_PC_BB_07_1F>();
handle_command.operator()<S_MenuItem_PC_BB_08>();
} else {
handle_command.operator()<S_MenuEntry_DC_V3_07_1F>();
handle_command.operator()<S_MenuItem_DC_V3_08_Ep3_E6>();
}
this->channel.send(0x10, 0x00, ret);
+3 -1
View File
@@ -13,8 +13,10 @@
class HTTPServer {
public:
// shared_base should be null unless
// shared_base should be null unless the HTTP server should run on the main
// thread (on Windows).
HTTPServer(std::shared_ptr<ServerState> state, std::shared_ptr<struct event_base> shared_base);
HTTPServer(const HTTPServer&) = delete;
HTTPServer(HTTPServer&&) = delete;
HTTPServer& operator=(const HTTPServer&) = delete;
+2 -6
View File
@@ -885,19 +885,15 @@ uint32_t encrypt_challenge_time(uint16_t value) {
available_bits.erase(it);
}
uint32_t ret = (mask << 16) | (value ^ mask);
fprintf(stderr, "encrypt_challenge_time %04hX => %08" PRIX32 "\n", value, ret);
return ret;
return (mask << 16) | (value ^ mask);
}
uint16_t decrypt_challenge_time(uint32_t value) {
uint16_t mask = (value >> 0x10);
uint8_t mask_one_bits = count_one_bits(mask);
uint16_t ret = ((mask_one_bits < 4) || (mask_one_bits > 12))
return ((mask_one_bits < 4) || (mask_one_bits > 12))
? 0xFFFF
: ((mask ^ value) & 0xFFFF);
fprintf(stderr, "decrypt_challenge_time %08" PRIX32 " => %04hX\n", value, ret);
return ret;
}
string decrypt_v2_registry_value(const void* data, size_t size) {
+1 -2
View File
@@ -391,7 +391,6 @@ void forward_subcommand_with_entity_id_transcode_t(
lc->log.info("Subcommand cannot be translated to client\'s version");
}
} else {
fprintf(stderr, "NOCOMMIT: same version\n");
send_command_t(lc, command, flag, cmd);
}
}
@@ -2903,7 +2902,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
return;
case Lobby::DropMode::CLIENT: {
// If the leader is BB, use SERVER_SHARED instead
// NOCOMMIT: We should also use server drops if any clients have incompatible object lists, since they might generate incorrect IDs for items and we can't override them
// TODO: We should also use server drops if any clients have incompatible object lists, since they might generate incorrect IDs for items and we can't override them
auto leader = l->clients[l->leader_id];
if (leader && leader->version() == Version::BB_V4) {
drop_mode = Lobby::DropMode::SERVER_SHARED;
+10 -9
View File
@@ -1385,8 +1385,7 @@ void send_menu_t(shared_ptr<Client> c, shared_ptr<const Menu> menu, bool is_info
auto& e = entries.emplace_back();
e.menu_id = menu->menu_id;
e.item_id = 0xFFFFFFFF;
e.flags = 0x0004;
e.text.encode(menu->name, c->language());
e.name.encode(menu->name, c->language());
}
for (const auto& item : menu->items) {
@@ -1440,8 +1439,10 @@ void send_menu_t(shared_ptr<Client> c, shared_ptr<const Menu> menu, bool is_info
auto& e = entries.emplace_back();
e.menu_id = menu->menu_id;
e.item_id = item.item_id;
e.flags = (c->version() == Version::BB_V4) ? 0x0004 : 0x0F04;
e.text.encode(item.name, c->language());
e.name.encode(item.name, c->language());
e.difficulty_tag = 0x04;
e.num_players = (c->version() == Version::BB_V4) ? 0x00 : 0x0F;
e.flags = 0xFF;
}
}
@@ -1451,9 +1452,9 @@ void send_menu_t(shared_ptr<Client> c, shared_ptr<const Menu> menu, bool is_info
void send_menu(shared_ptr<Client> c, shared_ptr<const Menu> menu, bool is_info_menu) {
if (uses_utf16(c->version())) {
send_menu_t<S_MenuEntry_PC_BB_07_1F>(c, menu, is_info_menu);
send_menu_t<S_MenuItem_PC_BB_08>(c, menu, is_info_menu);
} else {
send_menu_t<S_MenuEntry_DC_V3_07_1F>(c, menu, is_info_menu);
send_menu_t<S_MenuItem_DC_V3_08_Ep3_E6>(c, menu, is_info_menu);
}
}
@@ -1464,11 +1465,11 @@ void send_game_menu_t(
bool show_tournaments_only) {
auto s = c->require_server_state();
vector<S_GameMenuEntryT<Encoding>> entries;
vector<S_MenuItemT<Encoding>> entries;
{
auto& e = entries.emplace_back();
e.menu_id = MenuID::GAME;
e.game_id = 0x00000000;
e.item_id = 0x00000000;
e.difficulty_tag = 0x00;
e.num_players = 0x00;
e.name.encode(s->name, c->language());
@@ -1512,7 +1513,7 @@ void send_game_menu_t(
auto& e = entries.emplace_back();
e.menu_id = MenuID::GAME;
e.game_id = l->lobby_id;
e.item_id = l->lobby_id;
e.difficulty_tag = (is_ep3(c->version()) ? 0x0A : (l->difficulty + 0x22));
e.num_players = l->count_clients();
if (is_dc(c->version())) {