add some Ep3 command notes

This commit is contained in:
Martin Michelsen
2025-10-22 19:47:23 -07:00
parent be4c7f80cb
commit 01b1f42bac
8 changed files with 102 additions and 75 deletions
+24 -11
View File
@@ -4069,8 +4069,10 @@ struct G_SymbolChat_6x07 {
// equal to 0x1000, so any valid enemy ID would be far outside the array's
// range. newserv unconditionally blocks this command because it appears never
// to be used, and the array write is not bounds-checked, so it could be used
// to cause undefined behavior on other clients. It seems that this broken
// logic predates even DC NTE.
// to cause undefined behavior on other clients. It seems that this logic
// predates even DC NTE; it's likely that this was part of the implementation
// of enemy states before entity IDs and the standard 0xB50-entry array of
// states were introduced.
struct G_LegacyKillEnemy_6x09 {
G_EntityIDHeader header;
@@ -4455,11 +4457,11 @@ struct G_UseMedicalCenter_6x31 {
G_ClientIDHeader header;
} __packed_ws__(G_UseMedicalCenter_6x31, 4);
// 6x32: Revive player (Medical Center)
// 6x32: Revive all players (Medical Center)
struct G_MedicalCenterRevivePlayer_6x32 {
struct G_MedicalCenterReviveAllPlayers_6x32 {
G_UnusedHeader header;
} __packed_ws__(G_MedicalCenterRevivePlayer_6x32, 4);
} __packed_ws__(G_MedicalCenterReviveAllPlayers_6x32, 4);
// 6x33: Revive player (with Moon Atomizer) (protected on V3/V4)
@@ -4692,7 +4694,7 @@ struct G_PlayerDied_6x4D {
le_uint32_t death_flags = 0; // Same as 6x70's death_flags field
} __packed_ws__(G_PlayerDied_6x4D, 8);
// 6x4E: Player is dead can be revived (protected on GC NTE/V3/V4)
// 6x4E: Player can be revived (protected on GC NTE/V3/V4)
// This command creates the particle effect that Reverser and Moon Atomizers
// can target.
@@ -4707,7 +4709,9 @@ struct G_PlayerRevived_6x4F {
} __packed_ws__(G_PlayerRevived_6x4F, 4);
// 6x50: Switch interaction (protected on V3/V4)
// If UDP mode is enabled, this command is sent via UDP.
// If UDP mode is enabled, this command is sent via UDP. This command doesn't
// actually do anything with the switch; it just sets the player's animation
// state. 6x05 is used to set the switch flag if needed.
struct G_SwitchInteraction_6x50 {
G_ClientIDHeader header;
@@ -4804,12 +4808,11 @@ struct G_PickUpItemRequest_6x5A {
// This command has a handler, but it does nothing, even on DC NTE.
// 6x5C: Destroy floor item
// Same format as 6x63. It appears this version should not be used because it
// Same format as 6x63. It appears this command should not be used because it
// removes the item from the floor just like 6x63 does, but 6x5C doesn't call
// the item's destructor.
// 6x5D: Drop meseta or stacked item
// On DC NTE, this command has the same format, but is subcommand 6x4F instead.
struct G_DropStackedItem_DC_6x5D {
G_ClientIDHeader header;
@@ -4898,10 +4901,10 @@ struct G_DestroyFloorItem_6x5C_6x63 {
} __packed_ws__(G_DestroyFloorItem_6x5C_6x63, 0x0C);
// 6x64: Unused (not valid on Episode 3)
// This command has a handler, but it does nothing even on DC NTE.
// This command has a handler, but it does nothing, even on DC NTE.
// 6x65: Unused (not valid on Episode 3)
// This command has a handler, but it does nothing even on DC NTE.
// This command has a handler, but it does nothing, even on DC NTE.
// 6x66: Use star atomizer
@@ -5057,6 +5060,9 @@ struct G_SyncSetFlagState_6x6E_Decompressed {
} __packed_ws__(G_SyncSetFlagState_6x6E_Decompressed, 8);
// 6x6F: Set quest flags (used while loading into game)
// On Episode 3, this command sets the seq vars instead. However, the client
// never sends this, since seq vars don't need to be synced to the entire game
// in online play.
struct G_SetQuestFlags_DCv1_6x6F {
G_UnusedHeader header;
@@ -5068,6 +5074,11 @@ struct G_SetQuestFlags_V2_V3_6x6F {
QuestFlags quest_flags;
} __packed_ws__(G_SetQuestFlags_V2_V3_6x6F, 0x204);
struct G_SetSeqVars_Ep3_6x6F {
G_UnusedHeader header;
Ep3SeqVars seq_vars;
} __packed_ws__(G_SetSeqVars_Ep3_6x6F, 0x404);
struct G_SetQuestFlags_BB_6x6F {
G_UnusedHeader header;
QuestFlags quest_flags;
@@ -5248,6 +5259,7 @@ check_struct_size(G_WordSelect_6x74, 0x20);
check_struct_size(G_WordSelectBE_6x74, 0x20);
// 6x75: Update quest flag
// This command does nothing on Episode 3.
struct G_UpdateQuestFlag_DC_PC_6x75 {
G_UnusedHeader header;
@@ -5272,6 +5284,7 @@ struct G_SetEntitySetFlags_6x76 {
// 6x77: Sync quest register
// This is sent by the client when an opcode D9 is executed within a quest.
// This command does nothing on Episode 3.
struct G_SyncQuestRegister_6x77 {
G_UnusedHeader header;
+1 -16
View File
@@ -309,22 +309,7 @@ PlayerRecordsChallengeBB::operator PlayerRecordsChallengePC() const {
return ret;
}
QuestFlagsV1& QuestFlagsV1::operator=(const QuestFlags& other) {
this->data[0] = other.data[0];
this->data[1] = other.data[1];
this->data[2] = other.data[2];
return *this;
}
QuestFlagsV1::operator QuestFlags() const {
QuestFlags ret;
ret.data[0] = this->data[0];
ret.data[1] = this->data[1];
ret.data[2] = this->data[2];
return ret;
}
const QuestFlagsForDifficulty QuestFlagsForDifficulty::BB_QUEST_FLAG_APPLY_MASK{{
const QuestFlagsForDifficulty BB_QUEST_FLAG_APPLY_MASK{{
// clang-format off
/* 0000 */ 0x00, 0x3F, 0xFF, 0xE3, 0xE0, 0xFF, 0xFF, 0x00,
/* 0040 */ 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
+66 -41
View File
@@ -884,10 +884,25 @@ inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB>(
return src;
}
struct QuestFlagsForDifficulty {
static const QuestFlagsForDifficulty BB_QUEST_FLAG_APPLY_MASK;
template <size_t NumFlags>
struct FlagsArray {
parray<uint8_t, (NumFlags >> 3)> data = 0;
parray<uint8_t, 0x80> data;
FlagsArray() = default;
FlagsArray(const FlagsArray& other) = default;
FlagsArray(FlagsArray&& other) = default;
FlagsArray& operator=(const FlagsArray& other) = default;
FlagsArray& operator=(FlagsArray&& other) = default;
FlagsArray(std::initializer_list<uint8_t> init_items) : data(init_items) {}
template <size_t OtherNumFlags>
explicit FlagsArray(const FlagsArray<OtherNumFlags>& other) : data(other.data) {}
template <size_t OtherNumFlags>
FlagsArray& operator=(const FlagsArray<OtherNumFlags>& other) {
this->data = other.data;
return *this;
}
inline bool get(uint16_t flag_index) const {
size_t byte_index = flag_index >> 3;
@@ -904,6 +919,7 @@ struct QuestFlagsForDifficulty {
uint8_t mask = 0x80 >> (flag_index & 7);
this->data[byte_index] &= (~mask);
}
inline void update_all(bool set) {
if (set) {
this->data.clear(0xFF);
@@ -911,57 +927,66 @@ struct QuestFlagsForDifficulty {
this->data.clear(0x00);
}
}
} __packed_ws__(QuestFlagsForDifficulty, 0x80);
} __attribute__((packed));
struct QuestFlags {
parray<QuestFlagsForDifficulty, 4> data;
template <size_t NumFlagsPerTable, size_t NumTables, typename TableIndexT = size_t>
struct FlagsTable {
parray<FlagsArray<NumFlagsPerTable>, NumTables> data;
inline QuestFlagsForDifficulty& for_difficulty(Difficulty difficulty) {
return this->data[static_cast<size_t>(difficulty)];
}
inline const QuestFlagsForDifficulty& for_difficulty(Difficulty difficulty) const {
return this->data[static_cast<size_t>(difficulty)];
FlagsTable() = default;
FlagsTable(const FlagsTable& other) = default;
FlagsTable(FlagsTable&& other) = default;
FlagsTable& operator=(const FlagsTable& other) = default;
FlagsTable& operator=(FlagsTable&& other) = default;
template <size_t OtherNumFlagsPerTable, size_t OtherNumTables>
explicit FlagsTable(const FlagsTable<OtherNumFlagsPerTable, OtherNumTables, TableIndexT>& other) : data(other.data) {}
template <size_t OtherNumFlagsPerTable, size_t OtherNumTables>
FlagsTable& operator=(const FlagsTable<OtherNumFlagsPerTable, OtherNumTables, TableIndexT>& other) {
this->data = other.data;
return *this;
}
inline bool get(Difficulty difficulty, uint16_t flag_index) const {
return this->for_difficulty(difficulty).get(flag_index);
inline FlagsArray<NumFlagsPerTable>& array(TableIndexT which) {
return this->data[static_cast<size_t>(which)];
}
inline void set(Difficulty difficulty, uint16_t flag_index) {
this->for_difficulty(difficulty).set(flag_index);
inline const FlagsArray<NumFlagsPerTable>& array(TableIndexT which) const {
return this->data[static_cast<size_t>(which)];
}
inline void clear(Difficulty difficulty, uint16_t flag_index) {
this->for_difficulty(difficulty).clear(flag_index);
inline bool get(TableIndexT array_index, size_t flag_index) const {
return this->array(array_index).get(flag_index);
}
inline void update_all(Difficulty difficulty, bool set) {
this->for_difficulty(difficulty).update_all(set);
inline void set(TableIndexT array_index, size_t flag_index) {
this->array(array_index).set(flag_index);
}
inline void clear(TableIndexT array_index, size_t flag_index) {
this->array(array_index).clear(flag_index);
}
inline void update_all(TableIndexT array_index, bool set) {
this->array(array_index).update_all(set);
}
inline void update_all(bool set) {
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
this->update_all(difficulty, set);
for (size_t z = 0; z < this->data.size(); z++) {
this->update_all(z, set);
}
}
} __packed_ws__(QuestFlags, 0x200);
} __attribute__((packed));
struct QuestFlagsV1 {
parray<QuestFlagsForDifficulty, 3> data;
using QuestFlagsForDifficulty = FlagsArray<0x400>;
using QuestFlagsV1 = FlagsTable<0x400, 3, Difficulty>;
using QuestFlags = FlagsTable<0x400, 4, Difficulty>;
using Ep3SeqVars = FlagsArray<0x2000>;
using SwitchFlagsV1 = FlagsTable<0x100, 0x10>;
using SwitchFlags = FlagsTable<0x100, 0x12>;
static_assert(sizeof(QuestFlagsForDifficulty) == 0x80);
static_assert(sizeof(QuestFlagsV1) == 0x180);
static_assert(sizeof(QuestFlags) == 0x200);
static_assert(sizeof(Ep3SeqVars) == 0x400);
static_assert(sizeof(SwitchFlagsV1) == 0x200);
static_assert(sizeof(SwitchFlags) == 0x240);
QuestFlagsV1& operator=(const QuestFlags& other);
operator QuestFlags() const;
} __packed_ws__(QuestFlagsV1, 0x180);
struct SwitchFlags {
parray<parray<uint8_t, 0x20>, 0x12> data;
inline bool get(uint8_t floor, uint16_t flag_num) const {
return this->data[floor][flag_num >> 3] & (0x80 >> (flag_num & 7));
}
inline void set(uint8_t floor, uint16_t flag_num) {
this->data[floor][flag_num >> 3] |= (0x80 >> (flag_num & 7));
}
inline void clear(uint8_t floor, uint16_t flag_num) {
this->data[floor][flag_num >> 3] &= ~(0x80 >> (flag_num & 7));
}
} __packed_ws__(SwitchFlags, 0x240);
extern const QuestFlagsForDifficulty BB_QUEST_FLAG_APPLY_MASK;
struct BattleRules {
enum class TechDiskMode : uint8_t {
+1 -1
View File
@@ -4737,7 +4737,7 @@ shared_ptr<Lobby> create_game_generic(
if (quest_flag_rewrites && !quest_flag_rewrites->empty()) {
IntegralExpression::Env env = {
.flags = &p->quest_flags.for_difficulty(difficulty),
.flags = &p->quest_flags.array(difficulty),
.challenge_records = &p->challenge_records,
.team = creator_c->team(),
.num_players = 1,
+1 -1
View File
@@ -694,7 +694,7 @@ static asio::awaitable<void> on_sync_joining_player_compressed_state(shared_ptr<
// player), it's not surprising that no one noticed this. But it does
// mean we have to check switch_flags_r.eof() here.
for (size_t z = 0; (z < 0x20) && !switch_flags_r.eof(); z++) {
uint8_t& l_flags = l->switch_flags->data[floor][z];
uint8_t& l_flags = l->switch_flags->array(floor).data[z];
uint8_t r_flags = switch_flags_r.get_u8();
if (l_flags != r_flags) {
l->log.warning_f("Switch flags do not match at floor {:02X} byte {:02X} (expected {:02X}, received {:02X})",
+2 -2
View File
@@ -681,7 +681,7 @@ struct PSOGCEp3NTECharacter {
/* 0430:0014 */ be_uint32_t save_count = 1;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
/* 0460:0044 */ parray<uint8_t, 0x400> seq_vars;
/* 0460:0044 */ Ep3SeqVars seq_vars;
/* 0860:0444 */ be_uint32_t death_count = 0;
/* 0864:0448 */ PlayerBank200BE bank;
/* 1B2C:1710 */ GuildCardGCBE guild_card;
@@ -722,7 +722,7 @@ struct PSOGCEp3CharacterFile {
// NPC decks are unlocked, and whether the player has a VIP card or not.
// Logically, this structure maps to quest_flags in other versions, but is
// a different size.
/* 0460:0044 */ parray<uint8_t, 0x400> seq_vars;
/* 0460:0044 */ Ep3SeqVars seq_vars;
/* 0860:0444 */ be_uint32_t death_count = 0;
// Curiously, Episode 3 characters do have item banks, but there are only 4
// item slots. Presumably Sega didn't completely remove the bank in Ep3
+2 -2
View File
@@ -2956,8 +2956,8 @@ void send_game_flag_state_t(shared_ptr<Client> c) {
if ((difficulty != l->difficulty) && !use_v3_cmd) {
continue;
}
const auto& diff_flags = l->quest_flag_values->for_difficulty(difficulty);
const auto& diff_known_flags = l->quest_flags_known->for_difficulty(difficulty);
const auto& diff_flags = l->quest_flag_values->array(difficulty);
const auto& diff_known_flags = l->quest_flags_known->array(difficulty);
for (uint8_t z = 0; z < diff_known_flags.data.size(); z++) {
uint8_t known_flags = diff_known_flags.data[z];
if (!known_flags) {
+5 -1
View File
@@ -243,7 +243,11 @@ struct parray {
this->items[x] = s.items[x];
}
for (; x < Count; x++) {
this->items[x] = 0;
if constexpr (std::is_integral_v<ItemT>) {
this->items[x] = 0;
} else {
this->items[x] = ItemT();
}
}
} else {
for (size_t x = 0; x < Count; x++) {