split Episode3::DataIndex into multiple structures

This commit is contained in:
Martin Michelsen
2023-08-13 23:12:27 -07:00
parent 7e55719983
commit 87118049ab
36 changed files with 416 additions and 328 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ add_executable(newserv
src/Episode3/BattleRecord.cc
src/Episode3/Card.cc
src/Episode3/CardSpecial.cc
src/Episode3/DataIndex.cc
src/Episode3/DataIndexes.cc
src/Episode3/DeckState.cc
src/Episode3/MapState.cc
src/Episode3/PlayerState.cc
+4 -2
View File
@@ -68,6 +68,7 @@ Current known issues / missing features / things to do:
- Implement the C5 (battle/challenge records) command.
- Implement choice search.
- Episode 3 bugs
- Trial Edition can't select maps in battle setup. Fix this.
- Fix behavior when joining a spectator team after the beginning of a battle.
- Disconnecting during a match turns you into a COM if there are other humans in the match, even if the match is part of a tournament. This may be incorrect behavior for tournaments.
- Disconnecting during a tournament when there are no other humans in the match simply cancels the match (so it can be replayed) instead of forfeiting, which is almost certainly incorrect behavior. (Then again, no one likes losing tournaments to COMs...)
@@ -89,7 +90,7 @@ newserv supports several versions of PSO. Specifically:
| GC Ep1&2 Trial | Untested (2) | Untested (2) | Untested (2) | Untested (2) |
| GC Ep1&2 | Yes | Yes | Yes | Yes |
| GC Ep1&2 Plus | Yes | Yes | Yes | Yes |
| GC Ep3 Trial | Yes | Yes | Yes | Yes |
| GC Ep3 Trial | Yes | Yes | Partial (5) | Yes |
| GC Ep3 | Yes | Yes | Yes | Yes |
| XBOX Ep1&2 | Untested (2) | Untested (2) | Untested (2) | Untested (2) |
| BB (vanilla) | Yes | Yes | Yes (3) | Yes |
@@ -100,6 +101,7 @@ newserv supports several versions of PSO. Specifically:
2. *newserv's implementations of these versions are based on disassembly of the client executables and have never been tested.*
3. *BB games are mostly playable, but there are still some unimplemented features (for example, some quests that use rare commands may not work). Please submit a GitHub issue if you find something that doesn't work.*
4. *Support for PSO Dreamcast Trial Edition and the December 2000 prototype is somewhat incomplete and probably never will be complete. These versions are rather unstable and seem to crash often, but it's not obvious whether it's because they're prototypes or because newserv sends data they can't handle.*
5. *Creating a game works, but choosing a map during battle setup causes the Trial Edition client to crash. This is likely due to the trial version's map format being slightly different from the final version's map format.*
## Setup
@@ -201,7 +203,7 @@ Episode 3 state and game data is stored in the system/ep3 directory. The files i
* maps-free/ and maps-quest/: Online free battle and quest maps (.mnm/.bin/.mnmd/.bind files). Free battle and quest files have exactly the same format; the only difference between the files in these directories is which section of the menu they will appear in on the client.
* tournament-state.json: State of all active tournaments. This file is automatically written when any tournament changes state for any reason (e.g. a tournament is created/started/deleted or a match is resolved).
There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndex.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available.
There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndexes.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available.
Like quests, Episode 3 card definitions, maps, and quests are cached in memory. If you've changed any of these files, you can run `reload ep3` in the interactive shell to make the changes take effect without restarting the server.
+3 -3
View File
@@ -5,7 +5,7 @@
#include <stdexcept>
#include <string>
#include "Episode3/DataIndex.hh"
#include "Episode3/DataIndexes.hh"
#include "Episode3/DeckState.hh"
#include "Episode3/MapState.hh"
#include "Episode3/PlayerStateSubordinates.hh"
@@ -5178,8 +5178,8 @@ struct G_MapList_GC_Ep3_6xB6x40 {
le_uint16_t compressed_data_size;
le_uint16_t unused;
// PRS-compressed map list data follows here. newserv generates this from the
// map index at startup time; see the MapList struct in Episode3/DataIndex.hh
// and Episode3::DataIndex::get_compressed_map_list for details on the format.
// map index at startup; see the MapList struct in Episode3/DataIndexes.hh
// and Episode3::MapIndex::get_compressed_map_list for details on the format.
} __packed__;
struct G_MapData_GC_Ep3_6xB6x41 {
+1 -1
View File
@@ -133,7 +133,7 @@ uint16_t AssistServer::card_id_for_card_ref(uint16_t card_ref) const {
return this->server()->card_id_for_card_ref(card_ref);
}
shared_ptr<const DataIndex::CardEntry> AssistServer::definition_for_card_id(
shared_ptr<const CardIndex::CardEntry> AssistServer::definition_for_card_id(
uint16_t card_id) const {
return this->server()->definition_for_card_id(card_id);
}
+4 -4
View File
@@ -5,7 +5,7 @@
#include <memory>
#include <vector>
#include "DataIndex.hh"
#include "DataIndexes.hh"
#include "DeckState.hh"
#include "PlayerState.hh"
@@ -24,7 +24,7 @@ public:
std::shared_ptr<const Server> server() const;
uint16_t card_id_for_card_ref(uint16_t card_ref) const;
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_id(uint16_t card_id) const;
std::shared_ptr<const CardIndex::CardEntry> definition_for_card_id(uint16_t card_id) const;
uint32_t compute_num_assist_effects_for_client(uint16_t client_id);
uint32_t compute_num_assist_effects_for_team(uint32_t team_id);
@@ -40,11 +40,11 @@ private:
public:
parray<AssistEffect, 4> assist_effects;
std::shared_ptr<const DataIndex::CardEntry> assist_card_defs[4];
std::shared_ptr<const CardIndex::CardEntry> assist_card_defs[4];
uint32_t num_assist_cards_set;
parray<uint8_t, 4> client_ids_with_assists;
parray<AssistEffect, 4> active_assist_effects;
std::shared_ptr<const DataIndex::CardEntry> active_assist_card_defs[4];
std::shared_ptr<const CardIndex::CardEntry> active_assist_card_defs[4];
uint32_t num_active_assists;
std::shared_ptr<HandAndEquipState> hand_and_equip_states[4];
std::shared_ptr<parray<CardShortStatus, 0x10>> card_short_statuses[4];
+1 -1
View File
@@ -507,7 +507,7 @@ bool Card::get_attack_condition_value(
cond_type, card_ref, def_effect_index, value, out_value);
}
shared_ptr<const DataIndex::CardEntry> Card::get_definition() const {
shared_ptr<const CardIndex::CardEntry> Card::get_definition() const {
return this->def_entry;
}
+4 -4
View File
@@ -6,7 +6,7 @@
#include "../CommandFormats.hh"
#include "../Text.hh"
#include "DataIndex.hh"
#include "DataIndexes.hh"
namespace Episode3 {
@@ -58,7 +58,7 @@ public:
uint8_t def_effect_index,
uint16_t value,
uint16_t* out_value) const;
std::shared_ptr<const DataIndex::CardEntry> get_definition() const;
std::shared_ptr<const CardIndex::CardEntry> get_definition() const;
uint16_t get_card_ref() const;
uint8_t get_client_id() const;
uint8_t get_current_hp() const;
@@ -99,12 +99,12 @@ private:
public:
int16_t max_hp;
int16_t current_hp;
std::shared_ptr<const DataIndex::CardEntry> def_entry;
std::shared_ptr<const CardIndex::CardEntry> def_entry;
uint8_t client_id;
uint16_t card_id;
uint16_t card_ref;
uint16_t sc_card_ref;
std::shared_ptr<const DataIndex::CardEntry> sc_def_entry;
std::shared_ptr<const CardIndex::CardEntry> sc_def_entry;
CardType sc_card_type;
uint8_t team_id;
uint32_t card_flags;
+14 -14
View File
@@ -816,7 +816,7 @@ shared_ptr<Card> CardSpecial::compute_replaced_target_based_on_conditions(
// the Gifoie card's ID (00D9) for compute_effective_range.
// TODO: We should fix this so it doesn't rely on a fixed card definition.
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, 0x00D9, target_card_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, 0x00D9, target_card_loc, this->server()->base()->map_and_rules1);
auto card_refs_in_parry_range = target_ps->get_all_cards_within_range(
range, target_card_loc, 0xFF);
@@ -2526,7 +2526,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
if (ce && ps) {
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
add_card_refs(ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM));
}
}
@@ -2566,7 +2566,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
if (ce && ps) {
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()));
}
}
@@ -2631,7 +2631,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// should fix this eventually.
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2655,7 +2655,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Again with the Gifoie hardcoding...
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2721,7 +2721,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Again with the Gifoie hardcoding...
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2775,7 +2775,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Yet another Gifoie hardcode location :(
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2802,7 +2802,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Sigh. Gifoie again.
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2898,7 +2898,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// Slay instead of Gifoie
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x009C, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2927,7 +2927,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Sigh. Gifoie. Sigh.
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2965,7 +2965,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: One more Gifoie here.
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -3383,7 +3383,7 @@ void CardSpecial::check_for_defense_interference(
shared_ptr<Card> target_card,
int16_t* inout_unknown_p4) {
// Note: This check is not part of the original implementation.
if (this->server()->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
if (this->server()->base()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
return;
}
@@ -4126,7 +4126,7 @@ vector<shared_ptr<const Card>> CardSpecial::filter_cards_by_range(
// TODO: Remove hardcoded card ID here (Earthquake)
uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2);
parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, card_id, card1_loc, this->server()->base()->map_and_rules1);
compute_effective_range(range, this->server()->base()->card_index, card_id, card1_loc, this->server()->base()->map_and_rules1);
auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM);
for (auto card : cards) {
@@ -4342,7 +4342,7 @@ void CardSpecial::unknown_8024A9D8(const ActionState& pa, uint16_t action_card_r
void CardSpecial::check_for_attack_interference(shared_ptr<Card> unknown_p2) {
// Note: This check is not part of the original implementation.
if (this->server()->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
if (this->server()->base()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
return;
}
+1 -1
View File
@@ -5,7 +5,7 @@
#include <memory>
#include "../Text.hh"
#include "DataIndex.hh"
#include "DataIndexes.hh"
namespace Episode3 {
@@ -1,4 +1,4 @@
#include "DataIndex.hh"
#include "DataIndexes.hh"
#include <stdint.h>
@@ -1056,6 +1056,33 @@ Card: %04" PRIX32 " \"%s\"\n\
}
}
void PlayerConfig::decrypt() {
if (!this->is_encrypted) {
return;
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
this->basis);
this->is_encrypted = 0;
this->basis = 0;
}
void PlayerConfig::encrypt(uint8_t basis) {
if (this->is_encrypted) {
if (this->basis == basis) {
return;
}
this->decrypt();
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
basis);
this->is_encrypted = 1;
this->basis = basis;
}
HPType hp_type_for_name(const char* name) {
if (!strcmp(name, "DEFEAT_PLAYER")) {
return HPType::DEFEAT_PLAYER;
@@ -1351,7 +1378,7 @@ void StateFlags::clear_FF() {
this->client_sc_card_types.clear(CardType::INVALID_FF);
}
string MapDefinition::str(const DataIndex* data_index) const {
string MapDefinition::str(const CardIndex* card_index) const {
deque<string> lines;
auto add_map = [&](const parray<parray<uint8_t, 0x10>, 0x10>& tiles) {
for (size_t y = 0; y < 0x10; y++) {
@@ -1456,10 +1483,10 @@ string MapDefinition::str(const DataIndex* data_index) const {
lines.emplace_back(" name: " + string(this->npc_decks[z].name));
for (size_t w = 0; w < 0x20; w++) {
uint16_t card_id = this->npc_decks[z].card_ids[w];
shared_ptr<const DataIndex::CardEntry> entry;
if (data_index) {
shared_ptr<const CardIndex::CardEntry> entry;
if (card_index) {
try {
entry = data_index->definition_for_card_id(card_id);
entry = card_index->definition_for_id(card_id);
} catch (const out_of_range&) {
}
}
@@ -1496,10 +1523,10 @@ string MapDefinition::str(const DataIndex* data_index) const {
}
for (size_t z = 0; z < 0x10; z++) {
uint16_t card_id = this->reward_card_ids[z];
shared_ptr<const DataIndex::CardEntry> entry;
if (data_index) {
shared_ptr<const CardIndex::CardEntry> entry;
if (card_index) {
try {
entry = data_index->definition_for_card_id(card_id);
entry = card_index->definition_for_id(card_id);
} catch (const out_of_range&) {
}
}
@@ -1671,14 +1698,12 @@ bool Rules::check_and_reset_invalid_fields() {
return ret;
}
DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
: behavior_flags(behavior_flags) {
CardIndex::CardIndex(const string& filename, const string& decompressed_filename, const string& text_filename) {
unordered_map<uint32_t, vector<string>> card_tags;
unordered_map<uint32_t, string> card_text;
if (this->behavior_flags & BehaviorFlag::LOAD_CARD_TEXT) {
if (!text_filename.empty()) {
try {
string data = prs_decompress(load_file(directory + "/card-text.mnr"));
string data = prs_decompress(load_file(text_filename));
StringReader r(data);
while (!r.eof()) {
@@ -1769,20 +1794,20 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
try {
string decompressed_data;
if (isfile(directory + "/card-definitions.mnrd")) {
this->mtime_for_card_definitions = stat(directory + "/card-definitions.mnrd").st_mtime;
decompressed_data = load_file(directory + "/card-definitions.mnrd");
this->mtime_for_card_definitions = stat(filename).st_mtime;
try {
decompressed_data = load_file(decompressed_filename);
this->compressed_card_definitions.clear();
} else {
this->mtime_for_card_definitions = stat(directory + "/card-definitions.mnr").st_mtime;
this->compressed_card_definitions = load_file(directory + "/card-definitions.mnr");
} catch (const cannot_open_file&) {
this->compressed_card_definitions = load_file(filename);
decompressed_data = prs_decompress(this->compressed_card_definitions);
}
if (decompressed_data.size() > 0x36EC0) {
throw runtime_error("decompressed card list data is too long");
}
// There's a footer after the card definitions, but we ignore it
// There's a footer after the card definitions (it's a standard-format REL
// file), but we ignore it
if (decompressed_data.size() % sizeof(CardDefinition) != sizeof(CardDefinitionsFooter)) {
throw runtime_error(string_printf(
"decompressed card update file size %zX is not aligned with card definition size %zX (%zX extra bytes)",
@@ -1792,10 +1817,9 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
size_t max_cards = decompressed_data.size() / sizeof(CardDefinition);
for (size_t x = 0; x < max_cards; x++) {
// The last card entry has the build date and some other metadata (and
// isn't a real card, obviously), so skip it. Seems like the card ID is
// always a large number that won't fit in a uint16_t, so we use that to
// determine if the entry is a real card or not.
if (defs[x].card_id & 0xFFFF0000) {
// isn't a real card, obviously), so skip it. The game detects this by
// checking for a negative value in type, which we also do here.
if (static_cast<int8_t>(defs[x].type) < 0) {
continue;
}
@@ -1816,7 +1840,7 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
entry->def.mv.decode_code();
entry->def.decode_range();
if (this->behavior_flags & BehaviorFlag::LOAD_CARD_TEXT) {
if (!text_filename.empty()) {
try {
entry->text = std::move(card_text.at(defs[x].card_id));
} catch (const out_of_range&) {
@@ -1836,6 +1860,25 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
"Compressed card definitions (%zu bytes -> %zu bytes) in %" PRIu64 "ms",
decompressed_data.size(), this->compressed_card_definitions.size(), diff);
}
if (this->compressed_card_definitions.size() > 0x7BF8) {
// Try to reduce the compressed size by clearing out text
static_game_data_log.info("Compressed card list data is too long; removing text");
for (size_t x = 0; x < max_cards; x++) {
if (static_cast<int8_t>(defs[x].type) < 0) {
continue;
}
defs[x].jp_name.clear();
}
uint64_t start = now();
this->compressed_card_definitions = prs_compress(decompressed_data);
uint64_t diff = now() - start;
static_game_data_log.info(
"Compressed card definitions (%zu bytes -> %zu bytes) in %" PRIu64 "ms",
decompressed_data.size(), this->compressed_card_definitions.size(), diff);
print_data(stderr, this->compressed_card_definitions);
}
if (this->compressed_card_definitions.size() > 0x7BF8) {
throw runtime_error("compressed card list data is too long");
}
@@ -1844,7 +1887,36 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
} catch (const exception& e) {
static_game_data_log.warning("Failed to load Episode 3 card update: %s", e.what());
}
}
const string& CardIndex::get_compressed_definitions() const {
if (this->compressed_card_definitions.empty()) {
throw runtime_error("card definitions are not available");
}
return this->compressed_card_definitions;
}
shared_ptr<const CardIndex::CardEntry> CardIndex::definition_for_id(uint32_t id) const {
return this->card_definitions.at(id);
}
shared_ptr<const CardIndex::CardEntry> CardIndex::definition_for_name(const string& name) const {
return this->card_definitions_by_name.at(name);
}
set<uint32_t> CardIndex::all_ids() const {
set<uint32_t> ret;
for (const auto& it : this->card_definitions) {
ret.emplace(it.first);
}
return ret;
}
uint64_t CardIndex::definitions_mtime() const {
return this->mtime_for_card_definitions;
}
MapIndex::MapIndex(const string& directory) {
auto add_maps_from_dir = [&](const string& dir, bool is_quest) -> void {
for (const auto& filename : list_directory(dir)) {
try {
@@ -1879,32 +1951,13 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
};
add_maps_from_dir(directory + "/maps-free", false);
add_maps_from_dir(directory + "/maps-quest", true);
try {
auto json = JSONObject::parse(load_file(directory + "/com-decks.json"));
for (const auto& def_json : json->as_list()) {
auto& def = this->com_decks.emplace_back(new COMDeckDefinition());
def->index = this->com_decks.size() - 1;
def->player_name = def_json->at(0)->as_string();
def->deck_name = def_json->at(1)->as_string();
auto card_ids_json = def_json->at(2)->as_list();
for (size_t z = 0; z < 0x1F; z++) {
def->card_ids[z] = card_ids_json.at(z)->as_int();
}
if (!this->com_decks_by_name.emplace(def->deck_name, def).second) {
throw runtime_error("duplicate COM deck name: " + def->deck_name);
}
}
} catch (const exception& e) {
static_game_data_log.warning("Failed to load Episode 3 COM decks: %s", e.what());
}
}
DataIndex::MapEntry::MapEntry(const MapDefinition& map, bool is_quest)
MapIndex::MapEntry::MapEntry(const MapDefinition& map, bool is_quest)
: map(map),
is_quest(is_quest) {}
DataIndex::MapEntry::MapEntry(const string& compressed, bool is_quest)
MapIndex::MapEntry::MapEntry(const string& compressed, bool is_quest)
: is_quest(is_quest),
compressed_data(compressed) {
string decompressed = prs_decompress(this->compressed_data);
@@ -1916,43 +1969,14 @@ DataIndex::MapEntry::MapEntry(const string& compressed, bool is_quest)
this->map = *reinterpret_cast<const MapDefinition*>(decompressed.data());
}
string DataIndex::MapEntry::compressed() const {
string MapIndex::MapEntry::compressed() const {
if (this->compressed_data.empty()) {
this->compressed_data = prs_compress(&this->map, sizeof(this->map));
}
return this->compressed_data;
}
const string& DataIndex::get_compressed_card_definitions() const {
if (this->compressed_card_definitions.empty()) {
throw runtime_error("card definitions are not available");
}
return this->compressed_card_definitions;
}
shared_ptr<const DataIndex::CardEntry> DataIndex::definition_for_card_id(
uint32_t id) const {
return this->card_definitions.at(id);
}
shared_ptr<const DataIndex::CardEntry> DataIndex::definition_for_card_name(
const string& name) const {
return this->card_definitions_by_name.at(name);
}
set<uint32_t> DataIndex::all_card_ids() const {
set<uint32_t> ret;
for (const auto& it : this->card_definitions) {
ret.emplace(it.first);
}
return ret;
}
uint64_t DataIndex::card_definitions_mtime() const {
return this->mtime_for_card_definitions;
}
const string& DataIndex::get_compressed_map_list() const {
const string& MapIndex::get_compressed_list() const {
if (this->compressed_map_list.empty()) {
StringWriter entries_w;
StringWriter strings_w;
@@ -2012,16 +2036,15 @@ const string& DataIndex::get_compressed_map_list() const {
return this->compressed_map_list;
}
shared_ptr<const DataIndex::MapEntry> DataIndex::definition_for_map_number(uint32_t id) const {
shared_ptr<const MapIndex::MapEntry> MapIndex::definition_for_number(uint32_t id) const {
return this->maps.at(id);
}
shared_ptr<const DataIndex::MapEntry> DataIndex::definition_for_map_name(
const string& name) const {
shared_ptr<const MapIndex::MapEntry> MapIndex::definition_for_name(const string& name) const {
return this->maps_by_name.at(name);
}
set<uint32_t> DataIndex::all_map_ids() const {
set<uint32_t> MapIndex::all_numbers() const {
set<uint32_t> ret;
for (const auto& it : this->maps) {
ret.emplace(it.first);
@@ -2029,47 +2052,41 @@ set<uint32_t> DataIndex::all_map_ids() const {
return ret;
}
size_t DataIndex::num_com_decks() const {
return this->com_decks.size();
}
shared_ptr<const COMDeckDefinition> DataIndex::com_deck(size_t which) const {
return this->com_decks.at(which);
}
shared_ptr<const COMDeckDefinition> DataIndex::com_deck(const string& which) const {
return this->com_decks_by_name.at(which);
}
shared_ptr<const COMDeckDefinition> DataIndex::random_com_deck() const {
return this->com_decks[random_object<size_t>() % this->com_decks.size()];
}
void PlayerConfig::decrypt() {
if (!this->is_encrypted) {
return;
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
this->basis);
this->is_encrypted = 0;
this->basis = 0;
}
void PlayerConfig::encrypt(uint8_t basis) {
if (this->is_encrypted) {
if (this->basis == basis) {
return;
COMDeckIndex::COMDeckIndex(const string& filename) {
try {
auto json = JSONObject::parse(load_file(filename));
for (const auto& def_json : json->as_list()) {
auto& def = this->decks.emplace_back(new COMDeckDefinition());
def->index = this->decks.size() - 1;
def->player_name = def_json->at(0)->as_string();
def->deck_name = def_json->at(1)->as_string();
auto card_ids_json = def_json->at(2)->as_list();
for (size_t z = 0; z < 0x1F; z++) {
def->card_ids[z] = card_ids_json.at(z)->as_int();
}
if (!this->decks_by_name.emplace(def->deck_name, def).second) {
throw runtime_error("duplicate COM deck name: " + def->deck_name);
}
}
this->decrypt();
} catch (const exception& e) {
static_game_data_log.warning("Failed to load Episode 3 COM decks: %s", e.what());
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
basis);
this->is_encrypted = 1;
this->basis = basis;
}
size_t COMDeckIndex::num_decks() const {
return this->decks.size();
}
shared_ptr<const COMDeckDefinition> COMDeckIndex::deck_for_index(size_t which) const {
return this->decks.at(which);
}
shared_ptr<const COMDeckDefinition> COMDeckIndex::deck_for_name(const string& which) const {
return this->decks_by_name.at(which);
}
shared_ptr<const COMDeckDefinition> COMDeckIndex::random_deck() const {
return this->decks[random_object<size_t>() % this->decks.size()];
}
} // namespace Episode3
@@ -15,21 +15,22 @@
namespace Episode3 {
// The comment in Server.hh does not apply to this file (and DataIndex.cc).
// The comment in Server.hh does not apply to this file (and DataIndexes.cc).
// Except for the Location structure, these structures and functions are not
// based on Sega's original implementation.
class DataIndex;
class CardIndex;
class MapIndex;
class COMDeckIndex;
const char* name_for_link_color(uint8_t color);
enum BehaviorFlag {
enum BehaviorFlag : uint32_t {
SKIP_DECK_VERIFY = 0x00000001,
IGNORE_CARD_COUNTS = 0x00000002,
SKIP_D1_D2_REPLACE = 0x00000004,
DISABLE_TIME_LIMITS = 0x00000008,
ENABLE_STATUS_MESSAGES = 0x00000010,
LOAD_CARD_TEXT = 0x00000020,
ENABLE_RECORDING = 0x00000040,
DISABLE_MASKING = 0x00000080,
DISABLE_INTERFERENCE = 0x00000100,
@@ -952,10 +953,9 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
uint8_t deck_type;
} __attribute__((packed));
/* 5A10 */ parray<EntryState, 4> entry_states;
/* 5A18 */
std::string str(const DataIndex* data_index = nullptr) const;
std::string str(const CardIndex* card_index = nullptr) const;
} __attribute__((packed));
struct COMDeckDefinition {
@@ -965,9 +965,9 @@ struct COMDeckDefinition {
parray<le_uint16_t, 0x1F> card_ids;
};
class DataIndex {
class CardIndex {
public:
DataIndex(const std::string& directory, uint32_t behavior_flags);
CardIndex(const std::string& filename, const std::string& decompressed_filename, const std::string& text_filename = "");
struct CardEntry {
CardDefinition def;
@@ -975,6 +975,23 @@ public:
std::vector<std::string> debug_tags; // Empty unless debug == true
};
const std::string& get_compressed_definitions() const;
std::shared_ptr<const CardEntry> definition_for_id(uint32_t id) const;
std::shared_ptr<const CardEntry> definition_for_name(const std::string& name) const;
std::set<uint32_t> all_ids() const;
uint64_t definitions_mtime() const;
private:
std::string compressed_card_definitions;
std::unordered_map<uint32_t, std::shared_ptr<CardEntry>> card_definitions;
std::unordered_map<std::string, std::shared_ptr<CardEntry>> card_definitions_by_name;
uint64_t mtime_for_card_definitions;
};
class MapIndex {
public:
MapIndex(const std::string& directory);
class MapEntry {
public:
MapDefinition map;
@@ -989,42 +1006,33 @@ public:
mutable std::string compressed_data;
};
const std::string& get_compressed_card_definitions() const;
std::shared_ptr<const CardEntry> definition_for_card_id(uint32_t id) const;
std::shared_ptr<const CardEntry> definition_for_card_name(
const std::string& name) const;
std::set<uint32_t> all_card_ids() const;
uint64_t card_definitions_mtime() const;
const std::string& get_compressed_map_list() const;
std::shared_ptr<const MapEntry> definition_for_map_number(uint32_t id) const;
std::shared_ptr<const MapEntry> definition_for_map_name(
const std::string& name) const;
std::set<uint32_t> all_map_ids() const;
size_t num_com_decks() const;
std::shared_ptr<const COMDeckDefinition> com_deck(size_t which) const;
std::shared_ptr<const COMDeckDefinition> com_deck(const std::string& name) const;
std::shared_ptr<const COMDeckDefinition> random_com_deck() const;
const uint32_t behavior_flags;
const std::string& get_compressed_list() const;
std::shared_ptr<const MapEntry> definition_for_number(uint32_t id) const;
std::shared_ptr<const MapEntry> definition_for_name(const std::string& name) const;
std::set<uint32_t> all_numbers() const;
private:
std::string compressed_card_definitions;
std::unordered_map<uint32_t, std::shared_ptr<CardEntry>> card_definitions;
std::unordered_map<std::string, std::shared_ptr<CardEntry>> card_definitions_by_name;
uint64_t mtime_for_card_definitions;
// The compressed map list is generated on demand from the maps map below.
// It's marked mutable because the logical consistency of the DataIndex object
// It's marked mutable because the logical consistency of the MapIndex object
// is not violated from the caller's perspective even if we don't generate the
// compressed map list at load time.
mutable std::string compressed_map_list;
std::map<uint32_t, std::shared_ptr<MapEntry>> maps;
std::unordered_map<std::string, std::shared_ptr<MapEntry>> maps_by_name;
};
std::vector<std::shared_ptr<COMDeckDefinition>> com_decks;
std::unordered_map<std::string, std::shared_ptr<COMDeckDefinition>> com_decks_by_name;
class COMDeckIndex {
public:
COMDeckIndex(const std::string& filename);
size_t num_decks() const;
std::shared_ptr<const COMDeckDefinition> deck_for_index(size_t which) const;
std::shared_ptr<const COMDeckDefinition> deck_for_name(const std::string& name) const;
std::shared_ptr<const COMDeckDefinition> random_deck() const;
private:
std::vector<std::shared_ptr<COMDeckDefinition>> decks;
std::unordered_map<std::string, std::shared_ptr<COMDeckDefinition>> decks_by_name;
};
} // namespace Episode3
+1 -1
View File
@@ -5,7 +5,7 @@
#include <memory>
#include "../Text.hh"
#include "DataIndex.hh"
#include "DataIndexes.hh"
namespace Episode3 {
+1 -1
View File
@@ -6,7 +6,7 @@
#include "../Text.hh"
#include "Card.hh"
#include "DataIndex.hh"
#include "DataIndexes.hh"
#include "DeckState.hh"
#include "PlayerStateSubordinates.hh"
+1 -1
View File
@@ -5,7 +5,7 @@
#include <memory>
#include "../Text.hh"
#include "DataIndex.hh"
#include "DataIndexes.hh"
namespace Episode3 {
+8 -9
View File
@@ -8,7 +8,7 @@ namespace Episode3 {
void compute_effective_range(
parray<uint8_t, 9 * 9>& ret,
shared_ptr<const DataIndex> data_index,
shared_ptr<const CardIndex> card_index,
uint16_t card_id,
const Location& loc,
shared_ptr<const MapAndRulesState> map_and_rules) {
@@ -19,9 +19,9 @@ void compute_effective_range(
// Heavy Fog: one tile directly in front
range_def[3] = 0x00000100;
} else {
shared_ptr<const DataIndex::CardEntry> ce;
shared_ptr<const CardIndex::CardEntry> ce;
try {
ce = data_index->definition_for_card_id(card_id);
ce = card_index->definition_for_id(card_id);
} catch (const out_of_range&) {
return;
}
@@ -126,9 +126,9 @@ void compute_effective_range(
}
bool card_linkage_is_valid(
shared_ptr<const DataIndex::CardEntry> right_ce,
shared_ptr<const DataIndex::CardEntry> left_ce,
shared_ptr<const DataIndex::CardEntry> sc_ce,
shared_ptr<const CardIndex::CardEntry> right_ce,
shared_ptr<const CardIndex::CardEntry> left_ce,
shared_ptr<const CardIndex::CardEntry> sc_ce,
bool has_permission_effect) {
if (!right_ce) {
return false;
@@ -1613,7 +1613,7 @@ bool RulerServer::defense_card_matches_any_attack_card_top_color(
return false;
}
shared_ptr<const DataIndex::CardEntry> RulerServer::definition_for_card_ref(uint16_t card_ref) const {
shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_ref(uint16_t card_ref) const {
uint16_t card_id = this->card_id_for_card_ref(card_ref);
if (card_id == 0xFFFF) {
return nullptr;
@@ -1972,8 +1972,7 @@ uint16_t RulerServer::get_ally_sc_card_ref(uint16_t card_ref) const {
return 0xFFFF;
}
shared_ptr<const DataIndex::CardEntry> RulerServer::definition_for_card_id(
uint32_t card_id) const {
shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_id(uint32_t card_id) const {
return this->server()->definition_for_card_id(card_id);
}
+7 -7
View File
@@ -5,7 +5,7 @@
#include <memory>
#include "AssistServer.hh"
#include "DataIndex.hh"
#include "DataIndexes.hh"
#include "DeckState.hh"
#include "PlayerState.hh"
@@ -15,15 +15,15 @@ class Server;
void compute_effective_range(
parray<uint8_t, 9 * 9>& ret,
std::shared_ptr<const DataIndex> data_index,
std::shared_ptr<const CardIndex> card_index,
uint16_t card_id,
const Location& loc,
std::shared_ptr<const MapAndRulesState> map_and_rules);
bool card_linkage_is_valid(
std::shared_ptr<const DataIndex::CardEntry> right_def,
std::shared_ptr<const DataIndex::CardEntry> left_def,
std::shared_ptr<const DataIndex::CardEntry> sc_def,
std::shared_ptr<const CardIndex::CardEntry> right_def,
std::shared_ptr<const CardIndex::CardEntry> left_def,
std::shared_ptr<const CardIndex::CardEntry> sc_def,
bool has_permission_effect);
class RulerServer {
@@ -130,7 +130,7 @@ public:
uint16_t attacker_sc_card_ref) const;
bool defense_card_matches_any_attack_card_top_color(
const ActionState& pa) const;
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const;
std::shared_ptr<const CardIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const;
int32_t error_code_for_client_setting_card(
uint8_t client_id,
uint16_t card_ref,
@@ -156,7 +156,7 @@ public:
size_t num_occupied_tiles,
size_t num_vacant_tiles) const;
uint16_t get_ally_sc_card_ref(uint16_t card_ref) const;
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_id(
std::shared_ptr<const CardIndex::CardEntry> definition_for_card_id(
uint32_t card_id) const;
uint32_t get_card_id_with_effective_range(
uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const;
+22 -20
View File
@@ -26,11 +26,15 @@ void ServerBase::PresenceEntry::clear() {
ServerBase::ServerBase(
shared_ptr<Lobby> lobby,
shared_ptr<const DataIndex> data_index,
shared_ptr<const CardIndex> card_index,
shared_ptr<const MapIndex> map_index,
uint32_t behavior_flags,
shared_ptr<PSOLFGEncryption> random_crypt,
shared_ptr<const DataIndex::MapEntry> map_if_tournament)
shared_ptr<const MapIndex::MapEntry> map_if_tournament)
: lobby(lobby),
data_index(data_index),
card_index(card_index),
map_index(map_index),
behavior_flags(behavior_flags),
log(lobby->log.prefix + "[Ep3::Server] "),
random_crypt(random_crypt),
is_tournament(!!map_if_tournament),
@@ -182,7 +186,7 @@ void Server::send(const void* data, size_t size) const {
}
string masked_data;
if (!(this->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_MASKING)) {
if (!(this->base()->behavior_flags & BehaviorFlag::DISABLE_MASKING)) {
if (size >= 8) {
masked_data.assign(reinterpret_cast<const char*>(data), size);
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
@@ -216,13 +220,12 @@ void Server::send_6xB4x46() const {
G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46;
cmd46.version_signature = VERSION_SIGNATURE;
cmd46.date_str1 = format_time(this->base()->data_index->card_definitions_mtime() * 1000000);
cmd46.date_str1 = format_time(this->base()->card_index->definitions_mtime() * 1000000);
cmd46.date_str2 = string_printf("Lobby/%08" PRIX32, l->lobby_id);
this->send(cmd46);
}
string Server::prepare_6xB6x41_map_definition(
shared_ptr<const DataIndex::MapEntry> map) {
string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::MapEntry> map) {
const auto& compressed = map->compressed();
StringWriter w;
@@ -260,7 +263,7 @@ void Server::send_commands_for_joining_spectator(Channel& c) const {
__attribute__((format(printf, 2, 3))) void Server::log_debug(const char* fmt, ...) const {
auto l = this->base()->lobby.lock();
if (l && (this->base()->data_index->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
if (l && (this->base()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
va_list va;
va_start(va, fmt);
this->base()->log.info_v(fmt, va);
@@ -270,7 +273,7 @@ __attribute__((format(printf, 2, 3))) void Server::log_debug(const char* fmt, ..
__attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(const char* fmt, ...) const {
auto l = this->base()->lobby.lock();
if (l && (this->base()->data_index->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
if (l && (this->base()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
va_list va;
va_start(va, fmt);
std::string buf = string_vprintf(fmt, va);
@@ -367,10 +370,9 @@ void Server::draw_phase_before() {
}
}
shared_ptr<const DataIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const {
shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const {
try {
return this->base()->data_index->definition_for_card_id(
this->card_id_for_card_ref(card_ref));
return this->base()->card_index->definition_for_id(this->card_id_for_card_ref(card_ref));
} catch (const out_of_range&) {
return nullptr;
}
@@ -581,9 +583,9 @@ void Server::copy_player_states_to_prev_states() {
}
}
shared_ptr<const DataIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const {
shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const {
try {
return this->base()->data_index->definition_for_card_id(card_id);
return this->base()->card_index->definition_for_id(card_id);
} catch (const out_of_range&) {
return nullptr;
}
@@ -1839,7 +1841,7 @@ void Server::handle_6xB3x13_update_map_during_setup(const string& data) {
*b->map_and_rules1 = in_cmd.map_and_rules_state;
*b->map_and_rules2 = in_cmd.map_and_rules_state;
b->overlay_state = in_cmd.overlay_state;
if (b->data_index->behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) {
if (b->behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) {
b->map_and_rules1->rules.overall_time_limit = 0;
b->map_and_rules1->rules.phase_time_limit = 0;
b->map_and_rules2->rules.overall_time_limit = 0;
@@ -1869,9 +1871,9 @@ void Server::handle_6xB3x14_update_deck_during_setup(const string& data) {
}
DeckEntry entry = in_cmd.entry;
int32_t verify_error = 0;
if (!(this->base()->data_index->behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) {
if (!(this->base()->behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) {
// Note: Sega's original implementation doesn't use the card counts here
if (this->base()->data_index->behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) {
if (this->base()->behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) {
verify_error = this->ruler_server->verify_deck(entry.card_ids);
} else {
verify_error = this->ruler_server->verify_deck(entry.card_ids,
@@ -1881,7 +1883,7 @@ void Server::handle_6xB3x14_update_deck_during_setup(const string& data) {
if (verify_error) {
throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error));
}
if (!(this->base()->data_index->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
if (!(this->base()->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
this->ruler_server->replace_D1_D2_rarity_cards_with_Attack(entry.card_ids);
}
*this->base()->deck_entries[in_cmd.client_id] = in_cmd.entry;
@@ -2135,7 +2137,7 @@ void Server::handle_6xB3x40_map_list_request(const string& data) {
throw runtime_error("lobby is deleted");
}
const auto& list_data = this->base()->data_index->get_compressed_map_list();
const auto& list_data = this->base()->map_index->get_compressed_list();
StringWriter w;
uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3);
@@ -2164,7 +2166,7 @@ void Server::handle_6xB3x41_map_request(const string& data) {
throw runtime_error("lobby is deleted");
}
base->last_chosen_map = base->data_index->definition_for_map_number(cmd.map_number);
base->last_chosen_map = base->map_index->definition_for_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) {
+13 -9
View File
@@ -19,7 +19,7 @@ namespace Episode3 {
/**
* This implementation of Episode 3 battles (contained in all files in the
* src/Episode3 directory, except for DataIndex.hh/cc) is derived from Sega's
* src/Episode3 directory, except for DataIndexes.hh/cc) is derived from Sega's
* original server implementation, reverse-engineered from the Episode 3 client
* executable. The control flow, function breakdown, and structure definitions
* in these files map very closely to how their server implementation was
@@ -49,7 +49,7 @@ namespace Episode3 {
// - - - - - - - DeckState
// - - - - - - - HandAndEquipState
// - - - - - - - MapAndRulesState / OverlayState
// - - - - - - - - Everything within DataIndex
// - - - - - - - - Everything within DataIndexes
class Server;
@@ -57,9 +57,11 @@ class ServerBase : public std::enable_shared_from_this<ServerBase> {
public:
ServerBase(
std::shared_ptr<Lobby> lobby,
std::shared_ptr<const DataIndex> data_index,
std::shared_ptr<const CardIndex> card_index,
std::shared_ptr<const MapIndex> map_index,
uint32_t behavior_flags,
std::shared_ptr<PSOLFGEncryption> random_crypt,
std::shared_ptr<const DataIndex::MapEntry> map_if_tournament);
std::shared_ptr<const MapIndex::MapEntry> map_if_tournament);
void init();
void reset();
void recreate_server();
@@ -73,11 +75,13 @@ public:
} __attribute__((packed));
std::weak_ptr<Lobby> lobby;
std::shared_ptr<const DataIndex> data_index;
std::shared_ptr<const CardIndex> card_index;
std::shared_ptr<const MapIndex> map_index;
uint32_t behavior_flags;
PrefixedLogger log;
std::shared_ptr<PSOLFGEncryption> random_crypt;
bool is_tournament;
std::shared_ptr<const DataIndex::MapEntry> last_chosen_map;
std::shared_ptr<const MapIndex::MapEntry> last_chosen_map;
std::shared_ptr<MapAndRulesState> map_and_rules1;
std::shared_ptr<MapAndRulesState> map_and_rules2;
@@ -135,7 +139,7 @@ public:
bool advance_battle_phase();
void action_phase_after();
void draw_phase_before();
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const;
std::shared_ptr<const CardIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const;
std::shared_ptr<Card> card_for_set_card_ref(uint16_t card_ref);
std::shared_ptr<const Card> card_for_set_card_ref(uint16_t card_ref) const;
uint16_t card_id_for_card_ref(uint16_t card_ref) const;
@@ -147,7 +151,7 @@ public:
void compute_all_map_occupied_bits();
void compute_team_dice_boost(uint8_t team_id);
void copy_player_states_to_prev_states();
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_id(uint16_t card_id) const;
std::shared_ptr<const CardIndex::CardEntry> definition_for_card_id(uint16_t card_id) const;
void destroy_cards_with_zero_hp();
void determine_first_team_turn();
void dice_phase_after();
@@ -229,7 +233,7 @@ public:
G_UpdateDecks_GC_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const;
G_SetPlayerNames_GC_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const;
static std::string prepare_6xB6x41_map_definition(
std::shared_ptr<const DataIndex::MapEntry> map);
std::shared_ptr<const MapIndex::MapEntry> map);
G_SetTrapTileLocations_GC_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const;
std::vector<std::shared_ptr<Card>> const_cast_set_cards_v(
+21 -20
View File
@@ -280,15 +280,17 @@ shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(
}
Tournament::Tournament(
shared_ptr<const DataIndex> data_index,
shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number,
const string& name,
shared_ptr<const DataIndex::MapEntry> map,
shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules,
size_t num_teams,
bool is_2v2)
: log(string_printf("[Tournament/%02hhX] ", number)),
data_index(data_index),
map_index(map_index),
com_deck_index(com_deck_index),
number(number),
name(name),
map(map),
@@ -308,11 +310,13 @@ Tournament::Tournament(
}
Tournament::Tournament(
std::shared_ptr<const DataIndex> data_index,
shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number,
std::shared_ptr<const JSONObject> json)
: log(string_printf("[Tournament/%02hhX] ", number)),
data_index(data_index),
map_index(map_index),
com_deck_index(com_deck_index),
source_json(json),
number(number),
current_state(State::REGISTRATION) {}
@@ -324,7 +328,7 @@ void Tournament::init() {
if (this->source_json) {
auto& dict = this->source_json->as_dict();
this->name = dict.at("name")->as_string();
this->map = this->data_index->definition_for_map_number(dict.at("map_number")->as_int());
this->map = this->map_index->definition_for_number(dict.at("map_number")->as_int());
this->rules = Rules(dict.at("rules"));
this->is_2v2 = dict.at("is_2v2")->as_bool();
is_registration_complete = dict.at("is_registration_complete")->as_bool();
@@ -344,8 +348,7 @@ void Tournament::init() {
team->players.emplace_back(serial_number);
this->all_player_serial_numbers.emplace(serial_number);
} else {
team->players.emplace_back(this->data_index->com_deck(
player_json->as_string()));
team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string()));
}
}
}
@@ -486,10 +489,6 @@ std::shared_ptr<JSONObject> Tournament::json() const {
return shared_ptr<JSONObject>(new JSONObject(std::move(dict)));
}
std::shared_ptr<const DataIndex> Tournament::get_data_index() const {
return this->data_index;
}
uint8_t Tournament::get_number() const {
return this->number;
}
@@ -498,7 +497,7 @@ const string& Tournament::get_name() const {
return this->name;
}
shared_ptr<const DataIndex::MapEntry> Tournament::get_map() const {
shared_ptr<const MapIndex::MapEntry> Tournament::get_map() const {
return this->map;
}
@@ -593,13 +592,13 @@ void Tournament::start() {
throw logic_error("non-human player on team before tournament start");
}
}
if (this->data_index->num_com_decks() < t->max_players - t->players.size()) {
if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
throw runtime_error("not enough COM decks to complete team");
}
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the same
// team
while (t->players.size() < t->max_players) {
t->players.emplace_back(this->data_index->random_com_deck());
t->players.emplace_back(this->com_deck_index->random_deck());
}
}
@@ -654,10 +653,12 @@ void Tournament::print_bracket(FILE* stream) const {
}
TournamentIndex::TournamentIndex(
shared_ptr<const DataIndex> data_index,
shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
const string& state_filename,
bool skip_load_state)
: data_index(data_index),
: map_index(map_index),
com_deck_index(com_deck_index),
state_filename(state_filename) {
if (this->state_filename.empty() || skip_load_state) {
return;
@@ -671,7 +672,7 @@ TournamentIndex::TournamentIndex(
}
for (size_t z = 0; z < 0x20; z++) {
if (!list.at(z)->is_null()) {
this->tournaments[z].reset(new Tournament(this->data_index, z, list[z]));
this->tournaments[z].reset(new Tournament(this->map_index, this->com_deck_index, z, list[z]));
this->tournaments[z]->init();
}
}
@@ -706,7 +707,7 @@ vector<shared_ptr<Tournament>> TournamentIndex::all_tournaments() const {
shared_ptr<Tournament> TournamentIndex::create_tournament(
const string& name,
shared_ptr<const DataIndex::MapEntry> map,
shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules,
size_t num_teams,
bool is_2v2) {
@@ -722,7 +723,7 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
}
auto t = make_shared<Tournament>(
this->data_index, number, name, map, rules, num_teams, is_2v2);
this->map_index, this->com_deck_index, number, name, map, rules, num_teams, is_2v2);
t->init();
this->tournaments[number] = t;
return t;
+14 -10
View File
@@ -92,15 +92,17 @@ public:
};
Tournament(
std::shared_ptr<const DataIndex> data_index,
std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number,
const std::string& name,
std::shared_ptr<const DataIndex::MapEntry> map,
std::shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules,
size_t num_teams,
bool is_2v2);
Tournament(
std::shared_ptr<const DataIndex> data_index,
std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number,
std::shared_ptr<const JSONObject> json);
~Tournament() = default;
@@ -108,10 +110,9 @@ public:
std::shared_ptr<JSONObject> json() const;
std::shared_ptr<const DataIndex> get_data_index() const;
uint8_t get_number() const;
const std::string& get_name() const;
std::shared_ptr<const DataIndex::MapEntry> get_map() const;
std::shared_ptr<const MapIndex::MapEntry> get_map() const;
const Rules& get_rules() const;
bool get_is_2v2() const;
State get_state() const;
@@ -131,11 +132,12 @@ public:
private:
PrefixedLogger log;
std::shared_ptr<const DataIndex> data_index;
std::shared_ptr<const MapIndex> map_index;
std::shared_ptr<const COMDeckIndex> com_deck_index;
std::shared_ptr<const JSONObject> source_json;
uint8_t number;
std::string name;
std::shared_ptr<const DataIndex::MapEntry> map;
std::shared_ptr<const MapIndex::MapEntry> map;
Rules rules;
size_t num_teams;
bool is_2v2;
@@ -160,7 +162,8 @@ private:
class TournamentIndex {
public:
explicit TournamentIndex(
std::shared_ptr<const DataIndex> data_index,
std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
const std::string& state_filename,
bool skip_load_state = false);
~TournamentIndex() = default;
@@ -171,7 +174,7 @@ public:
std::shared_ptr<Tournament> create_tournament(
const std::string& name,
std::shared_ptr<const DataIndex::MapEntry> map,
std::shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules,
size_t num_teams,
bool is_2v2);
@@ -183,7 +186,8 @@ public:
uint32_t serial_number) const;
private:
std::shared_ptr<const DataIndex> data_index;
std::shared_ptr<const MapIndex> map_index;
std::shared_ptr<const COMDeckIndex> com_deck_index;
std::string state_filename;
std::shared_ptr<Tournament> tournaments[0x20];
};
+13 -1
View File
@@ -195,7 +195,7 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
isdigit(filename[filename.size() - 12]) &&
(filename[filename.size() - 11] == 'O' || filename[filename.size() - 11] == 'S') &&
(filename[filename.size() - 10] == 'E' || filename[filename.size() - 10] == 'J' || filename[filename.size() - 10] == 'P') &&
isdigit(filename[filename.size() - 9])) {
(isdigit(filename[filename.size() - 9]) || filename[filename.size() - 9] == 'T')) {
specific_version = 0x33000000 | (filename[filename.size() - 11] << 16) | (filename[filename.size() - 10] << 8) | filename[filename.size() - 9];
patch_name = filename.substr(0, filename.size() - 13);
}
@@ -375,6 +375,18 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
}
}
}
{
// Generate entries for Trial Editions
data.region_code = 'J';
data.system_code = 'D';
data.version_code = 0;
uint32_t checksum = crc32(&data, sizeof(data));
uint32_t specific_version = 0x33004A54 | (*game_code2 << 16);
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
throw logic_error("multiple specific_versions have same header checksum");
}
data.system_code = 'G';
}
}
}
return checksum_to_specific_version.at(header_checksum);
+1
View File
@@ -37,6 +37,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
SPECTATORS_FORBIDDEN = 0x00004000,
START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000,
DROPS_ENABLED = 0x00010000, // Does not affect BB
IS_EP3_TRIAL = 0x00020000,
// Flags used only for lobbies
PUBLIC = 0x01000000,
+23 -22
View File
@@ -1478,32 +1478,33 @@ int main(int argc, char** argv) {
break;
}
case Behavior::SHOW_EP3_MAPS:
case Behavior::SHOW_EP3_CARDS: {
config_log.info("Collecting Episode 3 data");
Episode3::DataIndex index("system/ep3", Episode3::BehaviorFlag::LOAD_CARD_TEXT);
Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr");
if (behavior == Behavior::SHOW_EP3_MAPS) {
auto map_ids = index.all_map_ids();
log_info("%zu maps", map_ids.size());
for (uint32_t map_id : map_ids) {
auto map = index.definition_for_map_number(map_id);
string s = map->map.str(&index);
fprintf(stdout, "%s\n", s.c_str());
}
} else {
auto card_ids = index.all_card_ids();
log_info("%zu card definitions", card_ids.size());
for (uint32_t card_id : card_ids) {
auto entry = index.definition_for_card_id(card_id);
string s = entry->def.str(false);
string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", ");
string text = entry->text.empty() ? "(No text available)" : str_replace_all(entry->text, "\n", "\n ");
fprintf(stdout, "%s\n Tags: %s\n Text:\n %s\n\n", s.c_str(), tags.c_str(), text.c_str());
}
auto card_ids = card_index.all_ids();
log_info("%zu card definitions", card_ids.size());
for (uint32_t card_id : card_ids) {
auto entry = card_index.definition_for_id(card_id);
string s = entry->def.str(false);
string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", ");
string text = entry->text.empty() ? "(No text available)" : str_replace_all(entry->text, "\n", "\n ");
fprintf(stdout, "%s\n Tags: %s\n Text:\n %s\n\n", s.c_str(), tags.c_str(), text.c_str());
}
break;
}
case Behavior::SHOW_EP3_MAPS: {
config_log.info("Collecting Episode 3 data");
Episode3::MapIndex map_index("system/ep3");
Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr");
auto map_ids = map_index.all_numbers();
log_info("%zu maps", map_ids.size());
for (uint32_t map_id : map_ids) {
auto map = map_index.definition_for_number(map_id);
string s = map->map.str(&card_index);
fprintf(stdout, "%s\n", s.c_str());
}
break;
}
+1 -1
View File
@@ -9,7 +9,7 @@
#include <utility>
#include <vector>
#include "Episode3/DataIndex.hh"
#include "Episode3/DataIndexes.hh"
#include "ItemData.hh"
#include "LevelTable.hh"
#include "Text.hh"
+24 -10
View File
@@ -303,7 +303,7 @@ static void set_console_client_flags(
c->log.info("Game version changed to DC");
} else if (c->version() == GameVersion::GC) {
c->flags |= Client::Flag::IS_GC_TRIAL_EDITION;
c->log.info("Trial edition flag set");
c->log.info("GC Trial Edition flag set");
}
}
c->flags |= flags_for_version(c->version(), sub_version);
@@ -930,7 +930,7 @@ static bool add_next_game_client(
state_cmd.state.first_team_turn = 0xFF;
state_cmd.state.tournament_flag = 0x01;
state_cmd.state.client_sc_card_types.clear(Episode3::CardType::INVALID_FF);
if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&state_cmd, sizeof(state_cmd), mask_key);
}
@@ -958,7 +958,7 @@ static bool add_next_game_client(
ex_cmd.lose_entries[z].threshold = lose_entries[z].first;
ex_cmd.lose_entries[z].value = lose_entries[z].second;
}
if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&ex_cmd, sizeof(ex_cmd), mask_key);
}
@@ -1239,7 +1239,7 @@ static void on_DC_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
if (l->tournament_match) {
auto tourn = l->tournament_match->tournament.lock();
if (tourn) {
send_ep3_set_tournament_player_decks(l, c, l->tournament_match);
send_ep3_set_tournament_player_decks(s, l, c, l->tournament_match);
string data = Episode3::Server::prepare_6xB6x41_map_definition(
tourn->get_map());
c->channel.send(0x6C, 0x00, data);
@@ -1312,8 +1312,14 @@ static void on_CA_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
l->log.info("Recreating Episode 3 server state");
}
auto tourn = l->tournament_match ? l->tournament_match->tournament.lock() : nullptr;
bool is_trial = (l->flags & Lobby::Flag::IS_EP3_TRIAL);
l->ep3_server_base = make_shared<Episode3::ServerBase>(
l, s->ep3_data_index, l->random_crypt, tourn ? tourn->get_map() : nullptr);
l,
is_trial ? s->ep3_card_index_trial : s->ep3_card_index,
s->ep3_map_index,
s->ep3_behavior_flags,
l->random_crypt,
tourn ? tourn->get_map() : nullptr);
l->ep3_server_base->init();
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
@@ -1366,7 +1372,7 @@ static void on_CA_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
} else {
throw logic_error("invalid winner team id");
}
send_ep3_tournament_match_result(l, l->tournament_match);
send_ep3_tournament_match_result(s, l, l->tournament_match);
on_tournament_bracket_updated(s, tourn);
l->ep3_server_base->server->tournament_match_result_sent = true;
@@ -1866,6 +1872,7 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
if ((game->version != c->version()) ||
(!game->is_ep3() != !(c->flags & Client::Flag::IS_EPISODE_3)) ||
(!(game->flags & Lobby::Flag::IS_EP3_TRIAL) != !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) ||
((game->flags & Lobby::Flag::NON_V1_ONLY) && (c->flags & Client::Flag::IS_DC_V1))) {
send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO.");
break;
@@ -2141,6 +2148,11 @@ static void on_84(shared_ptr<ServerState> s, shared_ptr<Client> c,
return;
}
if (new_lobby->is_game()) {
send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The specified lobby\nis a game.");
return;
}
if (new_lobby->is_ep3() && !(c->flags & Client::Flag::IS_EPISODE_3)) {
send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby is for\nEpisode 3 only.");
return;
@@ -3203,10 +3215,11 @@ shared_ptr<Lobby> create_game_generic(
(c->version() == GameVersion::BB) ||
(s->item_tracking_enabled && (mode == GameMode::NORMAL || mode == GameMode::SOLO));
// only disable drops if the config flag is set and are playing regualr multi-mode.
// drops are still enabled for battle and challenge modes.
bool drops_enabled =
(s->drops_enabled || (mode != GameMode::NORMAL));
// Only disable drops if the config flag is set and are playing regular
// multi-mode. Drops are still enabled for battle and challenge modes.
bool drops_enabled = (s->drops_enabled || (mode != GameMode::NORMAL));
bool is_ep3_trial = (c->version() == GameVersion::GC) && (c->flags & Client::Flag::IS_EPISODE_3) && (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION);
shared_ptr<Lobby> game = s->create_lobby();
game->name = name;
@@ -3214,6 +3227,7 @@ shared_ptr<Lobby> create_game_generic(
Lobby::Flag::GAME |
(item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) |
(drops_enabled ? Lobby::Flag::DROPS_ENABLED : 0) |
(is_ep3_trial ? Lobby::Flag::IS_EP3_TRIAL : 0) |
((s->cheat_mode_behavior == ServerState::CheatModeBehavior::ON_BY_DEFAULT) ? Lobby::Flag::CHEATS_ENABLED : 0);
game->password = password;
game->version = c->version();
+1 -1
View File
@@ -380,7 +380,7 @@ static void on_ep3_battle_subs(shared_ptr<ServerState> s,
}
}
if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = 0;
while (!mask_key) {
mask_key = random_object<uint8_t>();
+13 -6
View File
@@ -2180,7 +2180,9 @@ void send_rare_enemy_index_list(shared_ptr<Client> c, const vector<size_t>& inde
void send_ep3_card_list_update(shared_ptr<ServerState> s, shared_ptr<Client> c) {
if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) {
const auto& data = s->ep3_data_index->get_compressed_card_definitions();
const auto& data = (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)
? s->ep3_card_index_trial->get_compressed_definitions()
: s->ep3_card_index->get_compressed_definitions();
StringWriter w;
w.put_u32l(data.size());
@@ -2486,6 +2488,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
}
void send_ep3_set_tournament_player_decks(
shared_ptr<ServerState> s,
shared_ptr<Lobby> l,
shared_ptr<Client> c,
shared_ptr<const Episode3::Tournament::Match> match) {
@@ -2532,7 +2535,7 @@ void send_ep3_set_tournament_player_decks(
add_entries_for_team(match->preceding_a->winner_team, 0);
add_entries_for_team(match->preceding_b->winner_team, 2);
if (!(tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
}
@@ -2543,7 +2546,7 @@ void send_ep3_set_tournament_player_decks(
}
void send_ep3_tournament_match_result(
shared_ptr<Lobby> l, shared_ptr<const Episode3::Tournament::Match> match) {
shared_ptr<ServerState> s, shared_ptr<Lobby> l, shared_ptr<const Episode3::Tournament::Match> match) {
auto tourn = match->tournament.lock();
if (!tourn) {
return;
@@ -2585,13 +2588,13 @@ void send_ep3_tournament_match_result(
// the player 1000000 and never charge for anything.
cmd.meseta_amount = 100;
cmd.meseta_reward_text = "You got %s meseta!";
if (!(tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
}
send_command_t(l, 0xC9, 0x00, cmd);
if (tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
send_text_message_printf(l, "$C5TOURN/%02hhX/%zu WIN %c",
tourn->get_number(), match->round_num,
match->winner_team == match->preceding_a->winner_team ? 'A' : 'B');
@@ -2806,6 +2809,10 @@ void send_card_auction_if_all_clients_ready(
distribution_size += it.second.first;
}
auto card_index = (l->flags & Lobby::Flag::IS_EP3_TRIAL)
? s->ep3_card_index_trial
: s->ep3_card_index;
S_StartCardAuction_GC_Ep3_EF cmd;
cmd.points_available = s->ep3_card_auction_points;
for (size_t z = 0; z < num_cards; z++) {
@@ -2814,7 +2821,7 @@ void send_card_auction_if_all_clients_ready(
if (v >= it.second.first) {
v -= it.second.first;
} else {
cmd.entries[z].card_id = s->ep3_data_index->definition_for_card_name(it.first)->def.card_id.load();
cmd.entries[z].card_id = card_index->definition_for_name(it.first)->def.card_id.load();
cmd.entries[z].min_price = it.second.second;
break;
}
+2
View File
@@ -352,10 +352,12 @@ void send_ep3_tournament_info(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_set_tournament_player_decks(
std::shared_ptr<ServerState> s,
std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament::Match> match);
void send_ep3_tournament_match_result(
std::shared_ptr<ServerState> s,
std::shared_ptr<Lobby> l,
std::shared_ptr<const Episode3::Tournament::Match> match);
+1 -1
View File
@@ -451,7 +451,7 @@ Proxy session commands:\n\
} else if (command_name == "create-tournament") {
string name = get_quoted_string(command_args);
string map_name = get_quoted_string(command_args);
auto map = this->state->ep3_data_index->definition_for_map_name(map_name);
auto map = this->state->ep3_map_index->definition_for_name(map_name);
uint32_t num_teams = stoul(get_quoted_string(command_args), nullptr, 0);
Episode3::Rules rules;
rules.set_defaults();
+10 -5
View File
@@ -888,19 +888,24 @@ void ServerState::load_item_tables() {
}
void ServerState::load_ep3_data() {
config_log.info("Collecting Episode 3 data");
this->ep3_data_index.reset(new Episode3::DataIndex(
"system/ep3", this->ep3_behavior_flags));
config_log.info("Collecting Episode 3 maps");
this->ep3_map_index.reset(new Episode3::MapIndex("system/ep3"));
config_log.info("Loading Episode 3 card definitions");
this->ep3_card_index.reset(new Episode3::CardIndex("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr"));
config_log.info("Loading Episode 3 trial card definitions");
this->ep3_card_index_trial.reset(new Episode3::CardIndex("system/ep3/card-definitions-trial.mnr", "system/ep3/card-definitions-trial.mnrd", "system/ep3/card-text-trial.mnr"));
config_log.info("Loading Episode 3 COM decks");
this->ep3_com_deck_index.reset(new Episode3::COMDeckIndex("system/ep3/com-decks.json"));
const string& tournament_state_filename = "system/ep3/tournament-state.json";
try {
this->ep3_tournament_index.reset(new Episode3::TournamentIndex(
this->ep3_data_index, tournament_state_filename));
this->ep3_map_index, this->ep3_com_deck_index, tournament_state_filename));
config_log.info("Loaded Episode 3 tournament state");
} catch (const exception& e) {
config_log.warning("Cannot load Episode 3 tournament state: %s", e.what());
this->ep3_tournament_index.reset(new Episode3::TournamentIndex(
this->ep3_data_index, tournament_state_filename, true));
this->ep3_map_index, this->ep3_com_deck_index, tournament_state_filename, true));
}
}
+5 -2
View File
@@ -11,7 +11,7 @@
#include "Client.hh"
#include "CommonItemSet.hh"
#include "Episode3/DataIndex.hh"
#include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh"
#include "FunctionCompiler.hh"
#include "GSLArchive.hh"
@@ -70,7 +70,10 @@ struct ServerState {
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
std::shared_ptr<const DOLFileIndex> dol_file_index;
std::shared_ptr<const Episode3::DataIndex> ep3_data_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> quest_index;
std::shared_ptr<const LevelTable> level_table;
-1
View File
@@ -87,7 +87,6 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) {
return Client::Flag::NO_D6_AFTER_LOBBY |
Client::Flag::IS_EPISODE_3 |
Client::Flag::IS_EP3_TRIAL_EDITION |
Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL |
Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
case 0x42: // GC Ep3 JP
return Client::Flag::NO_D6_AFTER_LOBBY |
-1
View File
@@ -279,7 +279,6 @@
// 0x0008 => Disable overall and per-phase battle time limits, regardless of
// the values chosen during battle rules setup
// 0x0010 => Enable debug messages in Episode 3 games and battles
// 0x0020 => This flag is used internally and has no effect on battles
// 0x0040 => Enable battle recording (after a battle, players can save the
// recording with the $saverec <filename> command)
// 0x0080 => Disable command masking during battles
Binary file not shown.
BIN
View File
Binary file not shown.
+21 -13
View File
@@ -3,9 +3,10 @@
# The returned value has the format SSGGRRVV, where:
# S = 33 (which represents PSO GC)
# G = game (4F (O) = Episodes 1&2, 53 (S) = Episode 3)
# G = game (4F (O) = Ep1&2, 53 (S) = Ep3)
# R = region (45 (E), 4A (J), or 50 (P))
# V = minor version | 30 (30 = 1.00, 31 = 1.01, 32 = 1.02, etc.)
# V = minor version | 30 (30 = 1.00, 31 = 1.01, 32 = 1.02, etc.), or 54 for
# Trial Edition
# This results in a 4-character ASCII-printable version code which encodes all
# of the above information. This value is called specific_version in the places
# where it's used by the server.
@@ -19,16 +20,23 @@ reloc0:
start:
lis r3, 0x8000
lwz r4, [r3]
lbz r5, [r3 + 7]
li r3, -1
rlwinm r0, r4, 16, 16, 31
cmplwi r0, 0x4750
bnelr
lis r3, 0x3300
rlwimi r3, r4, 8, 8, 23
rlwimi r3, r5, 0, 24, 31
ori r3, r3, 0x0030
# For Trial Editions, set the V field to 54; for other versions, set it to
# 0x30 | disc_version
rlwinm r0, r4, 8, 24, 31
cmplwi r0, 0x47 # Check if high byte of game ID is 'G'
beq not_trial
cmplwi r0, 0x44 # Check if high byte of game ID is 'D'
beq is_trial
li r3, 0
blr
is_trial:
li r3, 0x0054
b end_trial_check
not_trial:
lbz r3, [r3 + 7]
ori r3, r3, 0x0030
end_trial_check:
oris r3, r3, 0x3300 # Set high byte ('3')
rlwimi r3, r4, 8, 8, 23 # Set middle two bytes to last to bytes of game ID
blr