more ep3 comamnd details
This commit is contained in:
+89
-51
@@ -289,13 +289,6 @@ struct S_Reconnect_Patch_14 : S_Reconnect<be_uint16_t> { } __packed__;
|
||||
// a key to continue. The maximum length of the message is 0x200 bytes.
|
||||
// This format is shared by multiple commands; for all of them except 06 (S->C),
|
||||
// the guild_card_number field is unused and should be 0.
|
||||
// During Episode 3 battles, the first byte of an inbound 06 command's message
|
||||
// is interpreted differently. It should be treated as a bit field, with the low
|
||||
// 4 bits intended as masks for who can see the message. If the low bit (1) is
|
||||
// set, for example, then the chat message displays as " (whisper)" on player
|
||||
// 0's screen regardless of the message contents. The next bit (2) hides the
|
||||
// message from player 1, etc. The high 4 bits of this byte appear not to be
|
||||
// used, but are often nonzero and set to the value 4.
|
||||
|
||||
struct SC_TextHeader_01_06_11_B0_EE {
|
||||
le_uint32_t unused = 0;
|
||||
@@ -461,11 +454,15 @@ struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig<ClientConfigBB> { } __p
|
||||
// is 0x200 bytes.
|
||||
// When sent by the client, the text field includes only the message. When sent
|
||||
// by the server, the text field includes the origin player's name, followed by
|
||||
// a tab character, followed by the message. During Episode 3 battles, chat
|
||||
// messages can additionally be targeted to only your teammate; in this case,
|
||||
// the message (at least, when seen by spectators) is of the form
|
||||
// '<targetname>\tE\t<message>'. Messages sent to the entire battle group
|
||||
// (including the opponents) are of the form '<targetname>\t@\t<message>'.
|
||||
// a tab character, followed by the message.
|
||||
// During Episode 3 battles, the first byte of an inbound 06 command's message
|
||||
// is interpreted differently. It should be treated as a bit field, with the low
|
||||
// 4 bits intended as masks for who can see the message. If the low bit (1) is
|
||||
// set, for example, then the chat message displays as " (whisper)" on player
|
||||
// 0's screen regardless of the message contents. The next bit (2) hides the
|
||||
// message from player 1, etc. The high 4 bits of this byte appear not to be
|
||||
// used, but are often nonzero and set to the value 4. We call this byte
|
||||
// private_flags in the places where newserv uses it.
|
||||
// Client->server format is very similar; we include a zero-length array in this
|
||||
// struct to make parsing easier.
|
||||
|
||||
@@ -1705,8 +1702,6 @@ struct C_UpdateQuestStatistics_V3_BB_AA {
|
||||
|
||||
// AB (S->C): Confirm update quest statistics (V3/BB)
|
||||
// This command is not valid on PSO GC Episodes 1&2 Trial Edition.
|
||||
// TODO: Does this command have a different meaning in Episode 3? Is it used at
|
||||
// all there, or is the handler an undeleted vestige from Episodes 1&2?
|
||||
|
||||
struct S_ConfirmUpdateQuestStatistics_V3_BB_AB {
|
||||
le_uint16_t unknown_a1 = 0; // 0
|
||||
@@ -1893,6 +1888,14 @@ struct S_UpdateMediaHeader_GC_Ep3_B9 {
|
||||
|
||||
// BA: Meseta transaction (Episode 3)
|
||||
// This command is not valid on Episode 3 Trial Edition.
|
||||
// header.flag specifies the transaction purpose. This has little meaning on the
|
||||
// client. Specific known values:
|
||||
// 00 = unknown
|
||||
// 01 = unknown (S->C; request_token must match the last token sent by client)
|
||||
// 02 = Spend meseta (at e.g. lobby jukebox or Pinz's shop) (C->S)
|
||||
// 03 = Spend meseta response (S->C; request_token must match the last token
|
||||
// sent by client)
|
||||
// 04 = unknown (S->C; request_token must match the last token sent by client)
|
||||
|
||||
struct C_Meseta_GC_Ep3_BA {
|
||||
le_uint32_t transaction_num = 0;
|
||||
@@ -1906,20 +1909,32 @@ struct S_Meseta_GC_Ep3_BA {
|
||||
le_uint32_t request_token = 0; // Should match the token sent by the client
|
||||
} __packed__;
|
||||
|
||||
// BB (S->C): Unknown (Episode 3)
|
||||
// header.flag is used, but it's not clear for what. It may be the number of
|
||||
// valid entries, similarly to how command 07 is implemented.
|
||||
// This command is not valid on Episode 3 Trial Edition.
|
||||
// BB (S->C): Tournament match information (Episode 3)
|
||||
// This command is not valid on Episode 3 Trial Edition. Because of this, it
|
||||
// must have been added fairly late in development, but it seems to be unused,
|
||||
// perhaps because the E1/E3 commands are generally more useful... but the E1/E3
|
||||
// commands exist in the Trial Edition! So why was this added? Was it just never
|
||||
// finished? We may never know...
|
||||
// header.flag is the number of valid match entries.
|
||||
|
||||
struct S_Unknown_GC_Ep3_BB {
|
||||
struct Entry {
|
||||
parray<uint8_t, 0x20> unknown_a1;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
struct S_TournamentMatchInformation_GC_Ep3_BB {
|
||||
ptext<char, 0x20> tournament_name;
|
||||
struct TeamEntry {
|
||||
le_uint16_t win_count = 0;
|
||||
le_uint16_t is_active = 0;
|
||||
ptext<char, 0x20> name;
|
||||
} __packed__;
|
||||
// The first entry here is probably fake, like for ship select menus (07)
|
||||
parray<Entry, 0x21> entries;
|
||||
parray<uint8_t, 0x900> unknown_a3;
|
||||
parray<TeamEntry, 0x20> team_entries;
|
||||
le_uint16_t num_teams = 0;
|
||||
le_uint16_t unknown_a3 = 0; // Probably actually unused
|
||||
struct MatchEntry {
|
||||
parray<char, 0x20> name;
|
||||
uint8_t locked = 0;
|
||||
uint8_t count = 0;
|
||||
uint8_t max_count = 0;
|
||||
uint8_t unused = 0;
|
||||
} __packed__;
|
||||
parray<MatchEntry, 0x40> match_entries;
|
||||
} __packed__;
|
||||
|
||||
// BC: Invalid command
|
||||
@@ -2163,7 +2178,11 @@ struct SC_TradeItems_D0_D3 { // D0 when sent by client, D3 when sent by server
|
||||
// Episode 3 will send D6 only for large message boxes that occur before the
|
||||
// client has joined a lobby. (After joining a lobby, large message boxes will
|
||||
// still be displayed if sent by the server, but the client won't send a D6 when
|
||||
// they are closed.)
|
||||
// they are closed.) In some of these versions, there is a bug that sets an
|
||||
// incorrect interaction mode when the message box is closed while the player is
|
||||
// in the lobby; some servers (e.g. Schtserv) send a lobby welcome message
|
||||
// anyway, along with an 01 (lobby message box) which properly sets the
|
||||
// interaction mode when closed.
|
||||
|
||||
// D7 (C->S): Request GBA game file (V3)
|
||||
// The server should send the requested file using A6/A7 commands.
|
||||
@@ -4571,28 +4590,33 @@ struct G_AcceptItemIdentification_BB_6xBA {
|
||||
le_uint32_t item_id;
|
||||
} __packed__;
|
||||
|
||||
// 6xBB: Unknown (Episode 3)
|
||||
// 6xBB: Sync card trade state (Episode 3)
|
||||
// TODO: Certain invalid values for slot/args in this command can crash the
|
||||
// client (what is properly bounds-checked). Find out the actual limits for
|
||||
// slot/args and make newserv enforce them.
|
||||
|
||||
struct G_Unknown_GC_Ep3_6xBB {
|
||||
struct G_SyncCardTradeState_GC_Ep3_6xBB {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1; // Low byte must be < 5
|
||||
le_uint16_t unknown_a2;
|
||||
parray<le_uint32_t, 4> unknown_a3;
|
||||
le_uint16_t what; // Must be < 5; this indexes into a jump table
|
||||
le_uint16_t slot;
|
||||
parray<le_uint32_t, 4> args;
|
||||
} __packed__;
|
||||
|
||||
// 6xBB: BB bank request (handled by the server)
|
||||
|
||||
// 6xBC: Unknown (Episode 3)
|
||||
// 6xBC: Card counts (Episode 3)
|
||||
// It's possible that this was an early, now-unused implementation of the CAx49
|
||||
// command. When the client receives this command, it copies the data into a
|
||||
// globally-allocated array, but nothing ever reads from this array.
|
||||
// Curiously, this command is smaller than 0x400 bytes, but uses the extended
|
||||
// subcommand format anyway (and uses the 6D command rather than 62).
|
||||
|
||||
struct G_Unknown_GC_Ep3_6xBC {
|
||||
struct G_CardCounts_GC_Ep3_6xBC {
|
||||
G_UnusedHeader header;
|
||||
parray<uint8_t, 4> unused1;
|
||||
// The length of this array strongly implies one flag or value per card type.
|
||||
le_uint32_t size;
|
||||
parray<uint8_t, 0x2F1> unknown_a1;
|
||||
parray<uint8_t, 3> unused2;
|
||||
// The client sends uninitialized data in this field
|
||||
parray<uint8_t, 3> unused;
|
||||
} __packed__;
|
||||
|
||||
// 6xBC: BB bank contents (server->client only)
|
||||
@@ -4954,11 +4978,13 @@ struct G_Unknown_GC_Ep3_6xB5x17 {
|
||||
// No arguments
|
||||
} __packed__;
|
||||
|
||||
// 6xB5x1A: Unknown
|
||||
// TODO: Document this from Episode 3 client/server disassembly
|
||||
// 6xB5x1A: Force disconnect
|
||||
// This command seems to cause the client to unconditionally disconnect. The
|
||||
// player is returned to the main menu (the "The line was disconnected" message
|
||||
// box is skipped).
|
||||
|
||||
struct G_Unknown_GC_Ep3_6xB5x1A {
|
||||
G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x1A) / 4, 0, 0x1A, 0, 0, 0};
|
||||
struct G_ForceDisconnect_GC_Ep3_6xB5x1A {
|
||||
G_CardBattleCommandHeader header = {0xB5, sizeof(G_ForceDisconnect_GC_Ep3_6xB5x1A) / 4, 0, 0x1A, 0, 0, 0};
|
||||
// No arguments
|
||||
} __packed__;
|
||||
|
||||
@@ -5208,6 +5234,11 @@ struct G_PhotonBlastStatus_GC_Ep3_6xB4x35 {
|
||||
|
||||
// 6xB5x36: Unknown
|
||||
// TODO: Document this from Episode 3 client/server disassembly
|
||||
// Setting unknown_a1 to a value 4 or greater while in a game causes the player
|
||||
// to be temporarily replaced with a default HUmar and placed inside the central
|
||||
// column in the Morgue, rendering them unable to move. The only ways out of
|
||||
// this predicament appear to be either to disconnect or receive an ED (force
|
||||
// leave game) command.
|
||||
|
||||
struct G_Unknown_GC_Ep3_6xB5x36 {
|
||||
G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x36) / 4, 0, 0x36, 0, 0, 0};
|
||||
@@ -5223,13 +5254,15 @@ struct G_AdvanceFromStartingRollsPhase_GC_Ep3_6xB3x37_CAx37 {
|
||||
parray<uint8_t, 3> unused2;
|
||||
} __packed__;
|
||||
|
||||
// 6xB5x38: Unknown
|
||||
// TODO: Document this from Episode 3 client/server disassembly
|
||||
// 6xB5x38: Card counts request
|
||||
// This command causes the client identified by requested_client_id to send a
|
||||
// 6xBC command to the client identified by reply_to_client_id (privately, via
|
||||
// the 6D command).
|
||||
|
||||
struct G_Unknown_GC_Ep3_6xB5x38 {
|
||||
G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x38) / 4, 0, 0x38, 0, 0, 0};
|
||||
uint8_t unknown_a1 = 0;
|
||||
uint8_t unknown_a2 = 0;
|
||||
struct G_CardCountsRequest_GC_Ep3_6xB5x38 {
|
||||
G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardCountsRequest_GC_Ep3_6xB5x38) / 4, 0, 0x38, 0, 0, 0};
|
||||
uint8_t requested_client_id = 0;
|
||||
uint8_t reply_to_client_id = 0;
|
||||
parray<uint8_t, 2> unused;
|
||||
} __packed__;
|
||||
|
||||
@@ -5254,13 +5287,18 @@ struct G_Unknown_GC_Ep3_6xB4x3B {
|
||||
parray<uint8_t, 4> unused;
|
||||
} __packed__;
|
||||
|
||||
// 6xB5x3C: Unknown
|
||||
// TODO: Document this from Episode 3 client/server disassembly
|
||||
// 6xB5x3C: Set player substatus
|
||||
// This command sets the text that appears under the player's name in the HUD.
|
||||
|
||||
struct G_Unknown_GC_Ep3_6xB5x3C {
|
||||
// Note: header.sender_client_id specifies which client's status to update
|
||||
G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x3C) / 4, 0, 0x3C, 0, 0, 0};
|
||||
// Note: This command uses header_b1 for... something.
|
||||
uint8_t unknown_a1 = 0;
|
||||
// Status values:
|
||||
// 00 (or any value not listed below) = (nothing)
|
||||
// 01 = Editing
|
||||
// 02 = Trading...
|
||||
// 03 = At Counter
|
||||
uint8_t status = 0;
|
||||
parray<uint8_t, 3> unused;
|
||||
} __packed__;
|
||||
|
||||
|
||||
+10
-7
@@ -35,11 +35,12 @@ ServerBase::ServerBase(
|
||||
shared_ptr<Lobby> lobby,
|
||||
shared_ptr<const DataIndex> data_index,
|
||||
uint32_t random_seed,
|
||||
bool is_tournament)
|
||||
shared_ptr<const DataIndex::MapEntry> map_if_tournament)
|
||||
: lobby(lobby),
|
||||
data_index(data_index),
|
||||
random_seed(random_seed),
|
||||
is_tournament(is_tournament) { }
|
||||
is_tournament(!!map_if_tournament),
|
||||
last_chosen_map(map_if_tournament) { }
|
||||
|
||||
void ServerBase::init() {
|
||||
this->reset();
|
||||
@@ -228,8 +229,9 @@ void Server::send_commands_for_joining_spectator(Channel& c) const {
|
||||
}
|
||||
}
|
||||
|
||||
if (this->last_chosen_map) {
|
||||
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map);
|
||||
auto map = this->base()->last_chosen_map;
|
||||
if (map) {
|
||||
string data = this->prepare_6xB6x41_map_definition(map);
|
||||
c.send(0x6C, 0x00, data);
|
||||
}
|
||||
|
||||
@@ -2134,13 +2136,14 @@ void Server::handle_6xB3x41_map_request(const string& data) {
|
||||
this->send_debug_command_received_message(
|
||||
cmd.header.subsubcommand, "MAP DATA");
|
||||
|
||||
auto l = this->base()->lobby.lock();
|
||||
auto base = this->base();
|
||||
auto l = base->lobby.lock();
|
||||
if (!l) {
|
||||
throw runtime_error("lobby is deleted");
|
||||
}
|
||||
|
||||
this->last_chosen_map = this->base()->data_index->definition_for_map_number(cmd.map_number);
|
||||
auto out_cmd = this->prepare_6xB6x41_map_definition(this->last_chosen_map);
|
||||
base->last_chosen_map = base->data_index->definition_for_map_number(cmd.map_number);
|
||||
auto out_cmd = this->prepare_6xB6x41_map_definition(base->last_chosen_map);
|
||||
send_command(l, 0x6C, 0x00, out_cmd);
|
||||
for (auto watcher_l : l->watcher_lobbies) {
|
||||
send_command(watcher_l, 0x6C, 0x00, out_cmd);
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
std::shared_ptr<Lobby> lobby,
|
||||
std::shared_ptr<const DataIndex> data_index,
|
||||
uint32_t random_seed,
|
||||
bool is_tournament);
|
||||
std::shared_ptr<const DataIndex::MapEntry> map_if_tournament);
|
||||
void init();
|
||||
void reset();
|
||||
void recreate_server();
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
std::shared_ptr<const DataIndex> data_index;
|
||||
uint32_t random_seed;
|
||||
bool is_tournament;
|
||||
std::shared_ptr<const DataIndex::MapEntry> last_chosen_map;
|
||||
|
||||
std::shared_ptr<MapAndRulesState> map_and_rules1;
|
||||
std::shared_ptr<MapAndRulesState> map_and_rules2;
|
||||
@@ -235,7 +236,6 @@ private:
|
||||
static const std::unordered_map<uint8_t, handler_t> subcommand_handlers;
|
||||
|
||||
std::weak_ptr<ServerBase> w_base;
|
||||
std::shared_ptr<const DataIndex::MapEntry> last_chosen_map;
|
||||
|
||||
public:
|
||||
uint32_t battle_finished;
|
||||
|
||||
@@ -947,6 +947,18 @@ static HandlerResult S_6x(shared_ptr<ServerState>,
|
||||
false, cmd.area, cmd.x, cmd.z, cmd.request_id);
|
||||
session.next_drop_item.clear();
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
|
||||
} else if ((static_cast<uint8_t>(data[0]) == 0xB5) &&
|
||||
(session.version == GameVersion::GC) &&
|
||||
(data.size() > 4)) {
|
||||
if (data[4] == 0x1A) {
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
} else if (data[4] == 0x36) {
|
||||
const auto& cmd = check_size_t<G_Unknown_GC_Ep3_6xB5x36>(data);
|
||||
if (session.is_in_game && (cmd.unknown_a1 >= 4)) {
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1061,8 +1061,9 @@ static void on_ep3_server_data_request(shared_ptr<ServerState> s, shared_ptr<Cli
|
||||
} else {
|
||||
l->log.info("Recreating Episode 3 server state");
|
||||
}
|
||||
auto tourn = l->tournament_match ? l->tournament_match->tournament.lock() : nullptr;
|
||||
l->ep3_server_base = make_shared<Episode3::ServerBase>(
|
||||
l, s->ep3_data_index, l->random_seed, l->tournament_match ? true : false);
|
||||
l, s->ep3_data_index, l->random_seed, tourn ? tourn->get_map() : nullptr);
|
||||
l->ep3_server_base->init();
|
||||
|
||||
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
|
||||
@@ -3056,8 +3057,11 @@ static void on_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
send_get_player_info(c);
|
||||
}
|
||||
|
||||
auto watched_lobby = l->watched_lobby.lock();
|
||||
if (l->battle_player && (l->flags & Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY)) {
|
||||
l->battle_player->start();
|
||||
} else if (watched_lobby && watched_lobby->ep3_server_base) {
|
||||
watched_lobby->ep3_server_base->server->send_commands_for_joining_spectator(c->channel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,10 +230,10 @@ static void on_subcommand_forward_check_size_ep3_game(shared_ptr<ServerState>,
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Ep3 subcommands
|
||||
|
||||
static void on_subcommand_ep3_battle_subs(shared_ptr<ServerState>,
|
||||
static void on_subcommand_ep3_battle_subs(shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const string& orig_data) {
|
||||
check_size_sc<G_CardBattleCommandHeader>(
|
||||
const auto& header = check_size_sc<G_CardBattleCommandHeader>(
|
||||
orig_data, sizeof(G_CardBattleCommandHeader), 0xFFFF);
|
||||
if (!l->is_game() || !(l->flags & Lobby::Flag::EPISODE_3_ONLY)) {
|
||||
return;
|
||||
@@ -242,11 +242,25 @@ static void on_subcommand_ep3_battle_subs(shared_ptr<ServerState>,
|
||||
string data = orig_data;
|
||||
set_mask_for_ep3_game_command(data.data(), data.size(), 0);
|
||||
|
||||
uint8_t mask_key = 0;
|
||||
while (!mask_key) {
|
||||
mask_key = random_object<uint8_t>();
|
||||
if (header.subcommand == 0xB5) {
|
||||
if (header.subsubcommand == 0x1A) {
|
||||
return;
|
||||
} else if (header.subsubcommand == 0x36) {
|
||||
const auto& cmd = check_size_t<G_Unknown_GC_Ep3_6xB5x36>(data);
|
||||
if (l->is_game() && (cmd.unknown_a1 >= 4)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
set_mask_for_ep3_game_command(data.data(), data.size(), mask_key);
|
||||
|
||||
if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
uint8_t mask_key = 0;
|
||||
while (!mask_key) {
|
||||
mask_key = random_object<uint8_t>();
|
||||
}
|
||||
set_mask_for_ep3_game_command(data.data(), data.size(), mask_key);
|
||||
}
|
||||
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user