implement full character backups on GC

This commit is contained in:
Martin Michelsen
2024-04-06 19:52:22 -07:00
parent 91131f8b36
commit af4d3a3325
64 changed files with 3155 additions and 2208 deletions
+3 -3
View File
@@ -11,6 +11,6 @@ struct AITalkBin {
be_uint32_t percent_chance; // 0-100
be_uint32_t count;
be_uint32_t string_ids[count];
} __attribute__((packed));
} __attribute__((packed));
} __attribute__((packed));
} __packed__;
} __packed__;
} __packed__;
+4 -2
View File
@@ -7,6 +7,8 @@
#include <phosg/Filesystem.hh>
#include <phosg/Strings.hh>
#include "Text.hh"
using namespace std;
AFSArchive::AFSArchive(shared_ptr<const string> data)
@@ -14,12 +16,12 @@ AFSArchive::AFSArchive(shared_ptr<const string> data)
struct FileHeader {
be_uint32_t magic;
le_uint32_t num_files;
} __attribute__((packed));
} __packed_ws__(FileHeader, 8);
struct FileEntry {
le_uint32_t offset;
le_uint32_t size;
} __attribute__((packed));
} __packed_ws__(FileEntry, 8);
StringReader r(*this->data);
const auto& header = r.get<FileHeader>();
+16 -6
View File
@@ -9,16 +9,21 @@
using namespace std;
template <bool IsBigEndian>
struct BMLHeader {
struct BMLHeaderT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
parray<uint8_t, 0x04> unknown_a1;
U32T num_entries;
parray<uint8_t, 0x38> unknown_a2;
} __attribute__((packed));
} __packed__;
using BMLHeader = BMLHeaderT<false>;
using BMLHeaderBE = BMLHeaderT<true>;
check_struct_size(BMLHeader, 0x40);
check_struct_size(BMLHeaderBE, 0x40);
template <bool IsBigEndian>
struct BMLHeaderEntry {
struct BMLHeaderEntryT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
pstring<TextEncoding::ASCII, 0x20> filename;
@@ -28,17 +33,22 @@ struct BMLHeaderEntry {
U32T compressed_gvm_size;
U32T decompressed_gvm_size;
parray<uint8_t, 0x0C> unknown_a2;
} __attribute__((packed));
} __packed__;
using BMLHeaderEntry = BMLHeaderEntryT<false>;
using BMLHeaderEntryBE = BMLHeaderEntryT<true>;
check_struct_size(BMLHeaderEntry, 0x40);
check_struct_size(BMLHeaderEntryBE, 0x40);
template <bool IsBigEndian>
void BMLArchive::load_t() {
StringReader r(*this->data);
const auto& header = r.get<BMLHeader<IsBigEndian>>();
const auto& header = r.get<BMLHeaderT<IsBigEndian>>();
size_t offset = 0x800;
while (this->entries.size() < header.num_entries) {
const auto& entry = r.get<BMLHeaderEntry<IsBigEndian>>();
const auto& entry = r.get<BMLHeaderEntryT<IsBigEndian>>();
if (offset + entry.compressed_size > this->data->size()) {
throw runtime_error("BML data entry extends beyond end of data");
+4 -4
View File
@@ -36,7 +36,7 @@ public:
/* 28 */ le_uint32_t unknown_a15;
/* 2C */ le_uint32_t unknown_a16;
/* 30 */
} __attribute__((packed));
} __packed_ws__(AttackData, 0x30);
struct ResistData {
/* 00 */ le_int16_t evp_bonus;
@@ -51,7 +51,7 @@ public:
/* 18 */ le_uint32_t unknown_a9;
/* 1C */ le_int32_t dfp_bonus;
/* 20 */
} __attribute__((packed));
} __packed_ws__(ResistData, 0x20);
struct MovementData {
/* 00 */ le_float idle_move_speed;
@@ -67,7 +67,7 @@ public:
/* 28 */ le_uint32_t unknown_a7;
/* 2C */ le_uint32_t unknown_a8;
/* 30 */
} __attribute__((packed));
} __packed_ws__(MovementData, 0x30);
struct Table {
/* 0000 */ parray<parray<PlayerStats, 0x60>, 4> stats;
@@ -77,7 +77,7 @@ public:
/* F600 */
void print(FILE* stream) const;
} __attribute__((packed));
} __packed_ws__(Table, 0xF600);
BattleParamsIndex(
std::shared_ptr<const std::string> data_on_ep1, // BattleParamEntry_on.dat
+6 -5
View File
@@ -1284,8 +1284,8 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
p->disp.stats.experience = stoul(tokens.at(1));
} else if (tokens.at(0) == "level" && cheats_allowed) {
uint32_t level = stoul(tokens.at(1)) - 1;
p->disp.stats.reset_to_base(p->disp.visual.char_class, s->level_table);
p->disp.stats.advance_to_level(p->disp.visual.char_class, level, s->level_table);
s->level_table->reset_to_base(p->disp.stats, p->disp.visual.char_class);
s->level_table->advance_to_level(p->disp.stats, level, p->disp.visual.char_class);
} else if (tokens.at(0) == "namecolor") {
uint32_t new_color;
sscanf(tokens.at(1).c_str(), "%8X", &new_color);
@@ -1434,9 +1434,10 @@ static void server_command_bbchar_savechar(shared_ptr<Client> c, const std::stri
}
c->pending_character_export = std::move(pending_export);
// Request the player data. The client will respond with a 61, and the handler
// for that command will execute the conversion
send_get_player_info(c);
// Request the player data. The client will respond with a 61 or 30, and the
// handler for either of those commands will execute the conversion
send_get_player_info(c, true);
}
static void server_command_bbchar(shared_ptr<Client> c, const std::string& args) {
+27 -6
View File
@@ -10,12 +10,16 @@
class Client;
struct ChoiceSearchConfig {
le_uint32_t disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
template <bool IsBigEndian>
struct ChoiceSearchConfigT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
struct Entry {
le_uint16_t parent_choice_id = 0;
le_uint16_t choice_id = 0;
} __attribute__((packed));
U16T parent_choice_id = 0;
U16T choice_id = 0;
} __packed_ws__(Entry, 4);
parray<Entry, 5> entries;
int32_t get_setting(uint16_t parent_choice_id) const {
@@ -26,7 +30,24 @@ struct ChoiceSearchConfig {
}
return -1;
}
} __attribute__((packed));
operator ChoiceSearchConfigT<!IsBigEndian>() const {
ChoiceSearchConfigT<!IsBigEndian> ret;
ret.disabled = this->disabled.load();
for (size_t z = 0; z < this->entries.size(); z++) {
auto& ret_e = ret.entries[z];
const auto& this_e = this->entries[z];
ret_e.parent_choice_id = this_e.parent_choice_id.load();
ret_e.choice_id = this_e.choice_id.load();
}
return ret;
}
} __packed__;
using ChoiceSearchConfig = ChoiceSearchConfigT<false>;
using ChoiceSearchConfigBE = ChoiceSearchConfigT<true>;
check_struct_size(ChoiceSearchConfig, 0x18);
check_struct_size(ChoiceSearchConfigBE, 0x18);
struct ChoiceSearchCategory {
struct Choice {
+6 -6
View File
@@ -486,8 +486,8 @@ void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_p
uint8_t char_class = this->overlay_character_data->disp.visual.char_class;
auto& stats = this->overlay_character_data->disp.stats;
stats.reset_to_base(char_class, level_table);
stats.advance_to_level(char_class, target_level, level_table);
level_table->reset_to_base(stats, char_class);
level_table->advance_to_level(stats, target_level, char_class);
stats.esp = 40;
stats.meseta = 300;
@@ -532,8 +532,8 @@ void Client::create_challenge_overlay(Version version, size_t template_index, sh
overlay->inventory.items[13].extension_data2 = 1;
overlay->disp.stats.reset_to_base(overlay->disp.visual.char_class, level_table);
overlay->disp.stats.advance_to_level(overlay->disp.visual.char_class, tpl.level, level_table);
level_table->reset_to_base(overlay->disp.stats, overlay->disp.visual.char_class);
level_table->advance_to_level(overlay->disp.stats, tpl.level, overlay->disp.visual.char_class);
overlay->disp.stats.esp = 40;
overlay->disp.stats.unknown_a3 = 10.0;
@@ -753,6 +753,7 @@ void Client::load_all_files() {
if (header.flag != 0x00000000) {
throw runtime_error("incorrect flag in character file header");
}
static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBFullSystemFile) == 0x3994, ".psochar size is incorrect");
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
files_manager->set_character(char_filename, this->character_data);
player_data_log.info("Loaded character data from %s", char_filename.c_str());
@@ -831,7 +832,6 @@ void Client::load_all_files() {
this->character_data->inventory = nsc_data.inventory;
this->character_data->disp = nsc_data.disp;
this->character_data->play_time_seconds = 0;
this->character_data->unknown_a2 = nsc_data.unknown_a2;
this->character_data->quest_flags = nsc_data.quest_flags;
this->character_data->death_count = nsc_data.death_count;
this->character_data->bank = nsc_data.bank;
@@ -846,7 +846,7 @@ void Client::load_all_files() {
this->character_data->info_board = nsc_data.info_board;
this->character_data->battle_records = nsc_data.battle_records;
this->character_data->challenge_records = nsc_data.challenge_records;
this->character_data->tech_menu_config = nsc_data.tech_menu_config;
this->character_data->tech_menu_shortcut_entries = nsc_data.tech_menu_shortcut_entries;
this->character_data->quest_counters = nsc_data.quest_counters;
if (nsa_data) {
this->character_data->option_flags = nsa_data->option_flags;
+809 -798
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -124,7 +124,7 @@ void CommonItemSet::Table::parse_itempt_t(const StringReader& r, bool is_v3) {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
const auto& offsets = r.pget<Offsets<IsBigEndian>>(r.pget<U32T>(r.size() - 0x10));
const auto& offsets = r.pget<OffsetsT<IsBigEndian>>(r.pget<U32T>(r.size() - 0x10));
this->base_weapon_type_prob_table = r.pget<parray<uint8_t, 0x0C>>(offsets.base_weapon_type_prob_table_offset);
this->subtype_base_table = r.pget<parray<int8_t, 0x0C>>(offsets.subtype_base_table_offset);
+17 -11
View File
@@ -20,7 +20,7 @@ public:
struct Range {
IntT min;
IntT max;
} __attribute__((packed));
} __packed__;
parray<uint8_t, 0x0C> base_weapon_type_prob_table;
parray<int8_t, 0x0C> subtype_base_table;
@@ -53,7 +53,7 @@ public:
void parse_itempt_t(const StringReader& r, bool is_v3);
template <bool IsBigEndian>
struct Offsets {
struct OffsetsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
@@ -254,7 +254,11 @@ public:
/* 50 */ U32T box_item_class_prob_table_offset;
// There are several unused fields here.
} __attribute__((packed));
} __packed__;
using Offsets = OffsetsT<false>;
using OffsetsBE = OffsetsT<true>;
check_struct_size(Offsets, 0x54);
check_struct_size(OffsetsBE, 0x54);
};
std::shared_ptr<const Table> get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
@@ -327,10 +331,12 @@ public:
struct WeightTableEntry {
ValueT value;
WeightT weight;
} __attribute__((packed));
} __packed__;
using WeightTableEntry8 = WeightTableEntry<uint8_t>;
using WeightTableEntry32 = WeightTableEntry<be_uint32_t>;
check_struct_size(WeightTableEntry8, 2);
check_struct_size(WeightTableEntry32, 8);
protected:
std::shared_ptr<const std::string> data;
@@ -340,7 +346,7 @@ protected:
be_uint32_t offset;
uint8_t entries_per_table;
parray<uint8_t, 3> unused;
} __attribute__((packed));
} __packed_ws__(TableSpec, 8);
RELFileSet(std::shared_ptr<const std::string> data);
@@ -381,7 +387,7 @@ public:
Mode mode;
uint8_t player_level_divisor_or_min_level;
uint8_t max_level;
} __attribute__((packed));
} __packed_ws__(TechDiskLevelEntry, 3);
std::pair<const uint8_t*, size_t> get_common_recovery_table(size_t index) const;
std::pair<const WeightTableEntry8*, size_t> get_rare_recovery_table(size_t index) const;
@@ -403,7 +409,7 @@ public:
struct RangeTableEntry {
be_uint32_t min;
be_uint32_t max;
} __attribute__((packed));
} __packed_ws__(RangeTableEntry, 8);
std::pair<const WeightTableEntry8*, size_t> get_weapon_type_table(size_t index) const;
const parray<WeightTableEntry32, 6>* get_bonus_type_table(size_t which, size_t index) const;
@@ -422,7 +428,7 @@ private:
be_uint32_t special_mode_table; // [[{u32 value, u32 weight}](3)](8)
be_uint32_t standard_grind_range_table; // [{u32 min, u32 max}](6)
be_uint32_t favored_grind_range_table; // [{u32 min, u32 max}](6)
} __attribute__((packed));
} __packed_ws__(Offsets, 0x20);
const Offsets* offsets;
};
@@ -455,11 +461,11 @@ private:
uint8_t delta_index;
uint8_t count_default;
uint8_t count_favored;
} __attribute__((packed));
} __packed_ws__(DeltaProbabilityEntry, 3);
struct LuckTableEntry {
uint8_t delta_index;
int8_t luck;
} __attribute__((packed));
} __packed_ws__(LuckTableEntry, 2);
struct Offsets {
// Each section ID's favored weapon class has different probabilities than
@@ -565,7 +571,7 @@ private:
// In PSO V3, the bonus delta luck table is:
// +10 => +15, +5 => +8, 0 => 0, -5 => -8, -10 => -15
be_uint32_t bonus_delta_luck_offset; // LuckTableEntry[...]; ending with FF FF
} __attribute__((packed));
} __packed_ws__(Offsets, 0x18);
const Offsets* offsets;
+1 -1
View File
@@ -26,7 +26,7 @@ public:
le_uint32_t level;
void print(FILE* stream) const;
} __attribute__((packed));
} __packed_ws__(PlayerEntry, 0x440);
struct Event {
enum class Type : uint8_t {
+2 -1
View File
@@ -6,6 +6,7 @@
#include "../Text.hh"
#include "DataIndexes.hh"
#include "Server.hh"
namespace Episode3 {
@@ -94,7 +95,7 @@ public:
void print(FILE* stream) const;
uint32_t at(size_t index) const;
} __attribute__((packed));
} __packed_ws__(AttackEnvStats, 0x9C);
CardSpecial(std::shared_ptr<Server> server);
std::shared_ptr<Server> server();
+22 -22
View File
@@ -447,7 +447,7 @@ struct Location {
void clear();
void clear_FF();
} __attribute__((packed));
} __packed_ws__(Location, 4);
struct CardDefinition {
struct Stat {
@@ -470,7 +470,7 @@ struct CardDefinition {
void decode_code();
std::string str() const;
JSON json() const;
} __attribute__((packed));
} __packed_ws__(Stat, 4);
struct Effect {
// effect_num is the 1-based index of this effect within the card definition
@@ -507,7 +507,7 @@ struct CardDefinition {
static std::string str_for_arg(const std::string& arg);
std::string str(const char* separator = ", ", const TextSet* text_archive = nullptr) const;
JSON json() const;
} __attribute__((packed));
} __packed_ws__(Effect, 0x20);
/* 0000 */ be_uint32_t card_id;
/* 0004 */ pstring<TextEncoding::SJIS, 0x40> jp_name;
@@ -774,7 +774,7 @@ struct CardDefinition {
void decode_range();
std::string str(bool single_line = true, const TextSet* text_archive = nullptr) const;
JSON json() const;
} __attribute__((packed)); // 0x128 bytes in total
} __packed_ws__(CardDefinition, 0x128);
struct CardDefinitionsFooter {
// Technically the card definitions file is a REL file, so the last 0x20 bytes
@@ -790,7 +790,7 @@ struct CardDefinitionsFooter {
/* 48 */ be_uint32_t footer_offset;
/* 4C */ parray<be_uint32_t, 3> unused2;
/* 58 */
} __attribute__((packed));
} __packed_ws__(CardDefinitionsFooter, 0x58);
struct DeckDefinition {
/* 00 */ pstring<TextEncoding::MARKED, 0x10> name;
@@ -810,7 +810,7 @@ struct DeckDefinition {
/* 82 */ uint8_t second;
/* 83 */ uint8_t unknown_a2;
/* 84 */
} __attribute__((packed));
} __packed_ws__(DeckDefinition, 0x84);
struct PlayerConfig {
// The game splits this internally into two structures. The first column of
@@ -828,7 +828,7 @@ struct PlayerConfig {
// earlier version, this was the offline records structure, but they later
// decided to just count online and offline records together in the main
// records structure and didn't remove the codepath that reads from this.
/* 0138:---- */ PlayerRecords_Battle<true> unused_offline_records;
/* 0138:---- */ PlayerRecordsBattleBE unused_offline_records;
/* 0150:---- */ parray<uint8_t, 4> unknown_a4;
// The PlayerDataSegment structure begins here. In newserv, we combine this
// structure into PlayerConfig since the two are always used together.
@@ -879,7 +879,7 @@ struct PlayerConfig {
struct PlayerReference {
/* 00 */ be_uint32_t guild_card_number;
/* 04 */ pstring<TextEncoding::MARKED, 0x18> name;
} __attribute__((packed));
} __packed_ws__(PlayerReference, 0x1C);
// This array is updated when a battle is started (via a 6xB4x05 command). The
// client adds the opposing players' info to ths first two entries here if the
// opponents are human. (The existing entries are always moved back by two
@@ -902,7 +902,7 @@ struct PlayerConfig {
void decrypt();
void encrypt(uint8_t basis);
} __attribute__((packed));
} __packed_ws__(PlayerConfig, 0x2350);
enum class HPType : uint8_t {
DEFEAT_PLAYER = 0,
@@ -972,7 +972,7 @@ struct Rules {
std::pair<uint8_t, uint8_t> def_dice_range(bool is_1p_2v1) const;
std::string str() const;
} __attribute__((packed));
} __packed_ws__(Rules, 0x14);
struct RulesTrial {
// Most fields here have the same meanings as in the final version.
@@ -996,7 +996,7 @@ struct RulesTrial {
RulesTrial() = default;
RulesTrial(const Rules&);
operator Rules() const;
} __attribute__((packed));
} __packed_ws__(RulesTrial, 0x0C);
struct StateFlags {
/* 00 */ le_uint16_t turn_num;
@@ -1018,7 +1018,7 @@ struct StateFlags {
bool operator!=(const StateFlags& other) const;
void clear();
void clear_FF();
} __attribute__((packed));
} __packed_ws__(StateFlags, 0x18);
struct MapList {
be_uint32_t num_maps;
@@ -1046,18 +1046,18 @@ struct MapList {
/* 021C */ uint8_t map_category;
/* 021D */ parray<uint8_t, 3> unused;
/* 0220 */
} __attribute__((packed));
} __packed_ws__(Entry, 0x220);
// Variable-length fields:
// Entry entries[num_maps];
// char strings[...EOF]; // Null-terminated strings, pointed to by offsets in Entry structs
} __attribute__((packed));
} __packed_ws__(MapList, 0x10);
struct CompressedMapHeader { // .mnm file format
le_uint32_t map_number;
le_uint32_t compressed_data_size;
// Compressed data immediately follows (which decompresses to a MapDefinition)
} __attribute__((packed));
} __packed_ws__(CompressedMapHeader, 8);
struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// If tag is not 0x00000100, the game considers the map to be corrupt in
@@ -1155,7 +1155,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
std::string str() const;
JSON json() const;
} __attribute__((packed));
} __packed_ws__(CameraSpec, 0x48);
// This array specifies the camera zone maps. A camera zone map is a subset of
// the main map (specified in map_tiles). Tiles that are part of each camera
@@ -1213,7 +1213,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 18 */ parray<be_uint16_t, 0x20> card_ids; // Last one appears to always be FFFF
/* 58 */
JSON json(uint8_t language) const;
} __attribute__((packed));
} __packed_ws__(NPCDeck, 0x58);
/* 1FE8 */ parray<NPCDeck, 3> npc_decks; // Unused if name[0] == 0
// These are almost (but not quite) the same format as the entries in
@@ -1229,7 +1229,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 0018 */ parray<be_uint16_t, 0x7E> params;
/* 0114 */
JSON json(uint8_t language) const;
} __attribute__((packed));
} __packed_ws__(AIParams, 0x114);
/* 20F0 */ parray<AIParams, 3> npc_ai_params; // Unused if name[0] == 0
/* 242C */ parray<uint8_t, 8> unknown_a7;
@@ -1282,7 +1282,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 0004 */ parray<pstring<TextEncoding::MARKED, 0x40>, 4> strings;
/* 0104 */
JSON json(uint8_t language) const;
} __attribute__((packed));
} __packed_ws__(DialogueSet, 0x104);
// There are up to 0x10 of these per valid NPC, but only the first 13 of them
// are used, since each one must have a unique value for .when and the values
@@ -1365,7 +1365,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
bool operator==(const EntryState& other) const = default;
bool operator!=(const EntryState& other) const = default;
JSON json() const;
} __attribute__((packed));
} __packed_ws__(EntryState, 2);
/* 5A10 */ parray<EntryState, 4> entry_states;
/* 5A18 */
@@ -1381,7 +1381,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
std::string str(const CardIndex* card_index, uint8_t language) const;
JSON json(uint8_t language) const;
} __attribute__((packed));
} __packed_ws__(MapDefinition, 0x5A18);
struct MapDefinitionTrial {
// This is the format of Episode 3 Trial Edition maps. See the comments in
@@ -1430,7 +1430,7 @@ struct MapDefinitionTrial {
MapDefinitionTrial(const MapDefinition& map);
operator MapDefinition() const;
} __attribute__((packed));
} __packed_ws__(MapDefinitionTrial, 0x41A0);
struct COMDeckDefinition {
size_t index;
+2 -2
View File
@@ -20,7 +20,7 @@ struct NameEntry {
NameEntry();
void clear();
} __attribute__((packed));
} __packed_ws__(NameEntry, 0x14);
struct DeckEntry {
/* 00 */ pstring<TextEncoding::MARKED, 0x10> name;
@@ -37,7 +37,7 @@ struct DeckEntry {
DeckEntry();
void clear();
} __attribute__((packed));
} __packed_ws__(DeckEntry, 0x58);
uint8_t index_for_card_ref(uint16_t card_ref);
uint8_t client_id_for_card_ref(uint16_t card_ref);
+4 -4
View File
@@ -20,7 +20,7 @@ struct MapState {
void clear();
void print(FILE* stream) const;
} __attribute__((packed));
} __packed_ws__(MapState, 0x110);
struct MapAndRulesState {
/* 0000 */ MapState map;
@@ -45,7 +45,7 @@ struct MapAndRulesState {
void set_occupied_bit_for_tile(uint8_t x, uint8_t y);
void clear_occupied_bit_for_tile(uint8_t x, uint8_t y);
} __attribute__((packed));
} __packed_ws__(MapAndRulesState, 0x138);
struct MapAndRulesStateTrial {
/* 0000 */ MapState map;
@@ -65,7 +65,7 @@ struct MapAndRulesStateTrial {
MapAndRulesStateTrial() = default;
MapAndRulesStateTrial(const MapAndRulesState& state);
operator MapAndRulesState() const;
} __attribute__((packed));
} __packed_ws__(MapAndRulesStateTrial, 0x130);
struct OverlayState {
parray<parray<uint8_t, 0x10>, 0x10> tiles;
@@ -75,6 +75,6 @@ struct OverlayState {
OverlayState();
void clear();
} __attribute__((packed));
} __packed_ws__(OverlayState, 0x174);
} // namespace Episode3
+11 -11
View File
@@ -36,7 +36,7 @@ struct Condition {
void clear_FF();
std::string str(std::shared_ptr<const Server> s) const;
} __attribute__((packed));
} __packed_ws__(Condition, 0x10);
struct EffectResult {
/* 00 */ le_uint16_t attacker_card_ref;
@@ -58,7 +58,7 @@ struct EffectResult {
void clear();
std::string str(std::shared_ptr<const Server> s) const;
} __attribute__((packed));
} __packed_ws__(EffectResult, 0x0C);
struct CardShortStatus {
/* 00 */ le_uint16_t card_ref;
@@ -78,7 +78,7 @@ struct CardShortStatus {
void clear_FF();
std::string str(std::shared_ptr<const Server> s) const;
} __attribute__((packed));
} __packed_ws__(CardShortStatus, 0x10);
struct ActionState {
/* 00 */ le_uint16_t client_id;
@@ -99,7 +99,7 @@ struct ActionState {
void clear();
std::string str(std::shared_ptr<const Server> s) const;
} __attribute__((packed));
} __packed_ws__(ActionState, 0x64);
struct ActionChain {
// Note: Episode 3 Trial Edition has a different format for this structure.
@@ -135,7 +135,7 @@ struct ActionChain {
void clear_FF();
std::string str(std::shared_ptr<const Server> s) const;
} __attribute__((packed));
} __packed_ws__(ActionChain, 0x70);
struct ActionChainWithConds {
/* 0000 */ ActionChain chain;
@@ -173,7 +173,7 @@ struct ActionChainWithConds {
uint8_t get_adjusted_move_ability_nte(uint8_t ability) const;
std::string str(std::shared_ptr<const Server> s) const;
} __attribute__((packed));
} __packed_ws__(ActionChainWithConds, 0x100);
struct ActionChainWithCondsTrial {
/* 0000 */ int8_t effective_ap;
@@ -205,7 +205,7 @@ struct ActionChainWithCondsTrial {
ActionChainWithCondsTrial() = default;
ActionChainWithCondsTrial(const ActionChainWithConds& src);
operator ActionChainWithConds() const;
} __attribute__((packed));
} __packed_ws__(ActionChainWithCondsTrial, 0x100);
struct ActionMetadata {
/* 00 */ le_uint16_t card_ref;
@@ -241,7 +241,7 @@ struct ActionMetadata {
uint16_t original_attacker_card_ref);
std::string str(std::shared_ptr<const Server> s) const;
} __attribute__((packed));
} __packed_ws__(ActionMetadata, 0x74);
struct HandAndEquipState {
/* 00 */ parray<uint8_t, 2> dice_results;
@@ -276,7 +276,7 @@ struct HandAndEquipState {
void clear_FF();
std::string str(std::shared_ptr<const Server> s) const;
} __attribute__((packed));
} __packed_ws__(HandAndEquipState, 0x54);
struct PlayerBattleStats {
/* 00 */ le_uint16_t damage_given;
@@ -310,7 +310,7 @@ struct PlayerBattleStats {
static uint8_t rank_for_score(float score);
static const char* name_for_rank(uint8_t rank);
} __attribute__((packed));
} __packed_ws__(PlayerBattleStats, 0x28);
struct PlayerBattleStatsTrial {
/* 00 */ le_uint32_t damage_given = 0;
@@ -323,7 +323,7 @@ struct PlayerBattleStatsTrial {
PlayerBattleStatsTrial() = default;
PlayerBattleStatsTrial(const PlayerBattleStats& data);
operator PlayerBattleStats() const;
} __attribute__((packed));
} __packed_ws__(PlayerBattleStatsTrial, 0x14);
std::vector<uint16_t> get_card_refs_within_range(
const parray<uint8_t, 9 * 9>& range,
+2 -1
View File
@@ -290,7 +290,8 @@ public:
uint8_t is_cpu_player;
PresenceEntry();
void clear();
} __attribute__((packed));
} __packed_ws__(PresenceEntry, 3);
std::shared_ptr<MapAndRulesState> map_and_rules;
bcarray<std::shared_ptr<DeckEntry>, 4> deck_entries;
parray<PresenceEntry, 4> presence_entries;
+1 -1
View File
@@ -440,7 +440,7 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
char developer_code2 = 'P';
uint8_t disc_number = 0;
uint8_t version_code;
} __attribute__((packed)) data;
} __packed__ data;
for (const char* game_code2 = "OS"; *game_code2; game_code2++) {
data.game_code2 = *game_code2;
for (const char* region_code = "JEP"; *region_code; region_code++) {
+10 -5
View File
@@ -9,21 +9,26 @@
using namespace std;
template <bool IsBigEndian>
struct GSLHeaderEntry {
struct GSLHeaderEntryT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
pstring<TextEncoding::ASCII, 0x20> filename;
U32T offset; // In pages, so actual offset is this * 0x800
U32T size;
uint64_t unused;
} __attribute__((packed));
} __packed__;
using GSLHeaderEntry = GSLHeaderEntryT<false>;
using GSLHeaderEntryBE = GSLHeaderEntryT<true>;
check_struct_size(GSLHeaderEntry, 0x30);
check_struct_size(GSLHeaderEntryBE, 0x30);
template <bool IsBigEndian>
void GSLArchive::load_t() {
StringReader r(*this->data);
uint64_t min_data_offset = 0xFFFFFFFFFFFFFFFF;
while (r.where() < min_data_offset) {
const auto& entry = r.get<GSLHeaderEntry<IsBigEndian>>();
const auto& entry = r.get<GSLHeaderEntryT<IsBigEndian>>();
if (entry.filename.empty()) {
break;
}
@@ -85,10 +90,10 @@ string GSLArchive::generate_t(const unordered_map<string, string>& files) {
// Make sure there's enough space for a blank header entry before any file's
// data pages begin
uint32_t data_start_offset = ((sizeof(GSLHeaderEntry<IsBigEndian>) * (files.size() + 1)) + 0x7FF) & (~0x7FF);
uint32_t data_start_offset = ((sizeof(GSLHeaderEntryT<IsBigEndian>) * (files.size() + 1)) + 0x7FF) & (~0x7FF);
uint32_t data_offset = data_start_offset;
for (const auto& file : files) {
GSLHeaderEntry<IsBigEndian> entry;
GSLHeaderEntryT<IsBigEndian> entry;
entry.filename.encode(file.first);
entry.offset = data_offset >> 11;
entry.size = file.second.size();
+3 -3
View File
@@ -12,14 +12,14 @@ struct GVMFileEntry {
be_uint16_t file_num;
pstring<TextEncoding::ASCII, 0x1C> name;
parray<be_uint32_t, 2> unknown_a1;
} __attribute__((packed));
} __packed_ws__(GVMFileEntry, 0x26);
struct GVMFileHeader {
be_uint32_t magic; // 'GVMH'
le_uint32_t header_size;
be_uint16_t flags;
be_uint16_t num_files;
} __attribute__((packed));
} __packed_ws__(GVMFileHeader, 0x0C);
struct GVRHeader {
be_uint32_t magic; // 'GVRT'
@@ -29,7 +29,7 @@ struct GVRHeader {
GVRDataFormat data_format;
be_uint16_t width;
be_uint16_t height;
} __attribute__((packed));
} __packed_ws__(GVRHeader, 0x10);
string encode_gvm(const Image& img, GVRDataFormat data_format) {
if (img.get_width() > 0xFFFF) {
+5 -5
View File
@@ -380,10 +380,10 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
ret.emplace("BattleDisconnectCount", p->battle_records.disconnect_count.load());
if (!is_ep3(c->version())) {
auto json_for_challenge_times = []<size_t Count>(const parray<ChallengeTime<false>, Count>& times) -> JSON {
auto json_for_challenge_times = []<size_t Count>(const parray<ChallengeTime, Count>& times) -> JSON {
auto times_json = JSON::list();
for (size_t z = 0; z < times.size(); z++) {
times_json.emplace_back(times[z].load());
times_json.emplace_back(times[z].decode());
}
return times_json;
};
@@ -419,11 +419,11 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
ret.emplace("ChallengeGraveTeam", p->challenge_records.grave_team.decode());
ret.emplace("ChallengeGraveMessage", p->challenge_records.grave_message.decode());
ret.emplace("ChallengeAwardStateEp1OnlineFlags", p->challenge_records.ep1_online_award_state.rank_award_flags.load());
ret.emplace("ChallengeAwardStateEp1OnlineMaxRank", p->challenge_records.ep1_online_award_state.maximum_rank.load());
ret.emplace("ChallengeAwardStateEp1OnlineMaxRank", p->challenge_records.ep1_online_award_state.maximum_rank.decode());
ret.emplace("ChallengeAwardStateEp2OnlineFlags", p->challenge_records.ep2_online_award_state.rank_award_flags.load());
ret.emplace("ChallengeAwardStateEp2OnlineMaxRank", p->challenge_records.ep2_online_award_state.maximum_rank.load());
ret.emplace("ChallengeAwardStateEp2OnlineMaxRank", p->challenge_records.ep2_online_award_state.maximum_rank.decode());
ret.emplace("ChallengeAwardStateEp1OfflineFlags", p->challenge_records.ep1_offline_award_state.rank_award_flags.load());
ret.emplace("ChallengeAwardStateEp1OfflineMaxRank", p->challenge_records.ep1_offline_award_state.maximum_rank.load());
ret.emplace("ChallengeAwardStateEp1OfflineMaxRank", p->challenge_records.ep1_offline_award_state.maximum_rank.decode());
ret.emplace("ChallengeRankTitle", p->challenge_records.rank_title.decode());
}
}
+10 -10
View File
@@ -12,31 +12,31 @@ struct HDLCHeader {
uint8_t address; // 0xFF usually
uint8_t control; // 0x03 for PPP
be_uint16_t protocol;
} __attribute__((packed));
} __packed_ws__(HDLCHeader, 5);
struct LCPHeader {
uint8_t command;
uint8_t request_id;
be_uint16_t size;
} __attribute__((packed));
} __packed_ws__(LCPHeader, 4);
struct PAPHeader {
uint8_t command;
uint8_t request_id;
be_uint16_t size;
} __attribute__((packed));
} __packed_ws__(PAPHeader, 4);
struct IPCPHeader {
uint8_t command;
uint8_t request_id;
be_uint16_t size;
} __attribute__((packed));
} __packed_ws__(IPCPHeader, 4);
struct EthernetHeader {
parray<uint8_t, 6> dest_mac;
parray<uint8_t, 6> src_mac;
be_uint16_t protocol;
} __attribute__((packed));
} __packed_ws__(EthernetHeader, 0x0E);
struct ARPHeader {
be_uint16_t hardware_type;
@@ -44,7 +44,7 @@ struct ARPHeader {
uint8_t hwaddr_len;
uint8_t paddr_len;
be_uint16_t operation;
} __attribute__((packed));
} __packed_ws__(ARPHeader, 8);
struct IPv4Header {
uint8_t version_ihl;
@@ -57,14 +57,14 @@ struct IPv4Header {
be_uint16_t checksum;
be_uint32_t src_addr;
be_uint32_t dest_addr;
} __attribute__((packed));
} __packed_ws__(IPv4Header, 0x14);
struct UDPHeader {
be_uint16_t src_port;
be_uint16_t dest_port;
be_uint16_t size;
be_uint16_t checksum;
} __attribute__((packed));
} __packed_ws__(UDPHeader, 8);
struct TCPHeader {
enum Flag {
@@ -87,7 +87,7 @@ struct TCPHeader {
be_uint16_t window;
be_uint16_t checksum;
be_uint16_t urgent_ptr;
} __attribute__((packed));
} __packed_ws__(TCPHeader, 0x14);
struct DHCPHeader {
uint8_t opcode = 0;
@@ -105,7 +105,7 @@ struct DHCPHeader {
parray<uint8_t, 0xC0> unused_bootp_legacy;
be_uint32_t magic = 0x63825363;
// Options follow here, terminated with FF
} __attribute__((packed));
} __packed_ws__(DHCPHeader, 0xF0);
struct FrameInfo {
enum class LinkType {
+1 -1
View File
@@ -17,7 +17,7 @@ class IntegralExpression {
public:
struct Env {
const QuestFlagsForDifficulty* flags;
const PlayerRecordsBB_Challenge* challenge_records;
const PlayerRecordsChallengeBB* challenge_records;
std::shared_ptr<const TeamIndex::Team> team;
size_t num_players;
uint8_t event;
+1 -1
View File
@@ -78,7 +78,7 @@ private:
struct UnitResult {
uint8_t unit;
int8_t modifier;
} __attribute__((packed));
} __packed_ws__(UnitResult, 2);
std::array<std::vector<UnitResult>, 13> unit_results_by_star_count;
// Note: The original implementation uses 17 different random states for some
+3 -3
View File
@@ -121,7 +121,7 @@ struct ItemData {
parray<be_uint16_t, 6> data1wb;
parray<le_uint32_t, 3> data1d;
parray<be_uint32_t, 3> data1db;
} __attribute__((packed));
} __packed__;
le_uint32_t id;
union {
parray<uint8_t, 4> data2;
@@ -129,7 +129,7 @@ struct ItemData {
parray<be_uint16_t, 2> data2wb;
le_uint32_t data2d;
be_uint32_t data2db;
} __attribute__((packed));
} __packed__;
ItemData();
ItemData(const ItemData& other);
@@ -195,4 +195,4 @@ struct ItemData {
bool empty() const;
static bool compare_for_sort(const ItemData& a, const ItemData& b);
} __attribute__((packed));
} __packed_ws__(ItemData, 0x14);
+53 -53
View File
@@ -79,9 +79,9 @@ ItemParameterTable::ItemParameterTable(shared_ptr<const string> data, Version ve
case Version::GC_V3:
case Version::XB_V3: {
if (is_big_endian(this->version)) {
this->offsets_v3_be = &this->r.pget<TableOffsetsV3V4<true>>(offset_table_offset);
this->offsets_v3_be = &this->r.pget<TableOffsetsV3V4BE>(offset_table_offset);
} else {
this->offsets_v3_le = &this->r.pget<TableOffsetsV3V4<false>>(offset_table_offset);
this->offsets_v3_le = &this->r.pget<TableOffsetsV3V4>(offset_table_offset);
}
this->num_weapon_classes = 0xAA;
this->num_tool_classes = 0x18;
@@ -93,7 +93,7 @@ ItemParameterTable::ItemParameterTable(shared_ptr<const string> data, Version ve
}
case Version::BB_V4: {
this->offsets_v4 = &this->r.pget<TableOffsetsV3V4<false>>(offset_table_offset);
this->offsets_v4 = &this->r.pget<TableOffsetsV3V4>(offset_table_offset);
this->num_weapon_classes = 0xED;
this->num_tool_classes = 0x1B;
this->item_stars_first_id = 0xB1;
@@ -207,7 +207,7 @@ ItemParameterTable::WeaponV4 ItemParameterTable::WeaponGCNTE::to_v4() const {
}
template <bool IsBigEndian>
ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV3<IsBigEndian>::to_v4() const {
ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV3T<IsBigEndian>::to_v4() const {
WeaponV4 ret;
ret.base.id = this->base.id.load();
ret.base.type = this->base.type.load();
@@ -282,7 +282,7 @@ ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV1V2::to_v4
}
template <bool IsBigEndian>
ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV3<IsBigEndian>::to_v4() const {
ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV3T<IsBigEndian>::to_v4() const {
ArmorOrShieldV4 ret;
ret.base.id = this->base.id.load();
ret.base.type = this->base.type.load();
@@ -324,7 +324,7 @@ ItemParameterTable::UnitV4 ItemParameterTable::UnitV1V2::to_v4() const {
}
template <bool IsBigEndian>
ItemParameterTable::UnitV4 ItemParameterTable::UnitV3<IsBigEndian>::to_v4() const {
ItemParameterTable::UnitV4 ItemParameterTable::UnitV3T<IsBigEndian>::to_v4() const {
UnitV4 ret;
ret.base.id = this->base.id.load();
ret.base.type = this->base.type.load();
@@ -371,7 +371,7 @@ ItemParameterTable::MagV4 ItemParameterTable::MagV2::to_v4() const {
}
template <bool IsBigEndian>
ItemParameterTable::MagV4 ItemParameterTable::MagV3<IsBigEndian>::to_v4() const {
ItemParameterTable::MagV4 ItemParameterTable::MagV3T<IsBigEndian>::to_v4() const {
MagV4 ret;
ret.base.id = this->base.id.load();
ret.base.type = this->base.type.load();
@@ -402,7 +402,7 @@ ItemParameterTable::ToolV4 ItemParameterTable::ToolV1V2::to_v4() const {
}
template <bool IsBigEndian>
ItemParameterTable::ToolV4 ItemParameterTable::ToolV3<IsBigEndian>::to_v4() const {
ItemParameterTable::ToolV4 ItemParameterTable::ToolV3T<IsBigEndian>::to_v4() const {
ToolV4 ret;
ret.base.id = this->base.id.load();
ret.base.type = this->base.type.load();
@@ -416,13 +416,13 @@ ItemParameterTable::ToolV4 ItemParameterTable::ToolV3<IsBigEndian>::to_v4() cons
template <bool IsBigEndian>
size_t indirect_lookup_2d_count(const StringReader& r, size_t root_offset, size_t co_index) {
using ArrayRefT = typename std::conditional_t<IsBigEndian, ItemParameterTable::ArrayRefBE, ItemParameterTable::ArrayRefLE>;
using ArrayRefT = typename std::conditional_t<IsBigEndian, ItemParameterTable::ArrayRefBE, ItemParameterTable::ArrayRef>;
return r.pget<ArrayRefT>(root_offset + sizeof(ArrayRefT) * co_index).count;
}
template <typename T, bool IsBigEndian>
const T& indirect_lookup_2d(const StringReader& r, size_t root_offset, size_t co_index, size_t item_index) {
using ArrayRefT = typename std::conditional_t<IsBigEndian, ItemParameterTable::ArrayRefBE, ItemParameterTable::ArrayRefLE>;
using ArrayRefT = typename std::conditional_t<IsBigEndian, ItemParameterTable::ArrayRefBE, ItemParameterTable::ArrayRef>;
const auto& co = r.pget<ArrayRefT>(root_offset + sizeof(ArrayRefT) * co_index);
if (item_index >= co.count) {
@@ -473,9 +473,9 @@ const ItemParameterTable::WeaponV4& ItemParameterTable::get_weapon(uint8_t data1
} else if (this->offsets_gc_nte) {
def_v4 = indirect_lookup_2d<WeaponGCNTE, true>(this->r, this->offsets_gc_nte->weapon_table, data1_1, data1_2).to_v4();
} else if (this->offsets_v3_le) {
def_v4 = indirect_lookup_2d<WeaponV3<false>, false>(this->r, this->offsets_v3_le->weapon_table, data1_1, data1_2).to_v4();
def_v4 = indirect_lookup_2d<WeaponV3, false>(this->r, this->offsets_v3_le->weapon_table, data1_1, data1_2).to_v4();
} else if (this->offsets_v3_be) {
def_v4 = indirect_lookup_2d<WeaponV3<true>, true>(this->r, this->offsets_v3_be->weapon_table, data1_1, data1_2).to_v4();
def_v4 = indirect_lookup_2d<WeaponV3BE, true>(this->r, this->offsets_v3_be->weapon_table, data1_1, data1_2).to_v4();
} else {
throw logic_error("table is not v2, v3, or v4");
}
@@ -528,11 +528,11 @@ const ItemParameterTable::ArmorOrShieldV4& ItemParameterTable::get_armor_or_shie
} else if (this->offsets_v1_v2) {
def_v4 = indirect_lookup_2d<ArmorOrShieldV1V2, false>(this->r, this->offsets_v1_v2->armor_table, data1_1 - 1, data1_2).to_v4();
} else if (this->offsets_gc_nte) {
def_v4 = indirect_lookup_2d<ArmorOrShieldV3<true>, true>(this->r, this->offsets_gc_nte->armor_table, data1_1 - 1, data1_2).to_v4();
def_v4 = indirect_lookup_2d<ArmorOrShieldV3BE, true>(this->r, this->offsets_gc_nte->armor_table, data1_1 - 1, data1_2).to_v4();
} else if (this->offsets_v3_le) {
def_v4 = indirect_lookup_2d<ArmorOrShieldV3<false>, false>(this->r, this->offsets_v3_le->armor_table, data1_1 - 1, data1_2).to_v4();
def_v4 = indirect_lookup_2d<ArmorOrShieldV3, false>(this->r, this->offsets_v3_le->armor_table, data1_1 - 1, data1_2).to_v4();
} else if (this->offsets_v3_be) {
def_v4 = indirect_lookup_2d<ArmorOrShieldV3<true>, true>(this->r, this->offsets_v3_be->armor_table, data1_1 - 1, data1_2).to_v4();
def_v4 = indirect_lookup_2d<ArmorOrShieldV3BE, true>(this->r, this->offsets_v3_be->armor_table, data1_1 - 1, data1_2).to_v4();
} else {
throw logic_error("table is not v2, v3, or v4");
}
@@ -580,11 +580,11 @@ const ItemParameterTable::UnitV4& ItemParameterTable::get_unit(uint8_t data1_2)
} else if (this->offsets_v1_v2) {
def_v4 = indirect_lookup_2d<UnitV1V2, false>(this->r, this->offsets_v1_v2->unit_table, 0, data1_2).to_v4();
} else if (this->offsets_gc_nte) {
def_v4 = indirect_lookup_2d<UnitV3<true>, true>(this->r, this->offsets_gc_nte->unit_table, 0, data1_2).to_v4();
def_v4 = indirect_lookup_2d<UnitV3BE, true>(this->r, this->offsets_gc_nte->unit_table, 0, data1_2).to_v4();
} else if (this->offsets_v3_le) {
def_v4 = indirect_lookup_2d<UnitV3<false>, false>(this->r, this->offsets_v3_le->unit_table, 0, data1_2).to_v4();
def_v4 = indirect_lookup_2d<UnitV3, false>(this->r, this->offsets_v3_le->unit_table, 0, data1_2).to_v4();
} else if (this->offsets_v3_be) {
def_v4 = indirect_lookup_2d<UnitV3<true>, true>(this->r, this->offsets_v3_be->unit_table, 0, data1_2).to_v4();
def_v4 = indirect_lookup_2d<UnitV3BE, true>(this->r, this->offsets_v3_be->unit_table, 0, data1_2).to_v4();
} else {
throw logic_error("table is not v2, v3, or v4");
}
@@ -636,11 +636,11 @@ const ItemParameterTable::MagV4& ItemParameterTable::get_mag(uint8_t data1_1) co
def_v4 = indirect_lookup_2d<MagV2, false>(this->r, this->offsets_v1_v2->mag_table, 0, data1_1).to_v4();
}
} else if (this->offsets_gc_nte) {
def_v4 = indirect_lookup_2d<MagV3<true>, true>(this->r, this->offsets_gc_nte->mag_table, 0, data1_1).to_v4();
def_v4 = indirect_lookup_2d<MagV3BE, true>(this->r, this->offsets_gc_nte->mag_table, 0, data1_1).to_v4();
} else if (this->offsets_v3_le) {
def_v4 = indirect_lookup_2d<MagV3<false>, false>(this->r, this->offsets_v3_le->mag_table, 0, data1_1).to_v4();
def_v4 = indirect_lookup_2d<MagV3, false>(this->r, this->offsets_v3_le->mag_table, 0, data1_1).to_v4();
} else if (this->offsets_v3_be) {
def_v4 = indirect_lookup_2d<MagV3<true>, true>(this->r, this->offsets_v3_be->mag_table, 0, data1_1).to_v4();
def_v4 = indirect_lookup_2d<MagV3BE, true>(this->r, this->offsets_v3_be->mag_table, 0, data1_1).to_v4();
} else {
throw logic_error("table is not v2, v3, or v4");
}
@@ -693,11 +693,11 @@ const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1,
} else if (this->offsets_v1_v2) {
def_v4 = indirect_lookup_2d<ToolV1V2, false>(this->r, this->offsets_v1_v2->tool_table, data1_1, data1_2).to_v4();
} else if (this->offsets_gc_nte) {
def_v4 = indirect_lookup_2d<ToolV3<true>, true>(this->r, this->offsets_gc_nte->tool_table, data1_1, data1_2).to_v4();
def_v4 = indirect_lookup_2d<ToolV3BE, true>(this->r, this->offsets_gc_nte->tool_table, data1_1, data1_2).to_v4();
} else if (this->offsets_v3_le) {
def_v4 = indirect_lookup_2d<ToolV3<false>, false>(this->r, this->offsets_v3_le->tool_table, data1_1, data1_2).to_v4();
def_v4 = indirect_lookup_2d<ToolV3, false>(this->r, this->offsets_v3_le->tool_table, data1_1, data1_2).to_v4();
} else if (this->offsets_v3_be) {
def_v4 = indirect_lookup_2d<ToolV3<true>, true>(this->r, this->offsets_v3_be->tool_table, data1_1, data1_2).to_v4();
def_v4 = indirect_lookup_2d<ToolV3BE, true>(this->r, this->offsets_v3_be->tool_table, data1_1, data1_2).to_v4();
} else {
throw logic_error("table is not v2, v3, or v4");
}
@@ -706,13 +706,13 @@ const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1,
}
}
template <typename ToolT, bool IsBigEndian>
template <typename ToolDefT, bool IsBigEndian>
pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_id_t(uint32_t tool_table_offset, uint32_t item_id) const {
const auto* cos = &this->r.pget<ArrayRef<IsBigEndian>>(
tool_table_offset, this->num_tool_classes * sizeof(ArrayRef<IsBigEndian>));
const auto* cos = &this->r.pget<ArrayRefT<IsBigEndian>>(
tool_table_offset, this->num_tool_classes * sizeof(ArrayRefT<IsBigEndian>));
for (size_t z = 0; z < this->num_tool_classes; z++) {
const auto& co = cos[z];
const auto* defs = &this->r.pget<ToolT>(co.offset, sizeof(ToolT) * co.count);
const auto* defs = &this->r.pget<ToolDefT>(co.offset, sizeof(ToolDefT) * co.count);
for (size_t y = 0; y < co.count; y++) {
if (defs[y].base.id == item_id) {
return make_pair(z, y);
@@ -728,11 +728,11 @@ pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_id(uint32_t item_id) con
} else if (this->offsets_v1_v2) {
return this->find_tool_by_id_t<ToolV1V2, false>(this->offsets_v1_v2->tool_table, item_id);
} else if (this->offsets_gc_nte) {
return this->find_tool_by_id_t<ToolV3<true>, true>(this->offsets_gc_nte->tool_table, item_id);
return this->find_tool_by_id_t<ToolV3BE, true>(this->offsets_gc_nte->tool_table, item_id);
} else if (this->offsets_v3_le) {
return this->find_tool_by_id_t<ToolV3<false>, false>(this->offsets_v3_le->tool_table, item_id);
return this->find_tool_by_id_t<ToolV3, false>(this->offsets_v3_le->tool_table, item_id);
} else if (this->offsets_v3_be) {
return this->find_tool_by_id_t<ToolV3<true>, true>(this->offsets_v3_be->tool_table, item_id);
return this->find_tool_by_id_t<ToolV3BE, true>(this->offsets_v3_be->tool_table, item_id);
} else if (this->offsets_v4) {
return this->find_tool_by_id_t<ToolV4, false>(this->offsets_v4->tool_table, item_id);
} else {
@@ -752,7 +752,7 @@ float ItemParameterTable::get_sale_divisor_t(const OffsetsT* offsets, uint8_t da
return this->r.pget<FloatT>(offsets->weapon_sale_divisor_table + data1_1 * sizeof(FloatT));
case 1: {
const auto& divisors = this->r.pget<NonWeaponSaleDivisors<IsBigEndian>>(offsets->sale_divisor_table);
const auto& divisors = this->r.pget<NonWeaponSaleDivisorsT<IsBigEndian>>(offsets->sale_divisor_table);
switch (data1_1) {
case 1:
return divisors.armor_divisor;
@@ -765,7 +765,7 @@ float ItemParameterTable::get_sale_divisor_t(const OffsetsT* offsets, uint8_t da
}
case 2: {
const auto& divisors = this->r.pget<NonWeaponSaleDivisors<IsBigEndian>>(offsets->sale_divisor_table);
const auto& divisors = this->r.pget<NonWeaponSaleDivisorsT<IsBigEndian>>(offsets->sale_divisor_table);
return divisors.mag_divisor;
}
@@ -803,22 +803,22 @@ const ItemParameterTable::MagFeedResult& ItemParameterTable::get_mag_feed_result
uint32_t offset;
if (this->offsets_dc_protos) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<false>>(this->offsets_dc_protos->mag_feed_table);
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets>(this->offsets_dc_protos->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else if (this->offsets_v1_v2) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<false>>(this->offsets_v1_v2->mag_feed_table);
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets>(this->offsets_v1_v2->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else if (this->offsets_gc_nte) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<true>>(this->offsets_gc_nte->mag_feed_table);
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsetsBE>(this->offsets_gc_nte->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else if (this->offsets_v3_le) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<false>>(this->offsets_v3_le->mag_feed_table);
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets>(this->offsets_v3_le->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else if (this->offsets_v3_be) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<true>>(this->offsets_v3_be->mag_feed_table);
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsetsBE>(this->offsets_v3_be->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else if (this->offsets_v4) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<false>>(this->offsets_v4->mag_feed_table);
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets>(this->offsets_v4->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else {
throw logic_error("table is not v2, v3, or v4");
@@ -856,24 +856,24 @@ uint8_t ItemParameterTable::get_special_stars(uint8_t special) const {
: 0;
}
const ItemParameterTable::Special<false>& ItemParameterTable::get_special(uint8_t special) const {
const ItemParameterTable::Special& ItemParameterTable::get_special(uint8_t special) const {
special &= 0x3F;
if (special >= this->num_specials) {
throw out_of_range("invalid special index");
}
if (this->offsets_dc_protos) {
return this->r.pget<Special<false>>(this->offsets_dc_protos->special_data_table + sizeof(Special<false>) * special);
return this->r.pget<Special>(this->offsets_dc_protos->special_data_table + sizeof(Special) * special);
} else if (this->offsets_v1_v2) {
return this->r.pget<Special<false>>(this->offsets_v1_v2->special_data_table + sizeof(Special<false>) * special);
return this->r.pget<Special>(this->offsets_v1_v2->special_data_table + sizeof(Special) * special);
} else if (this->offsets_v3_le) {
return this->r.pget<Special<false>>(this->offsets_v3_le->special_data_table + sizeof(Special<false>) * special);
return this->r.pget<Special>(this->offsets_v3_le->special_data_table + sizeof(Special) * special);
} else if (this->offsets_gc_nte) {
if ((special >= this->parsed_specials.size()) || (this->parsed_specials[special].type != 0xFFFF)) {
if (special >= this->parsed_specials.size()) {
this->parsed_specials.resize(special + 1);
}
const auto& sp_be = this->r.pget<Special<true>>(this->offsets_gc_nte->special_data_table + sizeof(Special<true>) * special);
const auto& sp_be = this->r.pget<SpecialBE>(this->offsets_gc_nte->special_data_table + sizeof(SpecialBE) * special);
this->parsed_specials[special].type = sp_be.type.load();
this->parsed_specials[special].amount = sp_be.amount.load();
}
@@ -883,13 +883,13 @@ const ItemParameterTable::Special<false>& ItemParameterTable::get_special(uint8_
if (special >= this->parsed_specials.size()) {
this->parsed_specials.resize(special + 1);
}
const auto& sp_be = this->r.pget<Special<true>>(this->offsets_v3_be->special_data_table + sizeof(Special<true>) * special);
const auto& sp_be = this->r.pget<SpecialBE>(this->offsets_v3_be->special_data_table + sizeof(SpecialBE) * special);
this->parsed_specials[special].type = sp_be.type.load();
this->parsed_specials[special].amount = sp_be.amount.load();
}
return this->parsed_specials[special];
} else if (this->offsets_v4) {
return this->r.pget<Special<false>>(this->offsets_v4->special_data_table + sizeof(Special<false>) * special);
return this->r.pget<Special>(this->offsets_v4->special_data_table + sizeof(Special) * special);
} else {
throw logic_error("table is not v2, v3, or v4");
}
@@ -1051,7 +1051,7 @@ bool ItemParameterTable::is_unsealable_item(uint8_t data1_0, uint8_t data1_1, ui
if (this->offsets_dc_protos || this->offsets_v1_v2 || this->offsets_gc_nte) {
return false;
} else if (this->offsets_v3_le) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v3_le->unsealable_table);
const auto& co = this->r.pget<ArrayRef>(this->offsets_v3_le->unsealable_table);
offset = co.offset;
count = co.count;
} else if (this->offsets_v3_be) {
@@ -1059,7 +1059,7 @@ bool ItemParameterTable::is_unsealable_item(uint8_t data1_0, uint8_t data1_1, ui
offset = co.offset;
count = co.count;
} else if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->unsealable_table);
const auto& co = this->r.pget<ArrayRef>(this->offsets_v4->unsealable_table);
offset = co.offset;
count = co.count;
} else {
@@ -1111,7 +1111,7 @@ const std::map<uint32_t, std::vector<ItemParameterTable::ItemCombination>>& Item
static const std::map<uint32_t, std::vector<ItemParameterTable::ItemCombination>> empty_map;
return empty_map;
} else if (this->offsets_v3_le) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v3_le->combination_table);
const auto& co = this->r.pget<ArrayRef>(this->offsets_v3_le->combination_table);
offset = co.offset;
count = co.count;
} else if (this->offsets_v3_be) {
@@ -1119,7 +1119,7 @@ const std::map<uint32_t, std::vector<ItemParameterTable::ItemCombination>>& Item
offset = co.offset;
count = co.count;
} else if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->combination_table);
const auto& co = this->r.pget<ArrayRef>(this->offsets_v4->combination_table);
offset = co.offset;
count = co.count;
} else {
@@ -1138,7 +1138,7 @@ const std::map<uint32_t, std::vector<ItemParameterTable::ItemCombination>>& Item
template <bool IsBigEndian>
size_t ItemParameterTable::num_events_t(uint32_t base_offset) const {
return this->r.pget<ArrayRef<IsBigEndian>>(base_offset).count;
return this->r.pget<ArrayRefT<IsBigEndian>>(base_offset).count;
}
size_t ItemParameterTable::num_events() const {
@@ -1158,11 +1158,11 @@ size_t ItemParameterTable::num_events() const {
template <bool IsBigEndian>
std::pair<const ItemParameterTable::EventItem*, size_t> ItemParameterTable::get_event_items_t(
uint32_t base_offset, uint8_t event_number) const {
const auto& co = this->r.pget<ArrayRef<IsBigEndian>>(base_offset);
const auto& co = this->r.pget<ArrayRefT<IsBigEndian>>(base_offset);
if (event_number >= co.count) {
throw out_of_range("invalid event number");
}
const auto& event_co = this->r.pget<ArrayRef<IsBigEndian>>(co.offset + sizeof(ArrayRef<IsBigEndian>) * event_number);
const auto& event_co = this->r.pget<ArrayRefT<IsBigEndian>>(co.offset + sizeof(ArrayRefT<IsBigEndian>) * event_number);
const auto* defs = &this->r.pget<EventItem>(event_co.offset, event_co.count * sizeof(EventItem));
return make_pair(defs, event_co.count);
}
+142 -101
View File
@@ -19,42 +19,43 @@ public:
// being null or not in each public function. Rewrite this and make it better.
template <bool IsBigEndian>
struct ArrayRef {
struct ArrayRefT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ U32T count;
/* 04 */ U32T offset;
/* 08 */
} __attribute__((packed));
struct ArrayRefLE : ArrayRef<false> {
} __attribute__((packed));
struct ArrayRefBE : ArrayRef<true> {
} __attribute__((packed));
} __packed__;
using ArrayRef = ArrayRefT<false>;
using ArrayRefBE = ArrayRefT<true>;
check_struct_size(ArrayRef, 8);
check_struct_size(ArrayRefBE, 8);
template <bool IsBigEndian>
struct ItemBaseV2 {
struct ItemBaseV2T {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
// id specifies several things; notably, it doubles as the index of the
// item's name in the text archive (e.g. TextEnglish) collection 0.
/* 00 */ U32T id = 0xFFFFFFFF;
/* 04 */
} __attribute__((packed));
} __packed__;
template <bool IsBigEndian>
struct ItemBaseV3 : ItemBaseV2<IsBigEndian> {
struct ItemBaseV3T : ItemBaseV2T<IsBigEndian> {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 04 */ U16T type = 0;
/* 06 */ U16T skin = 0;
/* 08 */
} __attribute__((packed));
} __packed__;
template <bool IsBigEndian>
struct ItemBaseV4 : ItemBaseV3<IsBigEndian> {
struct ItemBaseV4T : ItemBaseV3T<IsBigEndian> {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 08 */ U32T team_points = 0;
/* 0C */
} __attribute__((packed));
} __packed__;
struct WeaponV4;
struct WeaponDCProtos {
/* 00 */ ItemBaseV2<false> base;
/* 00 */ ItemBaseV2T<false> base;
/* 04 */ le_uint16_t class_flags = 0;
/* 06 */ le_uint16_t atp_min = 0;
/* 08 */ le_uint16_t atp_max = 0;
@@ -68,10 +69,10 @@ public:
/* 14 */
WeaponV4 to_v4() const;
} __attribute__((packed));
} __packed_ws__(WeaponDCProtos, 0x14);
struct WeaponV1V2 {
/* 00 */ ItemBaseV2<false> base;
/* 00 */ ItemBaseV2T<false> base;
/* 04 */ le_uint16_t class_flags = 0;
/* 06 */ le_uint16_t atp_min = 0;
/* 08 */ le_uint16_t atp_max = 0;
@@ -87,10 +88,10 @@ public:
/* 18 */
WeaponV4 to_v4() const;
} __attribute__((packed));
} __packed_ws__(WeaponV1V2, 0x18);
struct WeaponGCNTE {
/* 00 */ ItemBaseV3<true> base;
/* 00 */ ItemBaseV3T<true> base;
/* 08 */ be_uint16_t class_flags = 0;
/* 0A */ be_uint16_t atp_min = 0;
/* 0C */ be_uint16_t atp_max = 0;
@@ -115,12 +116,12 @@ public:
/* 24 */
WeaponV4 to_v4() const;
} __attribute__((packed));
} __packed_ws__(WeaponGCNTE, 0x24);
template <bool IsBigEndian>
struct WeaponV3 {
struct WeaponV3T {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 00 */ ItemBaseV3<IsBigEndian> base;
/* 00 */ ItemBaseV3T<IsBigEndian> base;
/* 08 */ U16T class_flags = 0;
/* 0A */ U16T atp_min = 0;
/* 0C */ U16T atp_max = 0;
@@ -149,10 +150,15 @@ public:
/* 28 */
WeaponV4 to_v4() const;
} __attribute__((packed));
} __packed__;
using WeaponV3 = WeaponV3T<false>;
using WeaponV3BE = WeaponV3T<true>;
check_struct_size(WeaponV3, 0x28);
check_struct_size(WeaponV3BE, 0x28);
struct WeaponV4 {
/* 00 */ ItemBaseV4<false> base;
/* 00 */ ItemBaseV4T<false> base;
/* 0C */ le_uint16_t class_flags = 0x00FF;
/* 0E */ le_uint16_t atp_min = 0;
/* 10 */ le_uint16_t atp_max = 0;
@@ -179,10 +185,10 @@ public:
/* 2A */ uint8_t tech_boost = 0;
/* 2B */ uint8_t combo_type = 0;
/* 2C */
} __attribute__((packed));
} __packed_ws__(WeaponV4, 0x2C);
template <typename BaseT, bool IsBigEndian>
struct ArmorOrShield {
struct ArmorOrShieldT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* V1/V2 offsets */
/* 00 */ BaseT base;
@@ -200,33 +206,36 @@ public:
/* 12 */ uint8_t dfp_range = 0;
/* 13 */ uint8_t evp_range = 0;
/* 14 */
} __attribute__((packed));
} __packed__;
struct ArmorOrShieldV4;
struct ArmorOrShieldDCProtos : ArmorOrShield<ItemBaseV2<false>, false> {
ArmorOrShieldV4 to_v4() const;
} __attribute__((packed));
template <typename BaseT, bool IsBigEndian>
struct ArmorOrShieldFinal : ArmorOrShield<BaseT, IsBigEndian> {
struct ArmorOrShieldFinalT : ArmorOrShieldT<BaseT, IsBigEndian> {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 14 */ uint8_t stat_boost = 0;
/* 15 */ uint8_t tech_boost = 0;
/* 16 */ U16T unknown_a2 = 0;
/* 18 */
} __attribute__((packed));
} __packed__;
using ArmorOrShieldV4 = ArmorOrShieldFinalT<ItemBaseV4T<false>, false>;
check_struct_size(ArmorOrShieldV4, 0x20);
struct ArmorOrShieldDCProtos : ArmorOrShieldT<ItemBaseV2T<false>, false> {
ArmorOrShieldV4 to_v4() const;
} __packed_ws__(ArmorOrShieldDCProtos, 0x14);
struct ArmorOrShieldV1V2 : ArmorOrShieldFinal<ItemBaseV2<false>, false> {
struct ArmorOrShieldV1V2 : ArmorOrShieldFinalT<ItemBaseV2T<false>, false> {
ArmorOrShieldV4 to_v4() const;
} __attribute__((packed));
} __packed_ws__(ArmorOrShieldV1V2, 0x18);
template <bool IsBigEndian>
struct ArmorOrShieldV3 : ArmorOrShieldFinal<ItemBaseV3<IsBigEndian>, IsBigEndian> {
struct ArmorOrShieldV3T : ArmorOrShieldFinalT<ItemBaseV3T<IsBigEndian>, IsBigEndian> {
ArmorOrShieldV4 to_v4() const;
} __attribute__((packed));
struct ArmorOrShieldV4 : ArmorOrShieldFinal<ItemBaseV4<false>, false> {
} __attribute__((packed));
} __packed__;
using ArmorOrShieldV3 = ArmorOrShieldV3T<false>;
using ArmorOrShieldV3BE = ArmorOrShieldV3T<true>;
check_struct_size(ArmorOrShieldV3, 0x1C);
check_struct_size(ArmorOrShieldV3BE, 0x1C);
template <typename BaseT, bool IsBigEndian>
struct Unit {
struct UnitT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using S16T = typename std::conditional<IsBigEndian, be_int16_t, le_int16_t>::type;
/* V1/V2 offsets */
@@ -234,31 +243,34 @@ public:
/* 04 */ U16T stat = 0;
/* 06 */ U16T stat_amount = 0;
/* 08 */
} __attribute__((packed));
} __packed__;
struct UnitV4;
struct UnitDCProtos : Unit<ItemBaseV2<false>, false> {
UnitV4 to_v4() const;
} __attribute__((packed));
template <typename BaseT, bool IsBigEndian>
struct UnitFinal : Unit<BaseT, IsBigEndian> {
struct UnitFinalT : UnitT<BaseT, IsBigEndian> {
using S16T = typename std::conditional<IsBigEndian, be_int16_t, le_int16_t>::type;
/* 08 */ S16T modifier_amount = 0;
/* 0A */ parray<uint8_t, 2> unused;
/* 0C */
} __attribute__((packed));
struct UnitV1V2 : UnitFinal<ItemBaseV2<false>, false> {
} __packed__;
using UnitV4 = UnitFinalT<ItemBaseV4T<false>, false>;
check_struct_size(UnitV4, 0x14);
struct UnitDCProtos : UnitT<ItemBaseV2T<false>, false> {
UnitV4 to_v4() const;
} __attribute__((packed));
} __packed_ws__(UnitDCProtos, 0x08);
struct UnitV1V2 : UnitFinalT<ItemBaseV2T<false>, false> {
UnitV4 to_v4() const;
} __packed_ws__(UnitV1V2, 0x0C);
template <bool IsBigEndian>
struct UnitV3 : UnitFinal<ItemBaseV3<IsBigEndian>, IsBigEndian> {
struct UnitV3T : UnitFinalT<ItemBaseV3T<IsBigEndian>, IsBigEndian> {
UnitV4 to_v4() const;
} __attribute__((packed));
struct UnitV4 : UnitFinal<ItemBaseV4<false>, false> {
} __attribute__((packed));
} __packed__;
using UnitV3 = UnitV3T<false>;
using UnitV3BE = UnitV3T<true>;
check_struct_size(UnitV3, 0x10);
check_struct_size(UnitV3BE, 0x10);
template <typename BaseT, bool IsBigEndian>
struct Mag {
struct MagT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* V1/V2 offsets */
/* 00 */ BaseT base;
@@ -289,36 +301,38 @@ public:
/* 0E */ uint8_t on_death_flag = 0;
/* 0F */ uint8_t on_boss_flag = 0;
/* 10 */
} __attribute__((packed));
} __packed__;
struct MagV4;
struct MagV1 : Mag<ItemBaseV2<false>, false> {
struct MagV4 : MagT<ItemBaseV4T<false>, false> {
le_uint16_t class_flags = 0x00FF;
parray<uint8_t, 2> unused;
} __packed_ws__(MagV4, 0x1C);
struct MagV1 : MagT<ItemBaseV2T<false>, false> {
MagV4 to_v4() const;
} __attribute__((packed));
struct MagV2 : Mag<ItemBaseV2<false>, false> {
} __packed_ws__(MagV1, 0x10);
struct MagV2 : MagT<ItemBaseV2T<false>, false> {
/* 10 */ le_uint16_t class_flags = 0x00FF;
/* 12 */ parray<uint8_t, 2> unused;
/* 14 */
MagV4 to_v4() const;
} __attribute__((packed));
} __packed_ws__(MagV2, 0x14);
template <bool IsBigEndian>
struct MagV3 : Mag<ItemBaseV3<IsBigEndian>, IsBigEndian> {
struct MagV3T : MagT<ItemBaseV3T<IsBigEndian>, IsBigEndian> {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 10 */ U16T class_flags = 0x00FF;
/* 12 */ parray<uint8_t, 2> unused;
/* 14 */
MagV4 to_v4() const;
} __attribute__((packed));
struct MagV4 : Mag<ItemBaseV4<false>, false> {
/* 10 */ le_uint16_t class_flags = 0x00FF;
/* 12 */ parray<uint8_t, 2> unused;
/* 14 */
} __attribute__((packed));
} __packed__;
using MagV3 = MagV3T<false>;
using MagV3BE = MagV3T<true>;
check_struct_size(MagV3, 0x18);
check_struct_size(MagV3BE, 0x18);
template <typename BaseT, bool IsBigEndian>
struct Tool {
struct ToolT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using S32T = typename std::conditional<IsBigEndian, be_int32_t, le_int32_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
@@ -329,18 +343,21 @@ public:
/* 08 */ S32T cost = 0;
/* 0C */ U32T item_flag = 0;
/* 10 */
} __attribute__((packed));
} __packed__;
struct ToolV4;
struct ToolV1V2 : Tool<ItemBaseV2<false>, false> {
using ToolV4 = ToolT<ItemBaseV4T<false>, false>;
check_struct_size(ToolV4, 0x18);
struct ToolV1V2 : ToolT<ItemBaseV2T<false>, false> {
ToolV4 to_v4() const;
} __attribute__((packed));
} __packed_ws__(ToolV1V2, 0x10);
template <bool IsBigEndian>
struct ToolV3 : Tool<ItemBaseV3<IsBigEndian>, IsBigEndian> {
struct ToolV3T : ToolT<ItemBaseV3T<IsBigEndian>, IsBigEndian> {
ToolV4 to_v4() const;
} __attribute__((packed));
struct ToolV4 : Tool<ItemBaseV4<false>, false> {
} __attribute__((packed));
} __packed__;
using ToolV3 = ToolV3T<false>;
using ToolV3BE = ToolV3T<true>;
check_struct_size(ToolV3, 0x14);
check_struct_size(ToolV3BE, 0x14);
struct MagFeedResult {
int8_t def = 0;
@@ -350,31 +367,43 @@ public:
int8_t iq = 0;
int8_t synchro = 0;
parray<uint8_t, 2> unused;
} __attribute__((packed));
} __packed_ws__(MagFeedResult, 8);
using MagFeedResultsList = parray<MagFeedResult, 11>;
template <bool IsBigEndian>
struct MagFeedResultsListOffsets {
struct MagFeedResultsListOffsetsT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
parray<U32T, 8> offsets; // Offsets of MagFeedResultsList objects
} __attribute__((packed));
} __packed__;
using MagFeedResultsListOffsets = MagFeedResultsListOffsetsT<false>;
using MagFeedResultsListOffsetsBE = MagFeedResultsListOffsetsT<true>;
check_struct_size(MagFeedResultsListOffsets, 0x20);
check_struct_size(MagFeedResultsListOffsetsBE, 0x20);
template <bool IsBigEndian>
struct Special {
struct SpecialT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
U16T type = 0xFFFF;
U16T amount = 0;
} __attribute__((packed));
} __packed__;
using Special = SpecialT<false>;
using SpecialBE = SpecialT<true>;
check_struct_size(Special, 4);
check_struct_size(SpecialBE, 4);
template <bool IsBigEndian>
struct StatBoost {
struct StatBoostT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
uint8_t stat1 = 0;
uint8_t stat2 = 0;
U16T amount1 = 0;
U16T amount2 = 0;
} __attribute__((packed));
} __packed__;
using StatBoost = StatBoostT<false>;
using StatBoostBE = StatBoostT<true>;
check_struct_size(StatBoost, 6);
check_struct_size(StatBoostBE, 6);
// Indexed as [tech_num][char_class]
using MaxTechniqueLevels = parray<parray<uint8_t, 12>, 19>;
@@ -388,10 +417,10 @@ public:
uint8_t level = 0;
uint8_t char_class = 0;
parray<uint8_t, 3> unused;
} __attribute__((packed));
} __packed_ws__(ItemCombination, 0x10);
template <bool IsBigEndian>
struct TechniqueBoost {
struct TechniqueBoostT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using FloatT = typename std::conditional<IsBigEndian, be_float, le_float>::type;
U32T tech1 = 0;
@@ -400,26 +429,34 @@ public:
FloatT boost2 = 0.0f;
U32T tech3 = 0;
FloatT boost3 = 0.0f;
} __attribute__((packed));
} __packed__;
using TechniqueBoost = TechniqueBoostT<false>;
using TechniqueBoostBE = TechniqueBoostT<true>;
check_struct_size(TechniqueBoost, 0x18);
check_struct_size(TechniqueBoostBE, 0x18);
struct EventItem {
parray<uint8_t, 3> item;
uint8_t probability = 0;
} __attribute__((packed));
} __packed_ws__(EventItem, 4);
struct UnsealableItem {
parray<uint8_t, 3> item;
uint8_t unused = 0;
} __attribute__((packed));
} __packed_ws__(UnsealableItem, 4);
template <bool IsBigEndian>
struct NonWeaponSaleDivisors {
struct NonWeaponSaleDivisorsT {
using FloatT = typename std::conditional<IsBigEndian, be_float, le_float>::type;
FloatT armor_divisor = 0.0f;
FloatT shield_divisor = 0.0f;
FloatT unit_divisor = 0.0f;
FloatT mag_divisor = 0.0f;
} __attribute__((packed));
} __packed__;
using NonWeaponSaleDivisors = NonWeaponSaleDivisorsT<false>;
using NonWeaponSaleDivisorsBE = NonWeaponSaleDivisorsT<true>;
check_struct_size(NonWeaponSaleDivisors, 0x10);
check_struct_size(NonWeaponSaleDivisorsBE, 0x10);
ItemParameterTable(std::shared_ptr<const std::string> data, Version version);
~ItemParameterTable() = default;
@@ -443,7 +480,7 @@ public:
const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const;
uint8_t get_item_stars(uint32_t id) const;
uint8_t get_special_stars(uint8_t special) const;
const Special<false>& get_special(uint8_t special) const;
const Special& get_special(uint8_t special) const;
uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const;
uint8_t get_weapon_v1_replacement(uint8_t data1_1) const;
@@ -493,7 +530,7 @@ private:
/* 44 / 0668 / 0668 */ le_uint32_t unknown_a2;
/* 48 / 030C / 030C */ le_uint32_t unknown_a3;
/* 4C / 2CE4 / 2D78 */ le_uint32_t unknown_a4;
} __attribute__((packed));
} __packed_ws__(TableOffsetsDCProtos, 0x50);
struct TableOffsetsV1V2 {
// TODO: Is weapon count 0x89 or 0x8A? It could be that the last entry in
@@ -516,7 +553,7 @@ private:
/* 38 / 2C12 / 4540 */ le_uint32_t special_data_table; // -> [Special](0x29)
/* 3C / 2CB8 / 58DC */ le_uint32_t stat_boost_table; // -> [StatBoost]
/* 40 / 3198 / 5704 */ le_uint32_t shield_effect_table; // -> [8-byte structs]
} __attribute__((packed));
} __packed_ws__(TableOffsetsV1V2, 0x44);
struct TableOffsetsGCNTE {
/* 00 / 6F0C */ be_uint32_t weapon_table; // -> [{count, offset -> [WeaponV3/WeaponV4]}](0xED)
@@ -539,10 +576,10 @@ private:
/* 44 / 737C */ be_uint32_t combination_table; // -> {count, offset -> [ItemCombination]}
/* 48 / 68B0 */ be_uint32_t unknown_a1;
/* 4C / 6B1C */ be_uint32_t tech_boost_table; // -> [TechniqueBoost] (always 0x2C of them? from counts struct?)
} __attribute__((packed));
} __packed_ws__(TableOffsetsGCNTE, 0x50);
template <bool IsBigEndian>
struct TableOffsetsV3V4 {
struct TableOffsetsV3V4T {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* ## / GC / BB */
/* 00 / F078 / 14884 */ U32T weapon_table; // -> [{count, offset -> [WeaponV3/WeaponV4]}](0xED)
@@ -568,7 +605,11 @@ private:
/* 50 / F5F0 / 15014 */ U32T unwrap_table; // -> {count, offset -> [{count, offset -> [EventItem]}]}
/* 54 / F5F8 / 1501C */ U32T unsealable_table; // -> {count, offset -> [UnsealableItem]}
/* 58 / F600 / 15024 */ U32T ranged_special_table; // -> {count, offset -> [4-byte structs]}
} __attribute__((packed));
} __packed__;
using TableOffsetsV3V4 = TableOffsetsV3V4T<false>;
using TableOffsetsV3V4BE = TableOffsetsV3V4T<true>;
check_struct_size(TableOffsetsV3V4, 0x5C);
check_struct_size(TableOffsetsV3V4BE, 0x5C);
Version version;
std::shared_ptr<const std::string> data;
@@ -576,9 +617,9 @@ private:
const TableOffsetsDCProtos* offsets_dc_protos;
const TableOffsetsV1V2* offsets_v1_v2;
const TableOffsetsGCNTE* offsets_gc_nte;
const TableOffsetsV3V4<false>* offsets_v3_le;
const TableOffsetsV3V4<true>* offsets_v3_be;
const TableOffsetsV3V4<false>* offsets_v4;
const TableOffsetsV3V4* offsets_v3_le;
const TableOffsetsV3V4BE* offsets_v3_be;
const TableOffsetsV3V4* offsets_v4;
// These are unused if offsets_v4 is not null (in that case, we just return
// references pointing inside the data string)
@@ -588,13 +629,13 @@ private:
mutable std::vector<UnitV4> parsed_units;
mutable std::vector<MagV4> parsed_mags;
mutable std::unordered_map<uint16_t, ToolV4> parsed_tools;
mutable std::vector<Special<false>> parsed_specials;
mutable std::vector<Special> parsed_specials;
// Key is used_item. We can't index on (used_item, equipped_item) because
// equipped_item may contain wildcards, and the matching order matters.
mutable std::map<uint32_t, std::vector<ItemCombination>> item_combination_index;
template <typename ToolT, bool IsBigEndian>
template <typename ToolDefT, bool IsBigEndian>
std::pair<uint8_t, uint8_t> find_tool_by_id_t(uint32_t tool_table_offset, uint32_t id) const;
template <bool IsBigEndian, typename OffsetsT>
float get_sale_divisor_t(const OffsetsT* offsets, uint8_t data1_0, uint8_t data1_1) const;
@@ -614,11 +655,11 @@ public:
/* 0C / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[num_mags]
/* 10 / 05AC */ le_uint32_t unknown_a5; // -> (float)[0x48]
/* 14 / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[num_mags]
} __attribute__((packed));
} __packed_ws__(TableOffsets, 0x18);
struct EvolutionNumberTable {
parray<uint8_t, 0x53> values;
} __attribute__((packed));
} __packed_ws__(EvolutionNumberTable, 0x53);
MagEvolutionTable(std::shared_ptr<const std::string> data);
~MagEvolutionTable() = default;
+16 -16
View File
@@ -9,26 +9,26 @@
using namespace std;
void PlayerStats::reset_to_base(uint8_t char_class, shared_ptr<const LevelTable> level_table) {
this->level = 0;
this->experience = 0;
this->char_stats = level_table->base_stats_for_class(char_class);
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
stats.level = 0;
stats.experience = 0;
stats.char_stats = this->base_stats_for_class(char_class);
}
void PlayerStats::advance_to_level(uint8_t char_class, uint32_t level, shared_ptr<const LevelTable> level_table) {
for (; this->level < level; this->level++) {
const auto& level_stats = level_table->stats_delta_for_level(char_class, this->level + 1);
void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const {
for (; stats.level < level; stats.level++) {
const auto& level_stats = this->stats_delta_for_level(char_class, stats.level + 1);
// The original code clamps the resulting stat values to [0, max_stat]; we
// don't have max_stat handy so we just allow them to be unbounded
this->char_stats.atp += level_stats.atp;
this->char_stats.mst += level_stats.mst;
this->char_stats.evp += level_stats.evp;
this->char_stats.hp += level_stats.hp;
this->char_stats.dfp += level_stats.dfp;
this->char_stats.ata += level_stats.ata;
stats.char_stats.atp += level_stats.atp;
stats.char_stats.mst += level_stats.mst;
stats.char_stats.evp += level_stats.evp;
stats.char_stats.hp += level_stats.hp;
stats.char_stats.dfp += level_stats.dfp;
stats.char_stats.ata += level_stats.ata;
// Note: It is not a bug that lck is ignored here; the original code
// ignores it too.
this->experience = level_stats.experience;
stats.experience = level_stats.experience;
}
}
@@ -52,7 +52,7 @@ LevelTableV2::LevelTableV2(const string& data, bool compressed) {
le_uint32_t unknown_a10; // -> u32[3] -> (0x10-byte struct)[0x0C]
le_uint32_t unknown_a11; // -> u32[3] -> (0x30-bytes)
le_uint32_t unknown_a12; // -> u32[3] -> (0x14-byte struct)[0x0F]
} __attribute__((packed));
} __packed_ws__(Offsets, 0x40);
StringReader r;
string decompressed_data;
@@ -158,7 +158,7 @@ LevelTableV4::LevelTableV4(const string& data, bool compressed) {
struct Offsets {
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
} __attribute__((packed));
} __packed_ws__(Offsets, 8);
StringReader r;
string decompressed_data;
+71 -30
View File
@@ -7,35 +7,74 @@
#include <phosg/Encoding.hh>
#include <string>
#include "Text.hh"
class LevelTable;
struct CharacterStats {
/* 00 */ le_uint16_t atp = 0;
/* 02 */ le_uint16_t mst = 0;
/* 04 */ le_uint16_t evp = 0;
/* 06 */ le_uint16_t hp = 0;
/* 08 */ le_uint16_t dfp = 0;
/* 0A */ le_uint16_t ata = 0;
/* 0C */ le_uint16_t lck = 0;
template <bool IsBigEndian>
struct CharacterStatsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 00 */ U16T atp = 0;
/* 02 */ U16T mst = 0;
/* 04 */ U16T evp = 0;
/* 06 */ U16T hp = 0;
/* 08 */ U16T dfp = 0;
/* 0A */ U16T ata = 0;
/* 0C */ U16T lck = 0;
/* 0E */
} __attribute__((packed));
struct PlayerStats {
/* 00 */ CharacterStats char_stats;
/* 0E */ le_uint16_t esp = 0;
/* 10 */ le_float height = 0.0;
/* 14 */ le_float unknown_a3 = 0.0;
/* 18 */ le_uint32_t level = 0;
/* 1C */ le_uint32_t experience = 0;
/* 20 */ le_uint32_t meseta = 0;
/* 24 */
void reset_to_base(uint8_t char_class, std::shared_ptr<const LevelTable> level_table);
void advance_to_level(uint8_t char_class, uint32_t level, std::shared_ptr<const LevelTable> level_table);
} __attribute__((packed));
operator CharacterStatsT<!IsBigEndian>() const {
CharacterStatsT<!IsBigEndian> ret;
ret.atp = this->atp.load();
ret.mst = this->mst.load();
ret.evp = this->evp.load();
ret.hp = this->hp.load();
ret.dfp = this->dfp.load();
ret.ata = this->ata.load();
ret.lck = this->lck.load();
return ret;
}
} __packed__;
using CharacterStats = CharacterStatsT<false>;
using CharacterStatsBE = CharacterStatsT<true>;
check_struct_size(CharacterStats, 0x0E);
check_struct_size(CharacterStatsBE, 0x0E);
template <bool IsBigEndian>
struct LevelStatsDeltaBase {
struct PlayerStatsT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type;
/* 00 */ CharacterStatsT<IsBigEndian> char_stats;
/* 0E */ U16T esp = 0;
/* 10 */ F32T height = 0.0;
/* 14 */ F32T unknown_a3 = 0.0;
/* 18 */ U32T level = 0;
/* 1C */ U32T experience = 0;
/* 20 */ U32T meseta = 0;
/* 24 */
operator PlayerStatsT<!IsBigEndian>() const {
PlayerStatsT<!IsBigEndian> ret;
ret.char_stats = this->char_stats;
ret.esp = this->esp.load();
ret.height = this->height.load();
ret.unknown_a3 = this->unknown_a3.load();
ret.level = this->level.load();
ret.experience = this->experience.load();
ret.meseta = this->meseta.load();
return ret;
}
} __packed__;
using PlayerStats = PlayerStatsT<false>;
using PlayerStatsBE = PlayerStatsT<true>;
check_struct_size(PlayerStats, 0x24);
check_struct_size(PlayerStatsBE, 0x24);
template <bool IsBigEndian>
struct LevelStatsDeltaT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ uint8_t atp;
@@ -58,12 +97,11 @@ struct LevelStatsDeltaBase {
ps.mst += this->mst;
ps.lck += this->lck;
}
} __attribute__((packed));
struct LevelStatsDelta : LevelStatsDeltaBase<false> {
} __attribute__((packed));
struct LevelStatsDeltaBE : LevelStatsDeltaBase<true> {
} __attribute__((packed));
} __packed__;
using LevelStatsDelta = LevelStatsDeltaT<false>;
using LevelStatsDeltaBE = LevelStatsDeltaT<true>;
check_struct_size(LevelStatsDelta, 0x0C);
check_struct_size(LevelStatsDeltaBE, 0x0C);
class LevelTable {
// This is the base class for all the LevelTable implementations. The public
@@ -76,6 +114,9 @@ public:
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0;
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0;
void reset_to_base(PlayerStats& stats, uint8_t char_class) const;
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
protected:
LevelTable() = default;
};
@@ -89,7 +130,7 @@ public:
/* 14 */ le_float unknown_a3 = 0.0;
/* 18 */ le_uint32_t level = 0;
/* 1C */
} __attribute__((packed));
} __packed_ws__(Level100Entry, 0x1C);
LevelTableV2(const std::string& data, bool compressed);
virtual ~LevelTableV2() = default;
+1 -1
View File
@@ -347,7 +347,7 @@ DiskLicenseIndex::DiskLicenseIndex() {
pstring<TextEncoding::ASCII, 0x0C> gc_password; // GC password
uint32_t privileges; // privilege level
uint64_t ban_end_time; // end time of ban (zero = not banned)
} __attribute__((packed));
} __packed_ws__(BinaryLicense, 0x54);
if (!isdir("system/licenses")) {
mkdir("system/licenses", 0755);
+1 -1
View File
@@ -650,7 +650,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
this->battle_record->add_player(
lobby_data,
p->inventory,
p->disp.to_dcpcv3(c->language(), c->language()),
p->disp.to_dcpcv3<false>(c->language(), c->language()),
c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0);
}
+2 -1
View File
@@ -1940,7 +1940,8 @@ void SetDataTable::load_table_t(const string& data) {
U32T unknown_a6; // == 0
U32T unknown_a7; // == 0
U32T unknown_a8; // == 0
} __attribute__((packed));
} __packed_ws__(Footer, 0x20);
if (r.size() < sizeof(Footer)) {
throw runtime_error("set data table is too small");
}
+12 -12
View File
@@ -34,7 +34,7 @@ struct Map {
inline Type type() const {
return static_cast<Type>(this->le_type.load());
}
} __attribute__((packed));
} __packed_ws__(SectionHeader, 0x10);
struct ObjectEntry { // Section type 1 (OBJECTS)
/* 00 */ le_uint16_t base_type;
@@ -61,7 +61,7 @@ struct Map {
/* 44 */
std::string str() const;
} __attribute__((packed));
} __packed_ws__(ObjectEntry, 0x44);
struct EnemyEntry { // Section type 2 (ENEMIES)
/* 00 */ le_uint16_t base_type;
@@ -91,7 +91,7 @@ struct Map {
/* 48 */
std::string str() const;
} __attribute__((packed));
} __packed_ws__(EnemyEntry, 0x48);
struct EventsSectionHeader { // Section type 3 (WAVE_EVENTS)
/* 00 */ le_uint32_t action_stream_offset;
@@ -99,7 +99,7 @@ struct Map {
/* 08 */ le_uint32_t entry_count;
/* 0C */ be_uint32_t format; // 0 or 'evt2'
/* 10 */
} __attribute__((packed));
} __packed_ws__(EventsSectionHeader, 0x10);
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
/* 00 */ le_uint32_t event_id;
@@ -114,7 +114,7 @@ struct Map {
/* 0C */ le_uint32_t delay;
/* 10 */ le_uint32_t action_stream_offset;
/* 14 */
} __attribute__((packed));
} __packed_ws__(Event1Entry, 0x14);
struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2'
/* 00 */ le_uint32_t event_id;
@@ -129,21 +129,21 @@ struct Map {
/* 12 */ le_uint16_t max_waves;
/* 14 */ le_uint32_t action_stream_offset;
/* 18 */
} __attribute__((packed));
} __packed_ws__(Event2Entry, 0x18);
struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_uint32_t section_table_offset; // Offset to RandomEnemyLocationSegment structs, from start of this struct
/* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocationEntry structs, from start of this struct
/* 08 */ le_uint32_t num_sections;
/* 0C */
} __attribute__((packed));
} __packed_ws__(RandomEnemyLocationsHeader, 0x0C);
struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_uint16_t section;
/* 02 */ le_uint16_t count;
/* 04 */ le_uint32_t offset;
/* 08 */
} __attribute__((packed));
} __packed_ws__(RandomEnemyLocationSection, 8);
struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS)
/* 00 */ le_float x;
@@ -155,7 +155,7 @@ struct Map {
/* 18 */ uint16_t unknown_a9;
/* 1A */ uint16_t unknown_a10;
/* 1C */
} __attribute__((packed));
} __packed_ws__(RandomEnemyLocationEntry, 0x1C);
struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
/* 00 */ le_uint32_t entries_offset; // Offset to RandomEnemyDefinition structs, from start of this struct
@@ -163,7 +163,7 @@ struct Map {
/* 08 */ le_uint32_t entry_count;
/* 0C */ le_uint32_t weight_entry_count;
/* 10 */
} __attribute__((packed));
} __packed_ws__(RandomEnemyDefinitionsHeader, 0x10);
struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
// All fields through entry_num map to the corresponding fields in
@@ -179,7 +179,7 @@ struct Map {
/* 1C */ le_uint16_t min_children;
/* 1E */ le_uint16_t max_children;
/* 20 */
} __attribute__((packed));
} __packed_ws__(RandomEnemyDefinition, 0x20);
struct RandomEnemyWeight { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
/* 00 */ uint8_t base_type_index;
@@ -187,7 +187,7 @@ struct Map {
/* 02 */ uint8_t weight;
/* 03 */ uint8_t unknown_a4;
/* 04 */
} __attribute__((packed));
} __packed_ws__(RandomEnemyWeight, 4);
struct RareEnemyRates {
uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE
+30 -20
View File
@@ -114,13 +114,13 @@ public:
parray<le_uint32_t, 0x12> as32;
InitialKeys() : as32() {}
InitialKeys(const InitialKeys& other) : as32(other.as32) {}
} __attribute__((packed));
} __packed_ws__(InitialKeys, 0x48);
union PrivateKeys {
parray<uint8_t, 0x1000> as8;
parray<le_uint32_t, 0x400> as32;
PrivateKeys() : as32() {}
PrivateKeys(const PrivateKeys& other) : as32(other.as32) {}
} __attribute__((packed));
} __packed_ws__(PrivateKeys, 0x1000);
InitialKeys initial_keys;
PrivateKeys private_keys;
// This field only really needs to be one byte, but annoyingly, some
@@ -129,7 +129,7 @@ public:
// this structure's size from not matching the .nsk files' sizes, we use
// an unnecessarily large size for this field.
le_uint64_t subtype;
} __attribute__((packed));
} __packed_ws__(KeyFile, 0x1050);
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
@@ -254,39 +254,49 @@ uint32_t encrypt_challenge_time(uint16_t value);
uint16_t decrypt_challenge_time(uint32_t value);
template <bool IsBigEndian>
class ChallengeTime {
class ChallengeTimeT {
private:
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T value;
public:
ChallengeTime() = default;
ChallengeTime(uint16_t v) {
this->store(v);
ChallengeTimeT() = default;
ChallengeTimeT(uint16_t v) {
this->encode(v);
}
ChallengeTime(const ChallengeTime& other) = default;
ChallengeTime(ChallengeTime&& other) = default;
ChallengeTime& operator=(const ChallengeTime& other) = default;
ChallengeTime& operator=(ChallengeTime&& other) = default;
ChallengeTimeT(const ChallengeTimeT& other) = default;
ChallengeTimeT(ChallengeTimeT&& other) = default;
ChallengeTimeT& operator=(const ChallengeTimeT& other) = default;
ChallengeTimeT& operator=(ChallengeTimeT&& other) = default;
bool has_value() const {
return this->value != 0;
}
uint16_t load() const {
uint32_t load_raw() const {
return this->value;
}
void store_raw(uint32_t value) {
this->value = value;
}
uint16_t decode() const {
return decrypt_challenge_time(this->value);
}
operator uint16_t() const {
return this->load();
}
void store(uint16_t v) {
void encode(uint16_t v) {
this->value = ((v == 0) || (v == 0xFFFF)) ? 0 : encrypt_challenge_time(v);
}
ChallengeTime& operator=(uint16_t v) {
this->store(v);
return *this;
operator ChallengeTimeT<!IsBigEndian>() const {
ChallengeTimeT<!IsBigEndian> ret;
ret.store_raw(this->value);
return ret;
}
} __attribute__((packed));
} __packed__;
using ChallengeTime = ChallengeTimeT<false>;
using ChallengeTimeBE = ChallengeTimeT<true>;
check_struct_size(ChallengeTime, 4);
check_struct_size(ChallengeTimeBE, 4);
std::string decrypt_v2_registry_value(const void* data, size_t size);
+2 -2
View File
@@ -11,7 +11,7 @@ struct TObjectVTable {
be_uint32_t update;
be_uint32_t render;
be_uint32_t render_shadow;
} __attribute__((packed));
} __packed_ws__(TObjectVTable, 0x18);
struct TObject {
be_uint32_t type_name_addr;
@@ -22,7 +22,7 @@ struct TObject {
be_uint32_t parent_addr;
be_uint32_t children_head_addr;
be_uint32_t vtable_addr;
} __attribute__((packed));
} __packed_ws__(TObject, 0x1C);
PSOGCObjectGraph::PSOGCObjectGraph(
const string& memory_data, uint32_t root_address) {
+4 -4
View File
@@ -13,19 +13,19 @@ struct PSOCommandHeaderPC {
le_uint16_t size;
uint8_t command;
uint8_t flag;
} __attribute__((packed));
} __packed_ws__(PSOCommandHeaderPC, 4);
struct PSOCommandHeaderDCV3 {
uint8_t command;
uint8_t flag;
le_uint16_t size;
} __attribute__((packed));
} __packed_ws__(PSOCommandHeaderDCV3, 4);
struct PSOCommandHeaderBB {
le_uint16_t size;
le_uint16_t command;
le_uint32_t flag;
} __attribute__((packed));
} __packed_ws__(PSOCommandHeaderBB, 8);
union PSOCommandHeader {
PSOCommandHeaderDCV3 dc;
@@ -45,7 +45,7 @@ union PSOCommandHeader {
}
PSOCommandHeader();
} __attribute__((packed));
} __packed_ws__(PSOCommandHeader, 8);
// This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type
+407
View File
@@ -0,0 +1,407 @@
#pragma once
#include <inttypes.h>
#include <stddef.h>
#include <array>
#include <phosg/Encoding.hh>
#include <phosg/JSON.hh>
#include <string>
#include <utility>
#include <vector>
#include "ChoiceSearch.hh"
#include "FileContentsCache.hh"
#include "ItemData.hh"
#include "PSOEncryption.hh"
#include "Text.hh"
#include "Version.hh"
class Client;
class ItemParameterTable;
// PSO V2 stored some extra data in the character structs in a format that I'm
// sure Sega thought was very clever for backward compatibility, but for us is
// just plain annoying. Specifically, they used the third and fourth bytes of
// the InventoryItem struct to store some things not present in V1. The game
// stores arrays of bytes striped across these structures. In newserv, we call
// those fields extension_data. They contain:
// items[0].extension_data1 through items[19].extension_data1:
// Extended technique levels. The values in the technique_levels_v1 array
// only go up to 14 (tech level 15); if the player has a technique above
// level 15, the corresponding extension_data1 field holds the remaining
// levels (so a level 20 tech would have 14 in technique_levels_v1 and 5
// in the corresponding item's extension_data1 field).
// items[0].extension_data2 through items[3].extension_data2:
// The flags field from the PSOGCCharacterFile::Character struct; see
// SaveFileFormats.hh for details.
// items[4].extension_data2 through items[7].extension_data2:
// The timestamp when the character was last saved, in seconds since
// January 1, 2000. Stored little-endian, so items[4] contains the LSB.
// items[8].extension_data2 through items[12].extension_data2:
// Number of power materials, mind materials, evade materials, def
// materials, and luck materials (respectively) used by the player.
// items[13].extension_data2 through items[15].extension_data2:
// Unknown. These are not an array, but do appear to be related.
template <bool IsBigEndian>
struct PlayerInventoryItemT {
/* 00 */ uint8_t present = 0;
/* 01 */ uint8_t unknown_a1 = 0;
// See note above about these fields
/* 02 */ uint8_t extension_data1 = 0;
/* 03 */ uint8_t extension_data2 = 0;
/* 04 */ le_uint32_t flags = 0; // 8 = equipped
/* 08 */ ItemData data;
/* 1C */
PlayerInventoryItemT() = default;
PlayerInventoryItemT(const ItemData& item, bool equipped)
: present(1),
unknown_a1(0),
extension_data1(0),
extension_data2(0),
flags(equipped ? 8 : 0),
data(item) {}
operator PlayerInventoryItemT<!IsBigEndian>() const {
PlayerInventoryItemT<!IsBigEndian> ret;
ret.present = this->present;
ret.unknown_a1 = this->unknown_a1;
ret.extension_data1 = this->extension_data1;
ret.extension_data2 = this->extension_data2;
ret.flags = this->flags.load();
ret.data = this->data;
return ret;
}
} __packed__;
using PlayerInventoryItem = PlayerInventoryItemT<false>;
using PlayerInventoryItemBE = PlayerInventoryItemT<true>;
check_struct_size(PlayerInventoryItem, 0x1C);
check_struct_size(PlayerInventoryItemBE, 0x1C);
template <bool IsBigEndian>
struct PlayerBankItemT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
/* 00 */ ItemData data;
/* 14 */ U16T amount = 0;
/* 16 */ U16T present = 0;
/* 18 */
inline bool operator<(const PlayerBankItemT<IsBigEndian>& other) const {
return this->data < other.data;
}
operator PlayerBankItemT<!IsBigEndian>() const {
PlayerBankItemT<!IsBigEndian> ret;
ret.data = this->data;
ret.amount = this->amount.load();
ret.present = this->present.load();
return ret;
}
} __packed__;
using PlayerBankItem = PlayerBankItemT<false>;
using PlayerBankItemBE = PlayerBankItemT<true>;
check_struct_size(PlayerBankItem, 0x18);
check_struct_size(PlayerBankItemBE, 0x18);
template <bool IsBigEndian>
struct PlayerInventoryT {
/* 0000 */ uint8_t num_items = 0;
/* 0001 */ uint8_t hp_from_materials = 0;
/* 0002 */ uint8_t tp_from_materials = 0;
/* 0003 */ uint8_t language = 0;
/* 0004 */ parray<PlayerInventoryItemT<IsBigEndian>, 30> items;
/* 034C */
size_t find_item(uint32_t item_id) const {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.id == item_id) {
return x;
}
}
throw std::out_of_range("item not present");
}
size_t find_item_by_primary_identifier(uint32_t primary_identifier) const {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.primary_identifier() == primary_identifier) {
return x;
}
}
throw std::out_of_range("item not present");
}
size_t find_equipped_item(EquipSlot slot) const {
ssize_t ret = -1;
for (size_t y = 0; y < this->num_items; y++) {
const auto& i = this->items[y];
if (!(i.flags & 0x00000008)) {
continue;
}
if (!i.data.can_be_equipped_in_slot(slot)) {
continue;
}
// Units can be equipped in multiple slots, so the currently-equipped slot
// is stored in the item data itself.
if (((slot == EquipSlot::UNIT_1) && (i.data.data1[4] != 0x00)) ||
((slot == EquipSlot::UNIT_2) && (i.data.data1[4] != 0x01)) ||
((slot == EquipSlot::UNIT_3) && (i.data.data1[4] != 0x02)) ||
((slot == EquipSlot::UNIT_4) && (i.data.data1[4] != 0x03))) {
continue;
}
if (ret < 0) {
ret = y;
} else {
throw std::runtime_error("multiple items are equipped in the same slot");
}
}
if (ret < 0) {
throw std::out_of_range("no item is equipped in this slot");
}
return ret;
}
bool has_equipped_item(EquipSlot slot) const {
try {
this->find_equipped_item(slot);
return true;
} catch (const std::out_of_range&) {
return false;
}
}
void equip_item_id(uint32_t item_id, EquipSlot slot, bool allow_overwrite) {
this->equip_item_index(this->find_item(item_id), slot, allow_overwrite);
}
void equip_item_index(size_t index, EquipSlot slot, bool allow_overwrite) {
auto& item = this->items[index];
if ((slot == EquipSlot::UNKNOWN) || !item.data.can_be_equipped_in_slot(slot)) {
slot = item.data.default_equip_slot();
}
if (this->has_equipped_item(slot)) {
if (allow_overwrite) {
this->unequip_item_slot(slot);
} else {
throw std::runtime_error("equip slot is already in use");
}
}
item.flags |= 0x00000008;
// Units store which slot they're equipped in within the item data itself
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) {
item.data.data1[4] = static_cast<uint8_t>(slot) - 9;
}
}
void unequip_item_id(uint32_t item_id) {
this->unequip_item_index(this->find_item(item_id));
}
void unequip_item_slot(EquipSlot slot) {
this->unequip_item_index(this->find_equipped_item(slot));
}
void unequip_item_index(size_t index) {
auto& item = this->items[index];
item.flags &= (~0x00000008);
// Units store which slot they're equipped in within the item data itself
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) {
item.data.data1[4] = 0x00;
}
// If the item is an armor, remove all units too
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x01)) {
for (size_t z = 0; z < 30; z++) {
auto& unit = this->items[z];
if ((unit.data.data1[0] == 0x01) && (unit.data.data1[1] == 0x03)) {
unit.flags &= (~0x00000008);
unit.data.data1[4] = 0x00;
}
}
}
}
size_t remove_all_items_of_type(uint8_t data1_0, int16_t data1_1 = -1) {
size_t write_offset = 0;
for (size_t read_offset = 0; read_offset < this->num_items; read_offset++) {
bool should_delete = ((this->items[read_offset].data.data1[0] == data1_0) &&
((data1_1 < 0) || (this->items[read_offset].data.data1[1] == static_cast<uint8_t>(data1_1))));
if (!should_delete) {
if (read_offset != write_offset) {
this->items[write_offset].present = this->items[read_offset].present;
this->items[write_offset].unknown_a1 = this->items[read_offset].unknown_a1;
this->items[write_offset].flags = this->items[read_offset].flags;
this->items[write_offset].data = this->items[read_offset].data;
}
write_offset++;
}
}
size_t ret = this->num_items - write_offset;
this->num_items = write_offset;
return ret;
}
void decode_from_client(Version v) {
for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.decode_for_version(v);
}
}
void encode_for_client(Version v, std::shared_ptr<const ItemParameterTable> item_parameter_table) {
if (v == Version::DC_NTE) {
// DC NTE has the item count as a 32-bit value here, whereas every other
// version uses a single byte. To stop DC NTE from crashing by trying to
// construct far more than 30 TItem objects, we clear the fields DC NTE
// doesn't know about. Note that the 11/2000 prototype does not have this
// issue - its inventory format matches the rest of the versions.
this->hp_from_materials = 0;
this->tp_from_materials = 0;
this->language = 0;
} else if ((v != Version::PC_NTE) && (v != Version::PC_V2)) {
if (this->language > 4) {
this->language = 0;
}
} else {
if (this->language > 7) {
this->language = 0;
}
}
// For pre-V2 clients, use the V2 parameter table, since the V1 table
// doesn't have correct encodings for backward-compatible V2 items.
for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.encode_for_version(v, item_parameter_table);
}
}
operator PlayerInventoryT<!IsBigEndian>() const {
PlayerInventoryT<!IsBigEndian> ret;
ret.num_items = this->num_items;
ret.hp_from_materials = this->hp_from_materials;
ret.tp_from_materials = this->tp_from_materials;
ret.language = this->language;
ret.items = this->items;
return ret;
}
} __packed__;
using PlayerInventory = PlayerInventoryT<false>;
using PlayerInventoryBE = PlayerInventoryT<true>;
check_struct_size(PlayerInventory, 0x34C);
check_struct_size(PlayerInventoryBE, 0x34C);
template <bool IsBigEndian>
struct PlayerBankT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 0000 */ U32T num_items = 0;
/* 0004 */ U32T meseta = 0;
/* 0008 */ parray<PlayerBankItemT<IsBigEndian>, 200> items;
/* 12C8 */
void add_item(const ItemData& item, const ItemData::StackLimits& limits) {
uint32_t primary_identifier = item.primary_identifier();
if (primary_identifier == 0x04000000) {
this->meseta += item.data2d;
if (this->meseta > 999999) {
this->meseta = 999999;
}
return;
}
size_t combine_max = item.max_stack_size(limits);
if (combine_max > 1) {
size_t y;
for (y = 0; y < this->num_items; y++) {
if (this->items[y].data.primary_identifier() == primary_identifier) {
break;
}
}
if (y < this->num_items) {
uint8_t new_count = this->items[y].data.data1[5] + item.data1[5];
if (new_count > combine_max) {
throw std::runtime_error("stack size would exceed limit");
}
this->items[y].data.data1[5] = new_count;
this->items[y].amount = new_count;
return;
}
}
if (this->num_items >= 200) {
throw std::runtime_error("no free space in bank");
}
auto& last_item = this->items[this->num_items];
last_item.data = item;
last_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1;
last_item.present = 1;
this->num_items++;
}
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits) {
size_t index = this->find_item(item_id);
auto& bank_item = this->items[index];
ItemData ret;
if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) {
ret = bank_item.data;
ret.data1[5] = amount;
bank_item.data.data1[5] -= amount;
bank_item.amount -= amount;
return ret;
}
ret = bank_item.data;
this->num_items--;
for (size_t x = index; x < this->num_items; x++) {
this->items[x] = this->items[x + 1];
}
auto& last_item = this->items[this->num_items];
last_item.amount = 0;
last_item.present = 0;
last_item.data.clear();
return ret;
}
size_t find_item(uint32_t item_id) {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.id == item_id) {
return x;
}
}
throw std::out_of_range("item not present");
}
void sort() {
std::sort(this->items.data(), this->items.data() + this->num_items);
}
void assign_ids(uint32_t base_id) {
for (size_t z = 0; z < this->num_items; z++) {
this->items[z].data.id = base_id + z;
}
}
operator PlayerBankT<!IsBigEndian>() const {
PlayerBankT<!IsBigEndian> ret;
ret.num_items = this->num_items.load();
ret.meseta = this->meseta.load();
for (size_t z = 0; z < this->items.size(); z++) {
ret.items[z] = this->items[z];
}
return ret;
}
} __packed__;
using PlayerBank = PlayerBankT<false>;
using PlayerBankBE = PlayerBankT<true>;
check_struct_size(PlayerBank, 0x12C8);
check_struct_size(PlayerBankBE, 0x12C8);
+106 -499
View File
@@ -22,184 +22,6 @@
using namespace std;
PlayerInventoryItem::PlayerInventoryItem(const ItemData& item, bool equipped)
: present(1),
unknown_a1(0),
extension_data1(0),
extension_data2(0),
flags(equipped ? 8 : 0),
data(item) {}
uint32_t PlayerVisualConfig::compute_name_color_checksum(uint32_t name_color) {
uint8_t x = (random_object<uint32_t>() % 0xFF) + 1;
uint8_t y = (random_object<uint32_t>() % 0xFF) + 1;
// name_color (ARGB) = ABCDEFGHabcdefghIJKLMNOPijklmnop
// name_color_checksum = 000000000ijklmabcdeIJKLM00000000 ^ xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy
uint32_t xbrgx95558 = ((name_color << 15) & 0x007C0000) | ((name_color >> 6) & 0x0003E000) | ((name_color >> 3) & 0x00001F00);
uint32_t mask = (x << 24) | (y << 16) | (x << 8) | y;
return xbrgx95558 ^ mask;
}
void PlayerVisualConfig::compute_name_color_checksum() {
this->name_color_checksum = this->compute_name_color_checksum(this->name_color);
}
void PlayerVisualConfig::enforce_lobby_join_limits_for_version(Version v) {
struct ClassMaxes {
uint16_t costume;
uint16_t skin;
uint16_t face;
uint16_t head;
uint16_t hair;
};
static constexpr ClassMaxes v1_v2_class_maxes[14] = {
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
};
static constexpr ClassMaxes v3_v4_class_maxes[19] = {
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0001},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0001},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000}};
const ClassMaxes* maxes;
if (v == Version::GC_NTE) {
// GC NTE has HUcaseal, FOmar, and RAmarl, but missing others
if (this->char_class >= 12) {
this->char_class = 0; // Invalid classes -> HUmar
}
// GC NTE is basically v2, but uses v3 maxes
this->version = min<uint8_t>(this->version, 2);
maxes = &v3_v4_class_maxes[this->char_class];
// Prevent GC NTE from crashing from extra models
this->extra_model = 0;
this->validation_flags &= 0xFD;
} else if (is_v1_or_v2(v)) {
// V1/V2 have fewer classes, so we'll substitute some here
switch (this->char_class) {
case 0: // HUmar
case 1: // HUnewearl
case 2: // HUcast
case 3: // RAmar
case 4: // RAcast
case 5: // RAcaseal
case 6: // FOmarl
case 7: // FOnewm
case 8: // FOnewearl
case 12: // V3 custom 1
case 13: // V3 custom 2
break;
case 9: // HUcaseal
this->char_class = 5; // HUcaseal -> RAcaseal
break;
case 10: // FOmar
this->char_class = 0; // FOmar -> HUmar
break;
case 11: // RAmarl
this->char_class = 1; // RAmarl -> HUnewearl
break;
case 14: // V2 custom 1 / V3 custom 3
case 15: // V2 custom 2 / V3 custom 4
case 16: // V2 custom 3 / V3 custom 5
case 17: // V2 custom 4 / V3 custom 6
case 18: // V2 custom 5 / V3 custom 7
this->char_class -= 5;
break;
default:
this->char_class = 0; // Invalid classes -> HUmar
}
this->version = min<uint8_t>(this->version, is_v1(v) ? 0 : 2);
maxes = &v1_v2_class_maxes[this->char_class];
} else {
if (this->char_class >= 19) {
this->char_class = 0; // Invalid classes -> HUmar
}
this->version = min<uint8_t>(this->version, 3);
maxes = &v3_v4_class_maxes[this->char_class];
}
// V1/V2 has fewer costumes and android skins, so substitute them here
this->costume = maxes->costume ? (this->costume % maxes->costume) : 0;
this->skin = maxes->skin ? (this->skin % maxes->skin) : 0;
this->face = maxes->face ? (this->face % maxes->face) : 0;
this->head = maxes->head ? (this->head % maxes->head) : 0;
this->hair = maxes->hair ? (this->hair % maxes->hair) : 0;
if (this->name_color == 0) {
this->name_color = 0xFFFFFFFF;
}
if (is_v1_or_v2(v)) {
this->compute_name_color_checksum();
} else {
this->name_color_checksum = 0;
}
this->class_flags = class_flags_for_class(this->char_class);
this->name.clear_after_bytes(0x0C);
}
void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_version(Version v) {
this->visual.enforce_lobby_join_limits_for_version(v);
}
void PlayerDispDataBB::enforce_lobby_join_limits_for_version(Version v) {
this->visual.enforce_lobby_join_limits_for_version(v);
this->name.clear_after_bytes(0x18); // 12 characters
}
PlayerDispDataBB PlayerDispDataDCPCV3::to_bb(uint8_t to_language, uint8_t from_language) const {
PlayerDispDataBB bb;
bb.stats = this->stats;
bb.visual = this->visual;
bb.visual.name.encode(" 0");
string decoded_name = this->visual.name.decode(from_language);
bb.name.encode(decoded_name, to_language);
bb.config = this->config;
bb.technique_levels_v1 = this->technique_levels_v1;
return bb;
}
PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3(uint8_t to_language, uint8_t from_language) const {
PlayerDispDataDCPCV3 ret;
ret.stats = this->stats;
ret.visual = this->visual;
string decoded_name = this->name.decode(from_language);
ret.visual.name.encode(decoded_name, to_language);
ret.config = this->config;
ret.technique_levels_v1 = this->technique_levels_v1;
return ret;
}
void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
this->visual.name_color = pre.visual.name_color;
this->visual.extra_model = pre.visual.extra_model;
@@ -233,6 +55,106 @@ void GuildCardBB::clear() {
this->char_class = 0;
}
GuildCardDCNTE::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardDC::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardPC::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardXB::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardBB::operator GuildCardDCNTE() const {
GuildCardDCNTE ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardBB::operator GuildCardDC() const {
GuildCardDC ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardBB::operator GuildCardPC() const {
GuildCardPC ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardBB::operator GuildCardXB() const {
GuildCardXB ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
void PlayerLobbyDataPC::clear() {
this->player_tag = 0;
this->guild_card_number = 0;
@@ -279,7 +201,7 @@ void PlayerLobbyDataBB::clear() {
this->hide_help_prompt = 0;
}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Challenge& rec)
PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengeDC& rec)
: title_color(rec.title_color),
unknown_u0(rec.unknown_u0),
times_ep1_online(rec.times_ep1_online),
@@ -303,7 +225,7 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Chall
rank_title(rec.rank_title.decode(), 1),
unknown_l7(0) {}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec)
PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengePC& rec)
: title_color(rec.title_color),
unknown_u0(rec.unknown_u0),
times_ep1_online(rec.times_ep1_online),
@@ -327,35 +249,8 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Chall
rank_title(rec.rank_title.decode(), 1),
unknown_l7(0) {}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsV3_Challenge<false>& rec)
: title_color(rec.stats.title_color),
unknown_u0(rec.stats.unknown_u0),
times_ep1_online(rec.stats.times_ep1_online),
times_ep2_online(rec.stats.times_ep2_online),
times_ep1_offline(rec.stats.times_ep1_offline),
grave_is_ep2(rec.stats.grave_is_ep2),
grave_stage_num(rec.stats.grave_stage_num),
grave_floor(rec.stats.grave_floor),
unknown_g0(rec.stats.unknown_g0),
grave_deaths(rec.stats.grave_deaths),
unknown_u4(rec.stats.unknown_u4),
grave_time(rec.stats.grave_time),
grave_defeated_by_enemy_rt_index(rec.stats.grave_defeated_by_enemy_rt_index),
grave_x(rec.stats.grave_x),
grave_y(rec.stats.grave_y),
grave_z(rec.stats.grave_z),
grave_team(rec.stats.grave_team.decode(), 1),
grave_message(rec.stats.grave_message.decode(), 1),
unknown_m5(rec.stats.unknown_m5),
unknown_t6(rec.stats.unknown_t6),
ep1_online_award_state(rec.stats.ep1_online_award_state),
ep2_online_award_state(rec.stats.ep2_online_award_state),
ep1_offline_award_state(rec.stats.ep1_offline_award_state),
rank_title(rec.rank_title.decode(), 1),
unknown_l7(rec.unknown_l7) {}
PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const {
PlayerRecordsDC_Challenge ret;
PlayerRecordsChallengeBB::operator PlayerRecordsChallengeDC() const {
PlayerRecordsChallengeDC ret;
ret.title_color = this->title_color;
ret.unknown_u0 = this->unknown_u0;
ret.rank_title.encode(this->rank_title.decode());
@@ -384,8 +279,8 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const {
return ret;
}
PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const {
PlayerRecordsPC_Challenge ret;
PlayerRecordsChallengeBB::operator PlayerRecordsChallengePC() const {
PlayerRecordsChallengePC ret;
ret.title_color = this->title_color;
ret.unknown_u0 = this->unknown_u0;
ret.rank_title.encode(this->rank_title.decode());
@@ -414,289 +309,6 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const {
return ret;
}
PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge<false>() const {
PlayerRecordsV3_Challenge<false> ret;
ret.stats.title_color = this->title_color;
ret.stats.unknown_u0 = this->unknown_u0;
ret.stats.times_ep1_online = this->times_ep1_online;
ret.stats.times_ep2_online = this->times_ep2_online;
ret.stats.times_ep1_offline = this->times_ep1_offline;
ret.stats.grave_is_ep2 = this->grave_is_ep2;
ret.stats.grave_stage_num = this->grave_stage_num;
ret.stats.grave_floor = this->grave_floor;
ret.stats.unknown_g0 = this->unknown_g0;
ret.stats.grave_deaths = this->grave_deaths;
ret.stats.unknown_u4 = this->unknown_u4;
ret.stats.grave_time = this->grave_time;
ret.stats.grave_defeated_by_enemy_rt_index = this->grave_defeated_by_enemy_rt_index;
ret.stats.grave_x = this->grave_x;
ret.stats.grave_y = this->grave_y;
ret.stats.grave_z = this->grave_z;
ret.stats.grave_team.encode(this->grave_team.decode(), 1);
ret.stats.grave_message.encode(this->grave_message.decode(), 1);
ret.stats.unknown_m5 = this->unknown_m5;
ret.stats.unknown_t6 = this->unknown_t6;
ret.stats.ep1_online_award_state = this->ep1_online_award_state;
ret.stats.ep2_online_award_state = this->ep2_online_award_state;
ret.stats.ep1_offline_award_state = this->ep1_offline_award_state;
ret.rank_title.encode(this->rank_title.decode(), 1);
ret.unknown_l7 = this->unknown_l7;
return ret;
}
void PlayerBank::add_item(const ItemData& item, const ItemData::StackLimits& limits) {
uint32_t primary_identifier = item.primary_identifier();
if (primary_identifier == 0x04000000) {
this->meseta += item.data2d;
if (this->meseta > 999999) {
this->meseta = 999999;
}
return;
}
size_t combine_max = item.max_stack_size(limits);
if (combine_max > 1) {
size_t y;
for (y = 0; y < this->num_items; y++) {
if (this->items[y].data.primary_identifier() == primary_identifier) {
break;
}
}
if (y < this->num_items) {
uint8_t new_count = this->items[y].data.data1[5] + item.data1[5];
if (new_count > combine_max) {
throw runtime_error("stack size would exceed limit");
}
this->items[y].data.data1[5] = new_count;
this->items[y].amount = new_count;
return;
}
}
if (this->num_items >= 200) {
throw runtime_error("no free space in bank");
}
auto& last_item = this->items[this->num_items];
last_item.data = item;
last_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1;
last_item.present = 1;
this->num_items++;
}
ItemData PlayerBank::remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits) {
size_t index = this->find_item(item_id);
auto& bank_item = this->items[index];
ItemData ret;
if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) {
ret = bank_item.data;
ret.data1[5] = amount;
bank_item.data.data1[5] -= amount;
bank_item.amount -= amount;
return ret;
}
ret = bank_item.data;
this->num_items--;
for (size_t x = index; x < this->num_items; x++) {
this->items[x] = this->items[x + 1];
}
auto& last_item = this->items[this->num_items];
last_item.amount = 0;
last_item.present = 0;
last_item.data.clear();
return ret;
}
size_t PlayerInventory::find_item(uint32_t item_id) const {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.id == item_id) {
return x;
}
}
throw out_of_range("item not present");
}
size_t PlayerInventory::find_item_by_primary_identifier(uint32_t primary_identifier) const {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.primary_identifier() == primary_identifier) {
return x;
}
}
throw out_of_range("item not present");
}
size_t PlayerInventory::find_equipped_item(EquipSlot slot) const {
ssize_t ret = -1;
for (size_t y = 0; y < this->num_items; y++) {
const auto& i = this->items[y];
if (!(i.flags & 0x00000008)) {
continue;
}
if (!i.data.can_be_equipped_in_slot(slot)) {
continue;
}
// Units can be equipped in multiple slots, so the currently-equipped slot
// is stored in the item data itself.
if (((slot == EquipSlot::UNIT_1) && (i.data.data1[4] != 0x00)) ||
((slot == EquipSlot::UNIT_2) && (i.data.data1[4] != 0x01)) ||
((slot == EquipSlot::UNIT_3) && (i.data.data1[4] != 0x02)) ||
((slot == EquipSlot::UNIT_4) && (i.data.data1[4] != 0x03))) {
continue;
}
if (ret < 0) {
ret = y;
} else {
throw runtime_error("multiple items are equipped in the same slot");
}
}
if (ret < 0) {
throw out_of_range("no item is equipped in this slot");
}
return ret;
}
bool PlayerInventory::has_equipped_item(EquipSlot slot) const {
try {
this->find_equipped_item(slot);
return true;
} catch (const out_of_range&) {
return false;
}
}
void PlayerInventory::equip_item_id(uint32_t item_id, EquipSlot slot, bool allow_overwrite) {
this->equip_item_index(this->find_item(item_id), slot, allow_overwrite);
}
void PlayerInventory::equip_item_index(size_t index, EquipSlot slot, bool allow_overwrite) {
auto& item = this->items[index];
if ((slot == EquipSlot::UNKNOWN) || !item.data.can_be_equipped_in_slot(slot)) {
slot = item.data.default_equip_slot();
}
if (this->has_equipped_item(slot)) {
if (allow_overwrite) {
this->unequip_item_slot(slot);
} else {
throw runtime_error("equip slot is already in use");
}
}
item.flags |= 0x00000008;
// Units store which slot they're equipped in within the item data itself
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) {
item.data.data1[4] = static_cast<uint8_t>(slot) - 9;
}
}
void PlayerInventory::unequip_item_id(uint32_t item_id) {
this->unequip_item_index(this->find_item(item_id));
}
void PlayerInventory::unequip_item_slot(EquipSlot slot) {
this->unequip_item_index(this->find_equipped_item(slot));
}
void PlayerInventory::unequip_item_index(size_t index) {
auto& item = this->items[index];
item.flags &= (~0x00000008);
// Units store which slot they're equipped in within the item data itself
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) {
item.data.data1[4] = 0x00;
}
// If the item is an armor, remove all units too
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x01)) {
for (size_t z = 0; z < 30; z++) {
auto& unit = this->items[z];
if ((unit.data.data1[0] == 0x01) && (unit.data.data1[1] == 0x03)) {
unit.flags &= (~0x00000008);
unit.data.data1[4] = 0x00;
}
}
}
}
size_t PlayerInventory::remove_all_items_of_type(uint8_t data1_0, int16_t data1_1) {
size_t write_offset = 0;
for (size_t read_offset = 0; read_offset < this->num_items; read_offset++) {
bool should_delete = ((this->items[read_offset].data.data1[0] == data1_0) &&
((data1_1 < 0) || (this->items[read_offset].data.data1[1] == static_cast<uint8_t>(data1_1))));
if (!should_delete) {
if (read_offset != write_offset) {
this->items[write_offset].present = this->items[read_offset].present;
this->items[write_offset].unknown_a1 = this->items[read_offset].unknown_a1;
this->items[write_offset].flags = this->items[read_offset].flags;
this->items[write_offset].data = this->items[read_offset].data;
}
write_offset++;
}
}
size_t ret = this->num_items - write_offset;
this->num_items = write_offset;
return ret;
}
void PlayerInventory::decode_from_client(shared_ptr<Client> c) {
for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.decode_for_version(c->version());
}
}
void PlayerInventory::encode_for_client(shared_ptr<Client> c) {
Version v = c->version();
if (v == Version::DC_NTE) {
// DC NTE has the item count as a 32-bit value here, whereas every other
// version uses a single byte. To stop DC NTE from crashing by trying to
// construct far more than 30 TItem objects, we clear the fields DC NTE
// doesn't know about. Note that the 11/2000 prototype does not have this
// issue - its inventory format matches the rest of the versions.
this->hp_from_materials = 0;
this->tp_from_materials = 0;
this->language = 0;
} else if ((v != Version::PC_NTE) && (v != Version::PC_V2)) {
if (this->language > 4) {
this->language = 0;
}
} else {
if (this->language > 7) {
this->language = 0;
}
}
// For pre-V2 clients, use the V2 parameter table, since the V1 table doesn't
// have correct encodings for backward-compatible V2 items.
auto item_parameter_table = c->require_server_state()->item_parameter_table_for_encode(v);
for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.encode_for_version(v, item_parameter_table);
}
}
size_t PlayerBank::find_item(uint32_t item_id) {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.id == item_id) {
return x;
}
}
throw out_of_range("item not present");
}
void PlayerBank::sort() {
std::sort(this->items.data(), this->items.data() + this->num_items);
}
void PlayerBank::assign_ids(uint32_t base_id) {
for (size_t z = 0; z < this->num_items; z++) {
this->items[z].data.id = base_id + z;
}
}
QuestFlagsV1& QuestFlagsV1::operator=(const QuestFlags& other) {
this->data[0] = other.data[0];
this->data[1] = other.data[1];
@@ -1087,11 +699,6 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
}
}
SymbolChat::SymbolChat()
: spec(0),
corner_objects(0x00FF),
face_parts() {}
void RecentSwitchFlags::add(uint16_t flag_num) {
if ((flag_num != ((this->flag_nums >> 48) & 0xFFFF)) &&
(flag_num != ((this->flag_nums >> 32) & 0xFFFF)) &&
+491 -207
View File
@@ -15,106 +15,25 @@
#include "ItemData.hh"
#include "LevelTable.hh"
#include "PSOEncryption.hh"
#include "PlayerInventory.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
class Client;
class ItemParameterTable;
// PSO V2 stored some extra data in the character structs in a format that I'm
// sure Sega thought was very clever for backward compatibility, but for us is
// just plain annoying. Specifically, they used the third and fourth bytes of
// the InventoryItem struct to store some things not present in V1. The game
// stores arrays of bytes striped across these structures. In newserv, we call
// those fields extension_data. They contain:
// items[0].extension_data1 through items[19].extension_data1:
// Extended technique levels. The values in the technique_levels_v1 array
// only go up to 14 (tech level 15); if the player has a technique above
// level 15, the corresponding extension_data1 field holds the remaining
// levels (so a level 20 tech would have 14 in technique_levels_v1 and 5
// in the corresponding item's extension_data1 field).
// items[0].extension_data2 through items[3].extension_data2:
// The flags field from the PSOGCCharacterFile::Character struct; see
// SaveFileFormats.hh for details.
// items[4].extension_data2 through items[7].extension_data2:
// The timestamp when the character was last saved, in seconds since
// January 1, 2000. Stored little-endian, so items[4] contains the LSB.
// items[8].extension_data2 through items[12].extension_data2:
// Number of power materials, mind materials, evade materials, def
// materials, and luck materials (respectively) used by the player.
// items[13].extension_data2 through items[15].extension_data2:
// Unknown. These are not an array, but do appear to be related.
struct PlayerInventoryItem {
/* 00 */ uint8_t present = 0;
/* 01 */ uint8_t unknown_a1 = 0;
// See note above about these fields
/* 02 */ uint8_t extension_data1 = 0;
/* 03 */ uint8_t extension_data2 = 0;
/* 04 */ le_uint32_t flags = 0; // 8 = equipped
/* 08 */ ItemData data;
/* 1C */
PlayerInventoryItem() = default;
explicit PlayerInventoryItem(const ItemData& item, bool equipped = false);
} __attribute__((packed));
struct PlayerBankItem {
/* 00 */ ItemData data;
/* 14 */ le_uint16_t amount = 0;
/* 16 */ le_uint16_t present = 0;
/* 18 */
inline bool operator<(const PlayerBankItem& other) const {
return this->data < other.data;
}
} __attribute__((packed));
struct PlayerInventory {
/* 0000 */ uint8_t num_items = 0;
/* 0001 */ uint8_t hp_from_materials = 0;
/* 0002 */ uint8_t tp_from_materials = 0;
/* 0003 */ uint8_t language = 0;
/* 0004 */ parray<PlayerInventoryItem, 30> items;
/* 034C */
size_t find_item(uint32_t item_id) const;
size_t find_item_by_primary_identifier(uint32_t primary_identifier) const;
size_t find_equipped_item(EquipSlot slot) const;
bool has_equipped_item(EquipSlot slot) const;
void equip_item_id(uint32_t item_id, EquipSlot slot, bool allow_overwrite);
void equip_item_index(size_t index, EquipSlot slot, bool allow_overwrite);
void unequip_item_id(uint32_t item_id);
void unequip_item_slot(EquipSlot slot);
void unequip_item_index(size_t index);
size_t remove_all_items_of_type(uint8_t data0, int16_t data1 = -1);
void decode_from_client(std::shared_ptr<Client> c);
void encode_for_client(std::shared_ptr<Client> c);
} __attribute__((packed));
struct PlayerBank {
/* 0000 */ le_uint32_t num_items = 0;
/* 0004 */ le_uint32_t meseta = 0;
/* 0008 */ parray<PlayerBankItem, 200> items;
/* 12C8 */
void add_item(const ItemData& item, const ItemData::StackLimits& limits);
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits);
size_t find_item(uint32_t item_id);
void sort();
void assign_ids(uint32_t base_id);
} __attribute__((packed));
struct PlayerDispDataBB;
struct PlayerVisualConfig {
template <bool IsBigEndian>
struct PlayerVisualConfigT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type;
/* 00 */ pstring<TextEncoding::ASCII, 0x10> name;
/* 10 */ parray<uint8_t, 8> unknown_a2;
/* 18 */ le_uint32_t name_color = 0xFFFFFFFF; // ARGB
/* 18 */ U32T name_color = 0xFFFFFFFF; // ARGB
/* 1C */ uint8_t extra_model = 0;
/* 1D */ parray<uint8_t, 0x0F> unused;
// See compute_name_color_checksum for details on how this is computed. If the
@@ -122,7 +41,7 @@ struct PlayerVisualConfig {
// default color instead. This field is ignored on GC; on BB (and presumably
// Xbox), if this has a nonzero value, the "Change Name" option appears in the
// character selection menu.
/* 2C */ le_uint32_t name_color_checksum = 0;
/* 2C */ U32T name_color_checksum = 0;
/* 30 */ uint8_t section_id = 0;
/* 31 */ uint8_t char_class = 0;
// validation_flags specifies that some parts of this structure are not valid
@@ -138,35 +57,206 @@ struct PlayerVisualConfig {
// F = force, R = ranger, H = hunter
// A = android, N = newman, M = human
// f = female, m = male
/* 34 */ le_uint32_t class_flags = 0;
/* 38 */ le_uint16_t costume = 0;
/* 3A */ le_uint16_t skin = 0;
/* 3C */ le_uint16_t face = 0;
/* 3E */ le_uint16_t head = 0;
/* 40 */ le_uint16_t hair = 0;
/* 42 */ le_uint16_t hair_r = 0;
/* 44 */ le_uint16_t hair_g = 0;
/* 46 */ le_uint16_t hair_b = 0;
/* 48 */ le_float proportion_x = 0.0;
/* 4C */ le_float proportion_y = 0.0;
/* 34 */ U32T class_flags = 0;
/* 38 */ U16T costume = 0;
/* 3A */ U16T skin = 0;
/* 3C */ U16T face = 0;
/* 3E */ U16T head = 0;
/* 40 */ U16T hair = 0;
/* 42 */ U16T hair_r = 0;
/* 44 */ U16T hair_g = 0;
/* 46 */ U16T hair_b = 0;
/* 48 */ F32T proportion_x = 0.0;
/* 4C */ F32T proportion_y = 0.0;
/* 50 */
static uint32_t compute_name_color_checksum(uint32_t name_color);
void compute_name_color_checksum();
static uint32_t compute_name_color_checksum(uint32_t name_color) {
uint8_t x = (random_object<uint32_t>() % 0xFF) + 1;
uint8_t y = (random_object<uint32_t>() % 0xFF) + 1;
// name_color (ARGB) = ABCDEFGHabcdefghIJKLMNOPijklmnop
// name_color_checksum = 000000000ijklmabcdeIJKLM00000000 ^ xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy
uint32_t xbrgx95558 = ((name_color << 15) & 0x007C0000) | ((name_color >> 6) & 0x0003E000) | ((name_color >> 3) & 0x00001F00);
uint32_t mask = (x << 24) | (y << 16) | (x << 8) | y;
return xbrgx95558 ^ mask;
}
void enforce_lobby_join_limits_for_version(Version v);
} __attribute__((packed));
void compute_name_color_checksum() {
this->name_color_checksum = this->compute_name_color_checksum(this->name_color);
}
struct PlayerDispDataDCPCV3 {
/* 00 */ PlayerStats stats;
/* 24 */ PlayerVisualConfig visual;
void enforce_lobby_join_limits_for_version(Version v) {
struct ClassMaxes {
uint16_t costume;
uint16_t skin;
uint16_t face;
uint16_t head;
uint16_t hair;
};
static constexpr ClassMaxes v1_v2_class_maxes[14] = {
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
};
static constexpr ClassMaxes v3_v4_class_maxes[19] = {
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0001},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0001},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000}};
const ClassMaxes* maxes;
if (v == Version::GC_NTE) {
// GC NTE has HUcaseal, FOmar, and RAmarl, but missing others
if (this->char_class >= 12) {
this->char_class = 0; // Invalid classes -> HUmar
}
// GC NTE is basically v2, but uses v3 maxes
this->version = std::min<uint8_t>(this->version, 2);
maxes = &v3_v4_class_maxes[this->char_class];
// Prevent GC NTE from crashing from extra models
this->extra_model = 0;
this->validation_flags &= 0xFD;
} else if (is_v1_or_v2(v)) {
// V1/V2 have fewer classes, so we'll substitute some here
switch (this->char_class) {
case 0: // HUmar
case 1: // HUnewearl
case 2: // HUcast
case 3: // RAmar
case 4: // RAcast
case 5: // RAcaseal
case 6: // FOmarl
case 7: // FOnewm
case 8: // FOnewearl
case 12: // V3 custom 1
case 13: // V3 custom 2
break;
case 9: // HUcaseal
this->char_class = 5; // HUcaseal -> RAcaseal
break;
case 10: // FOmar
this->char_class = 0; // FOmar -> HUmar
break;
case 11: // RAmarl
this->char_class = 1; // RAmarl -> HUnewearl
break;
case 14: // V2 custom 1 / V3 custom 3
case 15: // V2 custom 2 / V3 custom 4
case 16: // V2 custom 3 / V3 custom 5
case 17: // V2 custom 4 / V3 custom 6
case 18: // V2 custom 5 / V3 custom 7
this->char_class -= 5;
break;
default:
this->char_class = 0; // Invalid classes -> HUmar
}
this->version = std::min<uint8_t>(this->version, is_v1(v) ? 0 : 2);
maxes = &v1_v2_class_maxes[this->char_class];
} else {
if (this->char_class >= 19) {
this->char_class = 0; // Invalid classes -> HUmar
}
this->version = std::min<uint8_t>(this->version, 3);
maxes = &v3_v4_class_maxes[this->char_class];
}
// V1/V2 has fewer costumes and android skins, so substitute them here
this->costume = maxes->costume ? (this->costume % maxes->costume) : 0;
this->skin = maxes->skin ? (this->skin % maxes->skin) : 0;
this->face = maxes->face ? (this->face % maxes->face) : 0;
this->head = maxes->head ? (this->head % maxes->head) : 0;
this->hair = maxes->hair ? (this->hair % maxes->hair) : 0;
if (this->name_color == 0) {
this->name_color = 0xFFFFFFFF;
}
if (is_v1_or_v2(v)) {
this->compute_name_color_checksum();
} else {
this->name_color_checksum = 0;
}
this->class_flags = class_flags_for_class(this->char_class);
this->name.clear_after_bytes(0x0C);
}
operator PlayerVisualConfigT<!IsBigEndian>() const {
PlayerVisualConfigT<!IsBigEndian> ret;
ret.name = this->name;
ret.unknown_a2 = this->unknown_a2;
ret.name_color = this->name_color.load();
ret.extra_model = this->extra_model;
ret.unused = this->unused;
ret.name_color_checksum = this->name_color_checksum.load();
ret.section_id = this->section_id;
ret.char_class = this->char_class;
ret.validation_flags = this->validation_flags;
ret.version = this->version;
ret.class_flags = this->class_flags.load();
ret.costume = this->costume.load();
ret.skin = this->skin.load();
ret.face = this->face.load();
ret.head = this->head.load();
ret.hair = this->hair.load();
ret.hair_r = this->hair_r.load();
ret.hair_g = this->hair_g.load();
ret.hair_b = this->hair_b.load();
ret.proportion_x = this->proportion_x.load();
ret.proportion_y = this->proportion_y.load();
return ret;
}
} __packed__;
using PlayerVisualConfig = PlayerVisualConfigT<false>;
using PlayerVisualConfigBE = PlayerVisualConfigT<true>;
check_struct_size(PlayerVisualConfig, 0x50);
check_struct_size(PlayerVisualConfigBE, 0x50);
template <bool IsBigEndian>
struct PlayerDispDataDCPCV3T {
/* 00 */ PlayerStatsT<IsBigEndian> stats;
/* 24 */ PlayerVisualConfigT<IsBigEndian> visual;
/* 74 */ parray<uint8_t, 0x48> config;
/* BC */ parray<uint8_t, 0x14> technique_levels_v1;
/* D0 */
void enforce_lobby_join_limits_for_version(Version v);
void enforce_lobby_join_limits_for_version(Version v) {
this->visual.enforce_lobby_join_limits_for_version(v);
}
PlayerDispDataBB to_bb(uint8_t to_language, uint8_t from_language) const;
} __attribute__((packed));
} __packed__;
using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T<false>;
using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T<true>;
check_struct_size(PlayerDispDataDCPCV3, 0xD0);
check_struct_size(PlayerDispDataDCPCV3BE, 0xD0);
struct PlayerDispDataBBPreview {
/* 00 */ le_uint32_t experience = 0;
@@ -177,7 +267,7 @@ struct PlayerDispDataBBPreview {
/* 58 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> name;
/* 78 */ uint32_t play_time_seconds = 0;
/* 7C */
} __attribute__((packed));
} __packed_ws__(PlayerDispDataBBPreview, 0x7C);
// BB player appearance and stats data
struct PlayerDispDataBB {
@@ -188,14 +278,44 @@ struct PlayerDispDataBB {
/* 017C */ parray<uint8_t, 0x14> technique_levels_v1;
/* 0190 */
void enforce_lobby_join_limits_for_version(Version v);
PlayerDispDataDCPCV3 to_dcpcv3(uint8_t to_language, uint8_t from_language) const;
void enforce_lobby_join_limits_for_version(Version v) {
this->visual.enforce_lobby_join_limits_for_version(v);
this->name.clear_after_bytes(0x18); // 12 characters
}
template <bool IsBigEndian>
PlayerDispDataDCPCV3T<IsBigEndian> to_dcpcv3(uint8_t to_language, uint8_t from_language) const {
PlayerDispDataDCPCV3T<IsBigEndian> ret;
ret.stats = this->stats;
ret.visual = this->visual;
std::string decoded_name = this->name.decode(from_language);
ret.visual.name.encode(decoded_name, to_language);
ret.config = this->config;
ret.technique_levels_v1 = this->technique_levels_v1;
return ret;
}
void apply_preview(const PlayerDispDataBBPreview&);
void apply_dressing_room(const PlayerDispDataBBPreview&);
} __attribute__((packed));
} __packed_ws__(PlayerDispDataBB, 0x190);
template <bool IsBigEndian>
PlayerDispDataBB PlayerDispDataDCPCV3T<IsBigEndian>::to_bb(uint8_t to_language, uint8_t from_language) const {
PlayerDispDataBB bb;
bb.stats = this->stats;
bb.visual = this->visual;
bb.visual.name.encode(" 0");
std::string decoded_name = this->visual.name.decode(from_language);
bb.name.encode(decoded_name, to_language);
bb.config = this->config;
bb.technique_levels_v1 = this->technique_levels_v1;
return bb;
}
struct GuildCardBB;
struct GuildCardDCNTE {
/* 00 */ le_uint32_t player_tag = 0;
/* 00 */ le_uint32_t player_tag = 0x00010000;
/* 04 */ le_uint32_t guild_card_number = 0;
/* 08 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 20 */ pstring<TextEncoding::MARKED, 0x48> description;
@@ -205,10 +325,12 @@ struct GuildCardDCNTE {
/* 79 */ uint8_t section_id = 0;
/* 7A */ uint8_t char_class = 0;
/* 7B */
} __attribute__((packed));
operator GuildCardBB() const;
} __packed_ws__(GuildCardDCNTE, 0x7B);
struct GuildCardDC {
/* 00 */ le_uint32_t player_tag = 0;
/* 00 */ le_uint32_t player_tag = 0x00010000;
/* 04 */ le_uint32_t guild_card_number = 0;
/* 08 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 20 */ pstring<TextEncoding::MARKED, 0x48> description;
@@ -218,10 +340,12 @@ struct GuildCardDC {
/* 7B */ uint8_t section_id = 0;
/* 7C */ uint8_t char_class = 0;
/* 7D */
} __attribute__((packed));
operator GuildCardBB() const;
} __packed_ws__(GuildCardDC, 0x7D);
struct GuildCardPC {
/* 00 */ le_uint32_t player_tag = 0;
/* 00 */ le_uint32_t player_tag = 0x00010000;
/* 04 */ le_uint32_t guild_card_number = 0;
// TODO: Is the length of the name field correct here?
/* 08 */ pstring<TextEncoding::UTF16, 0x18> name;
@@ -231,11 +355,16 @@ struct GuildCardPC {
/* EE */ uint8_t section_id = 0;
/* EF */ uint8_t char_class = 0;
/* F0 */
} __attribute__((packed));
struct GuildCardGC {
/* 00 */ le_uint32_t player_tag = 0;
/* 04 */ le_uint32_t guild_card_number = 0;
operator GuildCardBB() const;
} __packed_ws__(GuildCardPC, 0xF0);
template <bool IsBigEndian>
struct GuildCardGCT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ U32T player_tag = 0x00010000;
/* 04 */ U32T guild_card_number = 0;
/* 08 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 20 */ pstring<TextEncoding::MARKED, 0x6C> description;
/* 8C */ uint8_t present = 0;
@@ -243,10 +372,16 @@ struct GuildCardGC {
/* 8E */ uint8_t section_id = 0;
/* 8F */ uint8_t char_class = 0;
/* 90 */
} __attribute__((packed));
operator GuildCardBB() const;
} __packed__;
using GuildCardGC = GuildCardGCT<false>;
using GuildCardGCBE = GuildCardGCT<true>;
check_struct_size(GuildCardGC, 0x90);
check_struct_size(GuildCardGCBE, 0x90);
struct GuildCardXB {
/* 0000 */ le_uint32_t player_tag = 0;
/* 0000 */ le_uint32_t player_tag = 0x00010000;
/* 0004 */ le_uint32_t guild_card_number = 0;
/* 0008 */ le_uint32_t xb_user_id_high = 0;
/* 000C */ le_uint32_t xb_user_id_low = 0;
@@ -257,7 +392,9 @@ struct GuildCardXB {
/* 022A */ uint8_t section_id = 0;
/* 022B */ uint8_t char_class = 0;
/* 022C */
} __attribute__((packed));
operator GuildCardBB() const;
} __packed_ws__(GuildCardXB, 0x22C);
struct GuildCardBB {
/* 0000 */ le_uint32_t guild_card_number = 0;
@@ -271,7 +408,38 @@ struct GuildCardBB {
/* 0108 */
void clear();
} __attribute__((packed));
operator GuildCardDCNTE() const;
operator GuildCardDC() const;
operator GuildCardPC() const;
template <bool IsBigEndian>
operator GuildCardGCT<IsBigEndian>() const {
GuildCardGCT<IsBigEndian> ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number.load();
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
operator GuildCardXB() const;
} __packed_ws__(GuildCardBB, 0x108);
template <bool IsBigEndian>
GuildCardGCT<IsBigEndian>::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number.load();
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
struct PlayerLobbyDataPC {
le_uint32_t player_tag = 0;
@@ -286,7 +454,7 @@ struct PlayerLobbyDataPC {
pstring<TextEncoding::UTF16, 0x10> name;
void clear();
} __attribute__((packed));
} __packed_ws__(PlayerLobbyDataPC, 0x30);
struct PlayerLobbyDataDCGC {
le_uint32_t player_tag = 0;
@@ -296,7 +464,7 @@ struct PlayerLobbyDataDCGC {
pstring<TextEncoding::ASCII, 0x10> name;
void clear();
} __attribute__((packed));
} __packed_ws__(PlayerLobbyDataDCGC, 0x20);
struct XBNetworkLocation {
/* 00 */ le_uint32_t internal_ipv4_address = 0x0A0A0A0A;
@@ -307,21 +475,21 @@ struct XBNetworkLocation {
/* 14 */ le_uint32_t unknown_a2;
/* 18 */ le_uint64_t account_id = 0xFFFFFFFFFFFFFFFF;
/* 20 */ parray<le_uint32_t, 4> unknown_a3;
/* 24 */
/* 30 */
void clear();
} __attribute__((packed));
} __packed_ws__(XBNetworkLocation, 0x30);
struct PlayerLobbyDataXB {
/* 00 */ le_uint32_t player_tag = 0;
/* 04 */ le_uint32_t guild_card_number = 0;
/* 08 */ XBNetworkLocation netloc;
/* 2C */ le_uint32_t client_id = 0;
/* 30 */ pstring<TextEncoding::ASCII, 0x10> name;
/* 40 */
/* 38 */ le_uint32_t client_id = 0;
/* 3C */ pstring<TextEncoding::ASCII, 0x10> name;
/* 4C */
void clear();
} __attribute__((packed));
} __packed_ws__(PlayerLobbyDataXB, 0x4C);
struct PlayerLobbyDataBB {
/* 00 */ le_uint32_t player_tag = 0;
@@ -334,25 +502,36 @@ struct PlayerLobbyDataBB {
// If this field is zero, the "Press F1 for help" prompt appears in the corner
// of the screen in the lobby and on Pioneer 2.
/* 40 */ le_uint32_t hide_help_prompt = 1;
/* 44 */
/* 44 */
void clear();
} __attribute__((packed));
} __packed_ws__(PlayerLobbyDataBB, 0x44);
template <bool IsBigEndian>
struct ChallengeAwardState {
struct ChallengeAwardStateT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T rank_award_flags = 0;
ChallengeTime<IsBigEndian> maximum_rank;
} __attribute__((packed));
ChallengeTimeT<IsBigEndian> maximum_rank;
operator ChallengeAwardStateT<!IsBigEndian>() const {
ChallengeAwardStateT<!IsBigEndian> ret;
ret.rank_award_flags = this->rank_award_flags.load();
ret.maximum_rank = this->maximum_rank;
return ret;
}
} __packed__;
using ChallengeAwardState = ChallengeAwardStateT<false>;
using ChallengeAwardStateBE = ChallengeAwardStateT<true>;
check_struct_size(ChallengeAwardState, 8);
check_struct_size(ChallengeAwardStateBE, 8);
template <TextEncoding UnencryptedEncoding, TextEncoding EncryptedEncoding>
struct PlayerRecordsDCPC_Challenge {
struct PlayerRecordsChallengeDCPCT {
/* DC:PC */
/* 00:00 */ le_uint16_t title_color = 0x7FFF;
/* 02:02 */ parray<uint8_t, 2> unknown_u0;
/* 04:04 */ pstring<EncryptedEncoding, 0x0C> rank_title;
/* 10:1C */ parray<ChallengeTime<false>, 9> times_ep1_online; // TODO: This might be offline times
/* 10:1C */ parray<ChallengeTimeT<false>, 9> times_ep1_online; // TODO: This might be offline times
/* 34:40 */ uint8_t grave_stage_num = 0;
/* 35:41 */ uint8_t grave_floor = 0;
/* 36:42 */ le_uint16_t grave_deaths = 0;
@@ -370,51 +549,49 @@ struct PlayerRecordsDCPC_Challenge {
/* 48:54 */ le_float grave_z = 0.0f;
/* 4C:58 */ pstring<UnencryptedEncoding, 0x14> grave_team;
/* 60:80 */ pstring<UnencryptedEncoding, 0x18> grave_message;
/* 78:B0 */ parray<ChallengeTime<false>, 9> times_ep1_offline; // TODO: This might be online times
/* 78:B0 */ parray<ChallengeTimeT<false>, 9> times_ep1_offline; // TODO: This might be online times
/* 9C:D4 */ parray<uint8_t, 4> unknown_l4;
/* A0:D8 */
} __attribute__((packed));
struct PlayerRecordsDC_Challenge : PlayerRecordsDCPC_Challenge<TextEncoding::ASCII, TextEncoding::CHALLENGE8> {
} __attribute__((packed));
struct PlayerRecordsPC_Challenge : PlayerRecordsDCPC_Challenge<TextEncoding::UTF16, TextEncoding::CHALLENGE16> {
} __attribute__((packed));
} __packed__;
using PlayerRecordsChallengeDC = PlayerRecordsChallengeDCPCT<TextEncoding::ASCII, TextEncoding::CHALLENGE8>;
using PlayerRecordsChallengePC = PlayerRecordsChallengeDCPCT<TextEncoding::UTF16, TextEncoding::CHALLENGE16>;
check_struct_size(PlayerRecordsChallengeDC, 0xA0);
check_struct_size(PlayerRecordsChallengePC, 0xD8);
template <bool IsBigEndian>
struct PlayerRecordsV3_Challenge {
struct PlayerRecordsChallengeV3T {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using FloatT = typename std::conditional<IsBigEndian, be_float, le_float>::type;
using F32T = typename std::conditional<IsBigEndian, be_float, le_float>::type;
// Offsets are (1) relative to start of C5 entry, and (2) relative to start
// of save file structure
struct Stats {
/* 00:1C */ U16T title_color = 0x7FFF; // XRGB1555
/* 02:1E */ parray<uint8_t, 2> unknown_u0;
/* 04:20 */ parray<ChallengeTime<IsBigEndian>, 9> times_ep1_online;
/* 28:44 */ parray<ChallengeTime<IsBigEndian>, 5> times_ep2_online;
/* 3C:58 */ parray<ChallengeTime<IsBigEndian>, 9> times_ep1_offline;
/* 04:20 */ parray<ChallengeTimeT<IsBigEndian>, 9> times_ep1_online;
/* 28:44 */ parray<ChallengeTimeT<IsBigEndian>, 5> times_ep2_online;
/* 3C:58 */ parray<ChallengeTimeT<IsBigEndian>, 9> times_ep1_offline;
/* 60:7C */ uint8_t grave_is_ep2 = 0;
/* 61:7D */ uint8_t grave_stage_num = 0;
/* 62:7E */ uint8_t grave_floor = 0;
/* 63:7F */ uint8_t unknown_g0 = 0;
/* 64:80 */ U16T grave_deaths = 0;
/* 66:82 */ parray<uint8_t, 2> unknown_u4;
/* 68:84 */ U32T grave_time = 0; // Encoded as in PlayerRecordsDCPC_Challenge
/* 68:84 */ U32T grave_time = 0; // Encoded as in PlayerRecordsChallengeDCPC
/* 6C:88 */ U32T grave_defeated_by_enemy_rt_index = 0;
/* 70:8C */ FloatT grave_x = 0.0f;
/* 74:90 */ FloatT grave_y = 0.0f;
/* 78:94 */ FloatT grave_z = 0.0f;
/* 70:8C */ F32T grave_x = 0.0f;
/* 74:90 */ F32T grave_y = 0.0f;
/* 78:94 */ F32T grave_z = 0.0f;
/* 7C:98 */ pstring<TextEncoding::ASCII, 0x14> grave_team;
/* 90:AC */ pstring<TextEncoding::ASCII, 0x20> grave_message;
/* B0:CC */ parray<uint8_t, 4> unknown_m5;
/* B4:D0 */ parray<U32T, 3> unknown_t6;
/* C0:DC */ ChallengeAwardState<IsBigEndian> ep1_online_award_state;
/* C8:E4 */ ChallengeAwardState<IsBigEndian> ep2_online_award_state;
/* D0:EC */ ChallengeAwardState<IsBigEndian> ep1_offline_award_state;
/* C0:DC */ ChallengeAwardStateT<IsBigEndian> ep1_online_award_state;
/* C8:E4 */ ChallengeAwardStateT<IsBigEndian> ep2_online_award_state;
/* D0:EC */ ChallengeAwardStateT<IsBigEndian> ep1_offline_award_state;
/* D8:F4 */
} __attribute__((packed));
} __packed__;
/* 0000:001C */ Stats stats;
// On Episode 3, there are special cases that apply to this field - if the
// text ends with certain strings, the player will have particle effects
@@ -426,21 +603,25 @@ struct PlayerRecordsV3_Challenge {
/* 00D8:00F4 */ pstring<TextEncoding::CHALLENGE8, 0x0C> rank_title;
/* 00E4:0100 */ parray<uint8_t, 0x1C> unknown_l7;
/* 0100:011C */
} __attribute__((packed));
} __packed__;
using PlayerRecordsChallengeV3 = PlayerRecordsChallengeV3T<false>;
using PlayerRecordsChallengeV3BE = PlayerRecordsChallengeV3T<true>;
check_struct_size(PlayerRecordsChallengeV3, 0x100);
check_struct_size(PlayerRecordsChallengeV3BE, 0x100);
struct PlayerRecordsBB_Challenge {
struct PlayerRecordsChallengeBB {
/* 0000 */ le_uint16_t title_color = 0x7FFF; // XRGB1555
/* 0002 */ parray<uint8_t, 2> unknown_u0;
/* 0004 */ parray<ChallengeTime<false>, 9> times_ep1_online;
/* 0028 */ parray<ChallengeTime<false>, 5> times_ep2_online;
/* 003C */ parray<ChallengeTime<false>, 9> times_ep1_offline;
/* 0004 */ parray<ChallengeTimeT<false>, 9> times_ep1_online;
/* 0028 */ parray<ChallengeTimeT<false>, 5> times_ep2_online;
/* 003C */ parray<ChallengeTimeT<false>, 9> times_ep1_offline;
/* 0060 */ uint8_t grave_is_ep2 = 0;
/* 0061 */ uint8_t grave_stage_num = 0;
/* 0062 */ uint8_t grave_floor = 0;
/* 0063 */ uint8_t unknown_g0 = 0;
/* 0064 */ le_uint16_t grave_deaths = 0;
/* 0066 */ parray<uint8_t, 2> unknown_u4;
/* 0068 */ le_uint32_t grave_time = 0; // Encoded as in PlayerRecordsDCPC_Challenge
/* 0068 */ le_uint32_t grave_time = 0; // Encoded as in PlayerRecordsChallengeDCPC
/* 006C */ le_uint32_t grave_defeated_by_enemy_rt_index = 0;
/* 0070 */ le_float grave_x = 0.0f;
/* 0074 */ le_float grave_y = 0.0f;
@@ -449,36 +630,118 @@ struct PlayerRecordsBB_Challenge {
/* 00A4 */ pstring<TextEncoding::UTF16, 0x20> grave_message;
/* 00E4 */ parray<uint8_t, 4> unknown_m5;
/* 00E8 */ parray<le_uint32_t, 3> unknown_t6;
/* 00F4 */ ChallengeAwardState<false> ep1_online_award_state;
/* 00FC */ ChallengeAwardState<false> ep2_online_award_state;
/* 0104 */ ChallengeAwardState<false> ep1_offline_award_state;
/* 00F4 */ ChallengeAwardStateT<false> ep1_online_award_state;
/* 00FC */ ChallengeAwardStateT<false> ep2_online_award_state;
/* 0104 */ ChallengeAwardStateT<false> ep1_offline_award_state;
/* 010C */ pstring<TextEncoding::CHALLENGE16, 0x0C> rank_title;
/* 0124 */ parray<uint8_t, 0x1C> unknown_l7;
/* 0140 */
PlayerRecordsBB_Challenge() = default;
PlayerRecordsBB_Challenge(const PlayerRecordsBB_Challenge& other) = default;
PlayerRecordsBB_Challenge& operator=(const PlayerRecordsBB_Challenge& other) = default;
PlayerRecordsChallengeBB() = default;
PlayerRecordsChallengeBB(const PlayerRecordsChallengeBB& other) = default;
PlayerRecordsChallengeBB& operator=(const PlayerRecordsChallengeBB& other) = default;
PlayerRecordsBB_Challenge(const PlayerRecordsDC_Challenge& rec);
PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec);
PlayerRecordsBB_Challenge(const PlayerRecordsV3_Challenge<false>& rec);
PlayerRecordsChallengeBB(const PlayerRecordsChallengeDC& rec);
PlayerRecordsChallengeBB(const PlayerRecordsChallengePC& rec);
operator PlayerRecordsDC_Challenge() const;
operator PlayerRecordsPC_Challenge() const;
operator PlayerRecordsV3_Challenge<false>() const;
} __attribute__((packed));
template <bool IsBigEndian>
PlayerRecordsChallengeBB(const PlayerRecordsChallengeV3T<IsBigEndian>& rec)
: title_color(rec.stats.title_color.load()),
unknown_u0(rec.stats.unknown_u0),
times_ep1_online(rec.stats.times_ep1_online),
times_ep2_online(rec.stats.times_ep2_online),
times_ep1_offline(rec.stats.times_ep1_offline),
grave_is_ep2(rec.stats.grave_is_ep2),
grave_stage_num(rec.stats.grave_stage_num),
grave_floor(rec.stats.grave_floor),
unknown_g0(rec.stats.unknown_g0),
grave_deaths(rec.stats.grave_deaths.load()),
unknown_u4(rec.stats.unknown_u4),
grave_time(rec.stats.grave_time.load()),
grave_defeated_by_enemy_rt_index(rec.stats.grave_defeated_by_enemy_rt_index.load()),
grave_x(rec.stats.grave_x.load()),
grave_y(rec.stats.grave_y.load()),
grave_z(rec.stats.grave_z.load()),
grave_team(rec.stats.grave_team.decode(), 1),
grave_message(rec.stats.grave_message.decode(), 1),
unknown_m5(rec.stats.unknown_m5),
ep1_online_award_state(rec.stats.ep1_online_award_state),
ep2_online_award_state(rec.stats.ep2_online_award_state),
ep1_offline_award_state(rec.stats.ep1_offline_award_state),
rank_title(rec.rank_title.decode(), 1),
unknown_l7(rec.unknown_l7) {
for (size_t z = 0; z < std::min<size_t>(this->unknown_t6.size(), rec.stats.unknown_t6.size()); z++) {
this->unknown_t6[z] = rec.stats.unknown_t6[z].load();
}
}
operator PlayerRecordsChallengeDC() const;
operator PlayerRecordsChallengePC() const;
template <bool IsBigEndian>
operator PlayerRecordsChallengeV3T<IsBigEndian>() const {
PlayerRecordsChallengeV3T<IsBigEndian> ret;
ret.stats.title_color = this->title_color.load();
ret.stats.unknown_u0 = this->unknown_u0;
ret.stats.times_ep1_online = this->times_ep1_online;
ret.stats.times_ep2_online = this->times_ep2_online;
ret.stats.times_ep1_offline = this->times_ep1_offline;
ret.stats.grave_is_ep2 = this->grave_is_ep2;
ret.stats.grave_stage_num = this->grave_stage_num;
ret.stats.grave_floor = this->grave_floor;
ret.stats.unknown_g0 = this->unknown_g0;
ret.stats.grave_deaths = this->grave_deaths.load();
ret.stats.unknown_u4 = this->unknown_u4;
ret.stats.grave_time = this->grave_time.load();
ret.stats.grave_defeated_by_enemy_rt_index = this->grave_defeated_by_enemy_rt_index.load();
ret.stats.grave_x = this->grave_x.load();
ret.stats.grave_y = this->grave_y.load();
ret.stats.grave_z = this->grave_z.load();
ret.stats.grave_team.encode(this->grave_team.decode(), 1);
ret.stats.grave_message.encode(this->grave_message.decode(), 1);
ret.stats.unknown_m5 = this->unknown_m5;
for (size_t z = 0; z < std::min<size_t>(ret.stats.unknown_t6.size(), this->unknown_t6.size()); z++) {
ret.stats.unknown_t6[z] = this->unknown_t6[z].load();
}
ret.stats.ep1_online_award_state = this->ep1_online_award_state;
ret.stats.ep2_online_award_state = this->ep2_online_award_state;
ret.stats.ep1_offline_award_state = this->ep1_offline_award_state;
ret.rank_title.encode(this->rank_title.decode(), 1);
ret.unknown_l7 = this->unknown_l7;
return ret;
}
} __packed_ws__(PlayerRecordsChallengeBB, 0x140);
template <bool IsBigEndian>
struct PlayerRecords_Battle {
struct PlayerRecordsBattleT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
// On Episode 3, place_counts[0] is win count and [1] is loss count
/* 00 */ parray<U16T, 4> place_counts;
/* 08 */ U16T disconnect_count = 0;
/* 0A */ parray<uint16_t, 3> unknown_a1;
/* 10 */ parray<uint32_t, 2> unknown_a2;
/* 0A */ parray<U16T, 3> unknown_a1;
/* 10 */ parray<U32T, 2> unknown_a2;
/* 18 */
} __attribute__((packed));
operator PlayerRecordsBattleT<!IsBigEndian>() const {
PlayerRecordsBattleT<!IsBigEndian> ret;
for (size_t z = 0; z < this->place_counts.size(); z++) {
ret.place_counts[z] = this->place_counts[z].load();
}
ret.disconnect_count = this->disconnect_count.load();
for (size_t z = 0; z < this->unknown_a1.size(); z++) {
ret.unknown_a1[z] = this->unknown_a1[z].load();
}
for (size_t z = 0; z < this->unknown_a2.size(); z++) {
ret.unknown_a2[z] = this->unknown_a2[z].load();
}
return ret;
}
} __packed__;
using PlayerRecordsBattle = PlayerRecordsBattleT<false>;
using PlayerRecordsBattleBE = PlayerRecordsBattleT<true>;
check_struct_size(PlayerRecordsBattle, 0x18);
check_struct_size(PlayerRecordsBattleBE, 0x18);
template <typename DestT, typename SrcT = DestT>
DestT convert_player_disp_data(const SrcT&, uint8_t, uint8_t) {
@@ -494,7 +757,7 @@ inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3>(const
template <>
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(
const PlayerDispDataBB& src, uint8_t to_language, uint8_t from_language) {
return src.to_dcpcv3(to_language, from_language);
return src.to_dcpcv3<false>(to_language, from_language);
}
template <>
@@ -534,7 +797,7 @@ struct QuestFlagsForDifficulty {
this->data.clear(0x00);
}
}
} __attribute__((packed));
} __packed_ws__(QuestFlagsForDifficulty, 0x80);
struct QuestFlags {
parray<QuestFlagsForDifficulty, 4> data;
@@ -556,14 +819,14 @@ struct QuestFlags {
this->update_all(z, set);
}
}
} __attribute__((packed));
} __packed_ws__(QuestFlags, 0x200);
struct QuestFlagsV1 {
parray<QuestFlagsForDifficulty, 3> data;
QuestFlagsV1& operator=(const QuestFlags& other);
operator QuestFlags() const;
} __attribute__((packed));
} __packed_ws__(QuestFlagsV1, 0x180);
struct SwitchFlags {
parray<parray<uint8_t, 0x20>, 0x12> data;
@@ -577,7 +840,7 @@ struct SwitchFlags {
inline void clear(uint8_t floor, uint16_t flag_num) {
this->data[floor][flag_num >> 3] &= ~(0x80 >> (flag_num & 7));
}
} __attribute__((packed));
} __packed_ws__(SwitchFlags, 0x240);
struct BattleRules {
enum class TechDiskMode : uint8_t {
@@ -696,7 +959,7 @@ struct BattleRules {
bool operator==(const BattleRules& other) const = default;
bool operator!=(const BattleRules& other) const = default;
} __attribute__((packed));
} __packed_ws__(BattleRules, 0x30);
struct ChallengeTemplateDefinition {
uint32_t level;
@@ -710,29 +973,50 @@ struct ChallengeTemplateDefinition {
const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index);
struct SymbolChat {
struct SymbolChatFacePart {
uint8_t type = 0xFF; // FF = no part in this slot
uint8_t x = 0;
uint8_t y = 0;
// Bits: ------VH (V = reverse vertical, H = reverse horizontal)
uint8_t flags = 0;
} __packed_ws__(SymbolChatFacePart, 4);
template <bool IsBigEndian>
struct SymbolChatT {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
// Bits: ----------------------DMSSSCCCFF
// S = sound, C = face color, F = face shape, D = capture, M = mute sound
/* 00 */ le_uint32_t spec = 0;
/* 00 */ U32T spec = 0;
// Corner objects are specified in reading order ([0] is the top-left one).
// Bits (each entry): ---VHCCCZZZZZZZZ
// V = reverse vertical, H = reverse horizontal, C = color, Z = object
// If Z is all 1 bits (0xFF), no corner object is rendered.
/* 04 */ parray<le_uint16_t, 4> corner_objects;
struct FacePart {
uint8_t type = 0xFF; // FF = no part in this slot
uint8_t x = 0;
uint8_t y = 0;
// Bits: ------VH (V = reverse vertical, H = reverse horizontal)
uint8_t flags = 0;
} __attribute__((packed));
/* 0C */ parray<FacePart, 12> face_parts;
/* 04 */ parray<U16T, 4> corner_objects;
/* 0C */ parray<SymbolChatFacePart, 12> face_parts;
/* 3C */
SymbolChat();
} __attribute__((packed));
SymbolChatT()
: spec(0),
corner_objects(0x00FF),
face_parts() {}
operator SymbolChatT<!IsBigEndian>() const {
SymbolChatT<!IsBigEndian> ret;
ret.spec = this->spec.load();
for (size_t z = 0; z < this->corner_objects.size(); z++) {
ret.corner_objects[z] = this->corner_objects[z].load();
}
ret.face_parts = this->face_parts;
return ret;
}
} __packed__;
using SymbolChat = SymbolChatT<false>;
using SymbolChatBE = SymbolChatT<true>;
check_struct_size(SymbolChat, 0x3C);
check_struct_size(SymbolChatBE, 0x3C);
struct RecentSwitchFlags {
uint64_t flag_nums = 0xFFFFFFFFFFFFFFFF;
+1 -1
View File
@@ -701,7 +701,7 @@ static HandlerResult S_B2(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
ses->log.info("Wrote code from server to file %s", output_filename.c_str());
#ifdef HAVE_RESOURCE_FILE
using FooterT = S_ExecuteCode_Footer_B2<IsBigEndian>;
using FooterT = S_ExecuteCode_FooterT_B2<IsBigEndian>;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
// TODO: Support SH-4 disassembly too
+1 -1
View File
@@ -837,7 +837,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
}
string port_name = login_port_name_for_version(this->version());
S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}};
S_Reconnect_19 reconnect_cmd = {0, s->name_to_port_config.at(port_name)->port, 0};
// If the client is on a virtual connection, we can use any address
// here and they should be able to connect back to the game server
+8 -9
View File
@@ -43,7 +43,7 @@ shared_ptr<const QuestCategoryIndex::Category> QuestCategoryIndex::at(uint32_t c
// GCI decoding logic
template <bool IsBigEndian>
struct PSOMemCardDLQFileEncryptedHeader {
struct PSOMemCardDLQFileEncryptedHeaderT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T round2_seed;
@@ -54,12 +54,11 @@ struct PSOMemCardDLQFileEncryptedHeader {
le_uint32_t decompressed_size;
le_uint32_t round3_seed;
// Data follows here.
} __attribute__((packed));
struct PSOVMSDLQFileEncryptedHeader : PSOMemCardDLQFileEncryptedHeader<false> {
} __attribute__((packed));
struct PSOGCIDLQFileEncryptedHeader : PSOMemCardDLQFileEncryptedHeader<true> {
} __attribute__((packed));
} __packed__;
using PSOVMSDLQFileEncryptedHeader = PSOMemCardDLQFileEncryptedHeaderT<false>;
using PSOGCIDLQFileEncryptedHeader = PSOMemCardDLQFileEncryptedHeaderT<true>;
check_struct_size(PSOVMSDLQFileEncryptedHeader, 0x10);
check_struct_size(PSOGCIDLQFileEncryptedHeader, 0x10);
template <bool IsBigEndian>
string decrypt_download_quest_data_section(
@@ -73,7 +72,7 @@ string decrypt_download_quest_data_section(
// not at the beginning. Presumably they did this because the system,
// character, and Guild Card files are a constant size, but download quest
// files can vary in size.
using HeaderT = PSOMemCardDLQFileEncryptedHeader<IsBigEndian>;
using HeaderT = PSOMemCardDLQFileEncryptedHeaderT<IsBigEndian>;
auto* header = reinterpret_cast<HeaderT*>(decrypted.data());
PSOV2Encryption round2_crypt(header->round2_seed);
round2_crypt.encrypt_t<IsBigEndian>(
@@ -193,7 +192,7 @@ string find_seed_and_decrypt_download_quest_data_section(
struct PSODownloadQuestHeader {
le_uint32_t size;
le_uint32_t encryption_seed;
} __attribute__((packed));
} __packed_ws__(PSODownloadQuestHeader, 8);
VersionedQuest::VersionedQuest(
uint32_t quest_number,
+1 -1
View File
@@ -117,7 +117,7 @@ static string format_and_indent_data(const void* data, size_t size, uint64_t sta
struct UnknownF8F2Entry {
parray<le_float, 4> unknown_a1;
} __attribute__((packed));
} __packed_ws__(UnknownF8F2Entry, 0x10);
struct QuestScriptOpcodeDefinition {
struct Argument {
+5 -5
View File
@@ -16,7 +16,7 @@ struct PSOQuestHeaderDCNTE {
/* 000C */ le_uint32_t unused;
/* 0010 */ pstring<TextEncoding::SJIS, 0x10> name;
/* 0020 */
} __attribute__((packed));
} __packed_ws__(PSOQuestHeaderDCNTE, 0x20);
struct PSOQuestHeaderDC { // Same format for DC v1 and v2
/* 0000 */ le_uint32_t code_offset;
@@ -30,7 +30,7 @@ struct PSOQuestHeaderDC { // Same format for DC v1 and v2
/* 0034 */ pstring<TextEncoding::MARKED, 0x80> short_description;
/* 00B4 */ pstring<TextEncoding::MARKED, 0x120> long_description;
/* 01D4 */
} __attribute__((packed));
} __packed_ws__(PSOQuestHeaderDC, 0x1D4);
struct PSOQuestHeaderPC {
/* 0000 */ le_uint32_t code_offset;
@@ -44,7 +44,7 @@ struct PSOQuestHeaderPC {
/* 0054 */ pstring<TextEncoding::UTF16, 0x80> short_description;
/* 0154 */ pstring<TextEncoding::UTF16, 0x120> long_description;
/* 0394 */
} __attribute__((packed));
} __packed_ws__(PSOQuestHeaderPC, 0x394);
// TODO: Is the XB quest header format the same as on GC? If not, make a
// separate struct; if so, rename this struct to V3.
@@ -61,7 +61,7 @@ struct PSOQuestHeaderGC {
/* 0034 */ pstring<TextEncoding::MARKED, 0x80> short_description;
/* 00B4 */ pstring<TextEncoding::MARKED, 0x120> long_description;
/* 01D4 */
} __attribute__((packed));
} __packed_ws__(PSOQuestHeaderGC, 0x1D4);
struct PSOQuestHeaderBB {
/* 0000 */ le_uint32_t code_offset;
@@ -78,7 +78,7 @@ struct PSOQuestHeaderBB {
/* 0058 */ pstring<TextEncoding::UTF16, 0x80> short_description;
/* 0158 */ pstring<TextEncoding::UTF16, 0x120> long_description;
/* 0398 */
} __attribute__((packed));
} __packed_ws__(PSOQuestHeaderBB, 0x398);
Episode episode_for_quest_episode_number(uint8_t episode_number);
+2 -2
View File
@@ -97,7 +97,7 @@ void RareItemSet::ParsedRELData::parse_t(StringReader r, bool is_v1) {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
uint32_t root_offset = r.pget<U32T>(r.size() - 0x10);
const auto& root = r.pget<Offsets<IsBigEndian>>(root_offset);
const auto& root = r.pget<OffsetsT<IsBigEndian>>(root_offset);
StringReader monsters_r = r.sub(root.monster_rares_offset);
for (size_t z = 0; z < (is_v1 ? 0x33 : 0x65); z++) {
@@ -125,7 +125,7 @@ std::string RareItemSet::ParsedRELData::serialize_t(bool is_v1) const {
static const PackedDrop empty_drop;
Offsets<IsBigEndian> root;
OffsetsT<IsBigEndian> root;
root.box_count = this->box_rares.size();
StringWriter w;
+7 -3
View File
@@ -64,17 +64,21 @@ protected:
PackedDrop() = default;
explicit PackedDrop(const ExpandedDrop&);
ExpandedDrop expand() const;
} __attribute__((packed));
} __packed_ws__(PackedDrop, 4);
template <bool IsBigEndian>
struct Offsets {
struct OffsetsT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ U32T monster_rares_offset; // -> parray<PackedDrop, 0x65> (or 0x33 on v1)
/* 04 */ U32T box_count; // Usually 30 (0x1E)
/* 08 */ U32T box_areas_offset; // -> parray<uint8_t, 0x1E>
/* 0C */ U32T box_rares_offset; // -> parray<PackedDrop, 0x1E>
/* 10 */
} __attribute__((packed));
} __packed__;
using Offsets = OffsetsT<false>;
using OffsetsBE = OffsetsT<true>;
check_struct_size(Offsets, 0x10);
check_struct_size(OffsetsBE, 0x10);
struct BoxRare {
uint8_t area;
+66 -7
View File
@@ -1715,7 +1715,7 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
l->battle_record->add_player(
lobby_data,
existing_p->inventory,
existing_p->disp.to_dcpcv3(c->language(), c->language()),
existing_p->disp.to_dcpcv3<false>(c->language(), c->language()),
c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0);
}
}
@@ -3208,7 +3208,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
default:
throw logic_error("player data command not implemented for version");
}
player->inventory.decode_from_client(c);
player->inventory.decode_from_client(c->version());
c->channel.language = player->inventory.language;
c->license->save();
@@ -3258,7 +3258,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
if (is_v1_or_v2(c->version())) {
bb_player->disp.stats = player->disp.stats;
} else {
bb_player->disp.stats.advance_to_level(bb_player->disp.visual.char_class, player->disp.stats.level, s->level_table);
s->level_table->advance_to_level(bb_player->disp.stats, player->disp.stats.level, bb_player->disp.visual.char_class);
bb_player->disp.stats.char_stats.atp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::POWER) * 2;
bb_player->disp.stats.char_stats.mst += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::MIND) * 2;
bb_player->disp.stats.char_stats.evp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::EVADE) * 2;
@@ -3276,7 +3276,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
bb_player->choice_search_config = player->choice_search_config;
try {
Client::save_character_file(filename, c->system_file(), bb_player);
send_text_message(c, "$C7Character data saved");
send_text_message(c, "$C7Character data saved\n(basic only)");
} catch (const exception& e) {
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
}
@@ -3291,6 +3291,65 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
}
}
static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
if (!c->pending_character_export) {
c->log.warning("No pending export is present");
return;
}
unique_ptr<Client::PendingCharacterExport> pending_export = std::move(c->pending_character_export);
c->pending_character_export.reset();
string filename;
if (pending_export->is_bb_conversion) {
filename = Client::character_filename(
pending_export->license->bb_username,
pending_export->character_index);
} else {
filename = Client::backup_character_filename(
pending_export->license->serial_number,
pending_export->character_index);
}
auto s = c->require_server_state();
if (s->player_files_manager->get_character(filename)) {
send_text_message(c, "$C6The target player\nis currently loaded.\nSign off in Blue\nBurst and try again.");
return;
}
shared_ptr<PSOBBCharacterFile> bb_char;
switch (c->version()) {
case Version::GC_V3: {
auto gc_char = check_size_t<PSOGCCharacterFile::Character>(data);
bb_char = PSOBBCharacterFile::create_from_gc(gc_char);
break;
}
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
case Version::GC_NTE:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::XB_V3:
case Version::BB_V4:
default:
throw logic_error("extended player data command not implemented for version");
}
bb_char->inventory.decode_from_client(c->version());
bb_char->disp.visual.version = 4;
bb_char->disp.visual.name_color_checksum = 0x00000000;
try {
Client::save_character_file(filename, c->system_file(), bb_char);
send_text_message(c, "$C7Character data saved\n(full save file)");
} catch (const exception& e) {
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
}
}
static void on_6x_C9_CB(shared_ptr<Client> c, uint16_t command, uint32_t flag, string& data) {
check_size_v(data.size(), 4, 0xFFFF);
if ((data.size() > 0x400) && (command != 0x6C) && (command != 0x6D)) {
@@ -3655,7 +3714,7 @@ static void on_ED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
}
case 0x06ED: {
const auto& cmd = check_size_t<C_UpdateTechMenu_BB_06ED>(data);
p->tech_menu_config = cmd.tech_menu;
p->tech_menu_shortcut_entries = cmd.tech_menu;
break;
}
case 0x07ED: {
@@ -4379,7 +4438,7 @@ static void on_0C_C1_E7_EC(shared_ptr<Client> c, uint16_t command, uint32_t, str
shared_ptr<Lobby> game;
if ((c->version() == Version::DC_NTE) || (c->version() == Version::DC_V1_11_2000_PROTOTYPE)) {
const auto& cmd = check_size_t<C_CreateGame_DCNTE<TextEncoding::SJIS>>(data);
const auto& cmd = check_size_t<C_CreateGame_DCNTE>(data);
game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, GameMode::NORMAL, 0, true);
} else {
@@ -5277,7 +5336,7 @@ static on_command_t handlers[0x100][NUM_VERSIONS - 2] = {
/* 2E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 30 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 30 */ {on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30},
/* 31 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 32 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 33 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
+13 -13
View File
@@ -1231,7 +1231,7 @@ static void on_symbol_chat(shared_ptr<Client> c, uint8_t command, uint8_t flag,
template <bool SenderIsBigEndian>
static void on_word_select_t(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
const auto& cmd = check_size_t<G_WordSelect_6x74<SenderIsBigEndian>>(data, size);
const auto& cmd = check_size_t<G_WordSelectT_6x74<SenderIsBigEndian>>(data, size);
if (c->can_chat && (cmd.client_id == c->lobby_client_id)) {
if (command_is_private(command)) {
return;
@@ -1282,12 +1282,12 @@ static void on_word_select_t(shared_ptr<Client> c, uint8_t command, uint8_t, voi
}
if (is_big_endian(lc->version())) {
G_WordSelect_6x74<true> out_cmd = {
G_WordSelectBE_6x74 out_cmd = {
subcommand, cmd.size, cmd.client_id.load(),
s->word_select_table->translate(cmd.message, from_version, lc_version)};
send_command_t(lc, 0x60, 0x00, out_cmd);
} else {
G_WordSelect_6x74<false> out_cmd = {
G_WordSelect_6x74 out_cmd = {
subcommand, cmd.size, cmd.client_id.load(),
s->word_select_table->translate(cmd.message, from_version, lc_version)};
send_command_t(lc, 0x60, 0x00, out_cmd);
@@ -2982,7 +2982,7 @@ static void on_activate_timed_switch(shared_ptr<Client> c, uint8_t command, uint
}
static void on_battle_scores(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
const auto& cmd = check_size_t<G_BattleScores_6x7F<false>>(data, size);
const auto& cmd = check_size_t<G_BattleScores_6x7F>(data, size);
if (command_is_private(command)) {
return;
@@ -2992,7 +2992,7 @@ static void on_battle_scores(shared_ptr<Client> c, uint8_t command, uint8_t, voi
return;
}
G_BattleScores_6x7F<true> sw_cmd;
G_BattleScoresBE_6x7F sw_cmd;
sw_cmd.header.subcommand = 0x7F;
sw_cmd.header.size = cmd.header.size;
sw_cmd.header.unused = 0;
@@ -3026,8 +3026,8 @@ static void on_dragon_actions(shared_ptr<Client> c, uint8_t command, uint8_t, vo
return;
}
G_DragonBossActions_GC_6x12 sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id},
cmd.unknown_a2, cmd.unknown_a3, cmd.unknown_a4, cmd.x.load(), cmd.z.load()}};
G_DragonBossActions_GC_6x12 sw_cmd = {{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id},
cmd.unknown_a2, cmd.unknown_a3, cmd.unknown_a4, cmd.x.load(), cmd.z.load()};
bool sender_is_be = is_big_endian(c->version());
for (auto lc : l->clients) {
if (lc && (lc != c)) {
@@ -3051,14 +3051,14 @@ static void on_gol_dragon_actions(shared_ptr<Client> c, uint8_t command, uint8_t
return;
}
G_GolDragonBossActions_GC_6xA8 sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id},
G_GolDragonBossActions_GC_6xA8 sw_cmd = {{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id},
cmd.unknown_a2,
cmd.unknown_a3,
cmd.unknown_a4,
cmd.x.load(),
cmd.z.load(),
cmd.unknown_a5,
0}};
0};
bool sender_is_be = is_big_endian(c->version());
for (auto lc : l->clients) {
if (lc && (lc != c)) {
@@ -3095,7 +3095,7 @@ static void on_update_enemy_state(shared_ptr<Client> c, uint8_t command, uint8_t
l->log.info("E-%hX updated to damage=%hu game_flags=%08" PRIX32, cmd.enemy_index.load(), enemy.total_damage, enemy.game_flags);
}
G_UpdateEnemyState_GC_6x0A sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.total_damage, cmd.flags.load()}};
G_UpdateEnemyState_GC_6x0A sw_cmd = {{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.total_damage, cmd.flags.load()};
bool sender_is_be = is_big_endian(c->version());
for (auto lc : l->clients) {
if (lc && (lc != c)) {
@@ -3752,7 +3752,7 @@ static void on_battle_level_up_bb(shared_ptr<Client> c, uint8_t, uint8_t, void*
auto lp = lc->character();
uint32_t target_level = lp->disp.stats.level + cmd.num_levels;
uint32_t before_exp = lp->disp.stats.experience;
lp->disp.stats.advance_to_level(lp->disp.visual.char_class, target_level, s->level_table);
s->level_table->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.char_class);
send_give_experience(lc, lp->disp.stats.experience - before_exp);
send_level_up(lc);
}
@@ -4626,10 +4626,10 @@ void on_subcommand_multi(shared_ptr<Client> c, uint8_t command, uint8_t flag, st
if (header->size != 0) {
cmd_size = header->size << 2;
} else {
if (offset + sizeof(G_ExtendedHeader<G_UnusedHeader>) > data.size()) {
if (offset + sizeof(G_ExtendedHeaderT<G_UnusedHeader>) > data.size()) {
throw runtime_error("insufficient data remaining for next extended subcommand header");
}
const auto* ext_header = reinterpret_cast<const G_ExtendedHeader<G_UnusedHeader>*>(data.data() + offset);
const auto* ext_header = reinterpret_cast<const G_ExtendedHeaderT<G_UnusedHeader>*>(data.data() + offset);
cmd_size = ext_header->size;
if (cmd_size < 8) {
throw runtime_error("extended subcommand header has size < 8");
+96 -13
View File
@@ -388,7 +388,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
ret->disp.config[z] = config[z];
}
ret->disp.stats.reset_to_base(ret->disp.visual.char_class, level_table);
level_table->reset_to_base(ret->disp.stats, ret->disp.visual.char_class);
ret->disp.technique_levels_v1.clear(0xFF);
if (ret->disp.visual.class_flags & 0x80) {
ret->disp.technique_levels_v1[0] = 0x00; // Forces start with Foie Lv.1
@@ -404,7 +404,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
ret->symbol_chats[z] = PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS[z].to_entry(language);
}
for (size_t z = 0; z < PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG.size(); z++) {
ret->tech_menu_config[z] = PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG[z];
ret->tech_menu_shortcut_entries[z] = PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG[z];
}
return ret;
}
@@ -418,16 +418,99 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_preview(
guild_card_number, language, preview.visual, preview.name.decode(language), level_table);
}
PSOBBCharacterFile::SymbolChatEntry PSOBBCharacterFile::DefaultSymbolChatEntry::to_entry(uint8_t language) const {
SymbolChatEntry ret;
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_gc(const PSOGCCharacterFile::Character& gc) {
auto ret = make_shared<PSOBBCharacterFile>();
ret->inventory = gc.inventory;
uint8_t language = ret->inventory.language;
ret->disp = gc.disp.to_bb(language, language);
ret->creation_timestamp = gc.creation_timestamp.load();
ret->play_time_seconds = gc.play_time_seconds.load();
ret->option_flags = gc.option_flags.load();
ret->save_count = gc.save_count.load();
ret->quest_flags = gc.quest_flags;
ret->death_count = gc.death_count.load();
ret->bank = gc.bank;
ret->guild_card = gc.guild_card;
for (size_t z = 0; z < std::min<size_t>(ret->symbol_chats.size(), gc.symbol_chats.size()); z++) {
auto& ret_sc = ret->symbol_chats[z];
const auto& gc_sc = gc.symbol_chats[z];
ret_sc.present = gc_sc.present.load();
ret_sc.name.encode(gc_sc.name.decode(language), language);
ret_sc.spec = gc_sc.spec;
}
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), gc.shortcuts.size()); z++) {
ret->shortcuts[z] = gc.shortcuts[z].convert<false, TextEncoding::UTF16>(language);
}
ret->auto_reply.encode(gc.auto_reply.decode(language), language);
ret->info_board.encode(gc.info_board.decode(language), language);
ret->battle_records = gc.battle_records;
ret->unknown_a4 = gc.unknown_a4;
ret->challenge_records = gc.challenge_records;
for (size_t z = 0; z < std::min<size_t>(ret->tech_menu_shortcut_entries.size(), gc.tech_menu_shortcut_entries.size()); z++) {
ret->tech_menu_shortcut_entries[z] = gc.tech_menu_shortcut_entries[z].load();
}
ret->choice_search_config = gc.choice_search_config;
ret->unknown_a6 = gc.unknown_a6;
for (size_t z = 0; z < std::min<size_t>(ret->quest_counters.size(), gc.quest_counters.size()); z++) {
ret->quest_counters[z] = gc.quest_counters[z].load();
}
ret->offline_battle_records = gc.offline_battle_records;
ret->unknown_a7 = gc.unknown_a7;
return ret;
}
PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const {
uint8_t language = this->inventory.language;
PSOGCCharacterFile::Character ret;
ret.inventory = this->inventory;
ret.disp = this->disp.to_dcpcv3<true>(language, language);
ret.creation_timestamp = this->creation_timestamp.load();
ret.play_time_seconds = this->play_time_seconds.load();
ret.option_flags = this->option_flags.load();
ret.save_count = this->save_count.load();
ret.quest_flags = this->quest_flags;
ret.death_count = this->death_count.load();
ret.bank = this->bank;
ret.guild_card = this->guild_card;
for (size_t z = 0; z < std::min<size_t>(ret.symbol_chats.size(), this->symbol_chats.size()); z++) {
auto& ret_sc = ret.symbol_chats[z];
const auto& gc_sc = this->symbol_chats[z];
ret_sc.present = gc_sc.present.load();
ret_sc.name.encode(gc_sc.name.decode(language), language);
ret_sc.spec = gc_sc.spec;
}
for (size_t z = 0; z < std::min<size_t>(ret.shortcuts.size(), this->shortcuts.size()); z++) {
ret.shortcuts[z] = this->shortcuts[z].convert<true, TextEncoding::MARKED>(language);
}
ret.auto_reply.encode(this->auto_reply.decode(language), language);
ret.info_board.encode(this->info_board.decode(language), language);
ret.battle_records = this->battle_records;
ret.unknown_a4 = this->unknown_a4;
ret.challenge_records = this->challenge_records;
for (size_t z = 0; z < std::min<size_t>(ret.tech_menu_shortcut_entries.size(), this->tech_menu_shortcut_entries.size()); z++) {
ret.tech_menu_shortcut_entries[z] = this->tech_menu_shortcut_entries[z].load();
}
ret.choice_search_config = this->choice_search_config;
ret.unknown_a6 = this->unknown_a6;
for (size_t z = 0; z < std::min<size_t>(ret.quest_counters.size(), this->quest_counters.size()); z++) {
ret.quest_counters[z] = this->quest_counters[z].load();
}
ret.offline_battle_records = this->offline_battle_records;
ret.unknown_a7 = this->unknown_a7;
return ret;
}
SaveFileSymbolChatEntryBB PSOBBCharacterFile::DefaultSymbolChatEntry::to_entry(uint8_t language) const {
SaveFileSymbolChatEntryBB ret;
ret.present = 1;
ret.name.encode(this->language_to_name.at(language), language);
ret.data.spec = this->spec;
ret.spec.spec = this->spec;
for (size_t z = 0; z < 4; z++) {
ret.data.corner_objects[z] = this->corner_objects[z];
ret.spec.corner_objects[z] = this->corner_objects[z];
}
for (size_t z = 0; z < 12; z++) {
ret.data.face_parts[z] = this->face_parts[z];
ret.spec.face_parts[z] = this->face_parts[z];
}
return ret;
}
@@ -617,12 +700,12 @@ void PSOBBCharacterFile::clear_all_material_usage() {
}
const array<PSOBBCharacterFile::DefaultSymbolChatEntry, 6> PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS = {
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF", "\tEHello", "\tEHallo", "\tESalut", "\tEHola", "\tB\xE4\xBD\xA0\xE5\xA5\xBD", "\tT\xE4\xBD\xA0\xE5\xA5\xBD", "\tK\xEC\x95\x88\xEB\x85\x95"}, 0x28, {0xFFFF, 0x000D, 0xFFFF, 0xFFFF}, {SymbolChat::FacePart{0x05, 0x18, 0x1D, 0x00}, {0x05, 0x28, 0x1D, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x95\xE3\x82\x88\xE3\x81\x86\xE3\x81\xAA\xE3\x82\x89", "\tEGood-bye", "\tETschus", "\tEAu revoir", "\tEAdios", "\tB\xE5\x86\x8D\xE8\xA7\x81", "\tT\xE5\x86\x8D\xE8\xA6\x8B", "\tK\xEC\x9E\x98\xEA\xB0\x80"}, 0x74, {0x0476, 0x000C, 0xFFFF, 0xFFFF}, {SymbolChat::FacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\xB0\xE3\x82\x93\xE3\x81\x96\xE3\x83\xBC\xE3\x81\x84", "\tEHurrah!", "\tEHurra!", "\tEHourra !", "\tEHurra", "\tB\xE4\xB8\x87\xE5\xB2\x81", "\tT\xE8\x90\xAC\xE6\xAD\xB2", "\tK\xEB\xA7\x8C\xEC\x84\xB8"}, 0x28, {0x0362, 0x0362, 0xFFFF, 0xFFFF}, {SymbolChat::FacePart{0x09, 0x16, 0x1B, 0x00}, {0x09, 0x2B, 0x1B, 0x01}, {0x37, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x86\xE3\x81\x87\xEF\xBD\x9E\xE3\x82\x93", "\tECrying", "\tEIch bin sauer!", "\tEJe suis triste", "\tELlanto", "\tB\xE5\x96\x82\xEF\xBD\x9E", "\tT\xE5\x96\x82\xEF\xBD\x9E", "\tK\xEC\x9D\x91~"}, 0x74, {0x074F, 0xFFFF, 0xFFFF, 0xFFFF}, {SymbolChat::FacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x21, 0x20, 0x2E, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x8A\xE3\x81\x93\xE3\x81\xA3\xE3\x81\x9F\xEF\xBC\x81", "\tEI'm angry!", "\tEWeinen", "\tEJe suis en colere !", "\tEEnfado", "\tB\xE7\x94\x9F\xE6\xB0\x94\xE4\xBA\x86\xEF\xBC\x81", "\tT\xE7\x94\x9F\xE6\xB0\xA3\xE4\xBA\x86\xEF\xBC\x81", "\tK\xEB\x82\x98\xEC\x99\x94\xEB\x8B\xA4\xEF\xBC\x81"}, 0x5C, {0x0116, 0x0001, 0xFFFF, 0xFFFF}, {SymbolChat::FacePart{0x0B, 0x18, 0x1B, 0x01}, {0x0B, 0x28, 0x1B, 0x00}, {0x33, 0x20, 0x2A, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x9F\xE3\x81\x99\xE3\x81\x91\xE3\x81\xA6\xEF\xBC\x81", "\tEHelp me!", "\tEHilf mir!", "\tEAide-moi !", "\tEAyuda", "\tB\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tT\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tK\xEB\x8F\x84\xEC\x99\x80\xEC\xA4\x98\xEF\xBC\x81"}, 0xEC, {0x065E, 0x0138, 0xFFFF, 0xFFFF}, {SymbolChat::FacePart{0x02, 0x17, 0x1B, 0x01}, {0x02, 0x2A, 0x1B, 0x00}, {0x31, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF", "\tEHello", "\tEHallo", "\tESalut", "\tEHola", "\tB\xE4\xBD\xA0\xE5\xA5\xBD", "\tT\xE4\xBD\xA0\xE5\xA5\xBD", "\tK\xEC\x95\x88\xEB\x85\x95"}, 0x28, {0xFFFF, 0x000D, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x05, 0x18, 0x1D, 0x00}, {0x05, 0x28, 0x1D, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x95\xE3\x82\x88\xE3\x81\x86\xE3\x81\xAA\xE3\x82\x89", "\tEGood-bye", "\tETschus", "\tEAu revoir", "\tEAdios", "\tB\xE5\x86\x8D\xE8\xA7\x81", "\tT\xE5\x86\x8D\xE8\xA6\x8B", "\tK\xEC\x9E\x98\xEA\xB0\x80"}, 0x74, {0x0476, 0x000C, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\xB0\xE3\x82\x93\xE3\x81\x96\xE3\x83\xBC\xE3\x81\x84", "\tEHurrah!", "\tEHurra!", "\tEHourra !", "\tEHurra", "\tB\xE4\xB8\x87\xE5\xB2\x81", "\tT\xE8\x90\xAC\xE6\xAD\xB2", "\tK\xEB\xA7\x8C\xEC\x84\xB8"}, 0x28, {0x0362, 0x0362, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x09, 0x16, 0x1B, 0x00}, {0x09, 0x2B, 0x1B, 0x01}, {0x37, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x86\xE3\x81\x87\xEF\xBD\x9E\xE3\x82\x93", "\tECrying", "\tEIch bin sauer!", "\tEJe suis triste", "\tELlanto", "\tB\xE5\x96\x82\xEF\xBD\x9E", "\tT\xE5\x96\x82\xEF\xBD\x9E", "\tK\xEC\x9D\x91~"}, 0x74, {0x074F, 0xFFFF, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x21, 0x20, 0x2E, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x8A\xE3\x81\x93\xE3\x81\xA3\xE3\x81\x9F\xEF\xBC\x81", "\tEI'm angry!", "\tEWeinen", "\tEJe suis en colere !", "\tEEnfado", "\tB\xE7\x94\x9F\xE6\xB0\x94\xE4\xBA\x86\xEF\xBC\x81", "\tT\xE7\x94\x9F\xE6\xB0\xA3\xE4\xBA\x86\xEF\xBC\x81", "\tK\xEB\x82\x98\xEC\x99\x94\xEB\x8B\xA4\xEF\xBC\x81"}, 0x5C, {0x0116, 0x0001, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x0B, 0x18, 0x1B, 0x01}, {0x0B, 0x28, 0x1B, 0x00}, {0x33, 0x20, 0x2A, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x9F\xE3\x81\x99\xE3\x81\x91\xE3\x81\xA6\xEF\xBC\x81", "\tEHelp me!", "\tEHilf mir!", "\tEAide-moi !", "\tEAyuda", "\tB\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tT\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tK\xEB\x8F\x84\xEC\x99\x80\xEC\xA4\x98\xEF\xBC\x81"}, 0xEC, {0x065E, 0x0138, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x02, 0x17, 0x1B, 0x01}, {0x02, 0x2A, 0x1B, 0x00}, {0x31, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
};
const array<uint16_t, 20> PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG = {
+303 -259
View File
@@ -29,9 +29,9 @@ struct ShuffleTables {
};
struct PSOVMSFileHeader {
/* 0000 */ pstring<TextEncoding::SJIS, 0x10> short_desc;
/* 0010 */ pstring<TextEncoding::SJIS, 0x20> long_desc;
/* 0030 */ pstring<TextEncoding::SJIS, 0x10> creator_id;
/* 0000 */ pstring<TextEncoding::MARKED, 0x10> short_desc;
/* 0010 */ pstring<TextEncoding::MARKED, 0x20> long_desc;
/* 0030 */ pstring<TextEncoding::MARKED, 0x10> creator_id;
/* 0040 */ le_uint16_t num_icons;
/* 0042 */ le_uint16_t animation_speed;
/* 0044 */ le_uint16_t eyecatch_type;
@@ -43,7 +43,7 @@ struct PSOVMSFileHeader {
/* 0080 */ // parray<uint8_t, num_icons> icon;
bool checksum_correct() const;
} __attribute__((packed));
} __packed_ws__(PSOVMSFileHeader, 0x80);
struct PSOGCIFileHeader {
// Every PSOGC save file begins with a PSOGCIFileHeader. The first 0x40 bytes
@@ -57,7 +57,7 @@ struct PSOGCIFileHeader {
// There is a structure for this part of the header, but we don't use it.
/* 0006 */ uint8_t unused;
/* 0007 */ uint8_t image_flags;
/* 0008 */ pstring<TextEncoding::SJIS, 0x20> internal_file_name;
/* 0008 */ pstring<TextEncoding::MARKED, 0x20> internal_file_name;
/* 0028 */ be_uint32_t modification_time;
/* 002C */ be_uint32_t image_data_offset;
/* 0030 */ be_uint16_t icon_formats;
@@ -70,9 +70,9 @@ struct PSOGCIFileHeader {
/* 003C */ be_uint32_t comment_offset;
// GCI header ends here (and memcard file data begins here)
// game_name is e.g. "PSO EPISODE I & II" or "PSO EPISODE III"
/* 0040 */ pstring<TextEncoding::SJIS, 0x1C> game_name;
/* 0040 */ pstring<TextEncoding::MARKED, 0x1C> game_name;
/* 005C */ be_uint32_t embedded_seed; // Used in some of Ralf's quest packs
/* 0060 */ pstring<TextEncoding::SJIS, 0x20> file_name;
/* 0060 */ pstring<TextEncoding::MARKED, 0x20> file_name;
/* 0080 */ parray<uint8_t, 0x1800> banner;
/* 1880 */ parray<uint8_t, 0x800> icon;
// data_size specifies the number of bytes remaining in the file. In all cases
@@ -90,7 +90,7 @@ struct PSOGCIFileHeader {
bool is_ep12() const;
bool is_ep3() const;
bool is_nte() const;
} __attribute__((packed));
} __packed_ws__(PSOGCIFileHeader, 0x2088);
struct PSOGCSystemFile {
/* 0000 */ be_uint32_t checksum;
@@ -110,7 +110,7 @@ struct PSOGCSystemFile {
// Guild Card files.
/* 0118 */ be_uint32_t creation_timestamp;
/* 011C */
} __attribute__((packed));
} __packed_ws__(PSOGCSystemFile, 0x11C);
struct PSOGCEp3SystemFile {
/* 0000 */ PSOGCSystemFile base;
@@ -118,196 +118,99 @@ struct PSOGCEp3SystemFile {
/* 011D */ parray<uint8_t, 11> unknown_a2;
/* 0128 */ be_uint32_t unknown_a3;
/* 012C */
} __attribute__((packed));
} __packed_ws__(PSOGCEp3SystemFile, 0x12C);
struct PSOBBMinimalSystemFile {
/* 0000 */ be_uint32_t checksum = 0;
/* 0004 */ be_int16_t music_volume = 0;
/* 0006 */ int8_t sound_volume = 0;
/* 0007 */ uint8_t language = 0;
/* 0008 */ be_int32_t server_time_delta_frames = 1728000;
/* 000C */ be_uint16_t udp_behavior = 0; // 0 = auto, 1 = on, 2 = off
/* 000E */ be_uint16_t surround_sound_enabled = 0;
/* 0010 */ parray<uint8_t, 0x0100> event_flags;
/* 0110 */ le_uint32_t creation_timestamp = 0;
/* 0114 */
} __attribute__((packed));
template <bool IsBigEndian, TextEncoding Encoding, size_t NameLength>
struct SaveFileSymbolChatEntryT {
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
struct PSOBBTeamMembership {
/* 0000 */ le_uint32_t team_master_guild_card_number = 0;
/* 0004 */ le_uint32_t team_id = 0;
/* 0008 */ le_uint32_t unknown_a5 = 0;
/* 000C */ le_uint32_t unknown_a6 = 0;
/* 0010 */ uint8_t privilege_level = 0;
/* 0011 */ uint8_t unknown_a7 = 0;
/* 0012 */ uint8_t unknown_a8 = 0;
/* 0013 */ uint8_t unknown_a9 = 0;
/* 0014 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> team_name;
/* 0034 */ parray<le_uint16_t, 0x20 * 0x20> flag_data;
/* 0834 */ le_uint32_t reward_flags = 0;
/* 0838 */
/* PC:GC:BB */
/* 00:00:00 */ U32T present;
/* 04:04:04 */ pstring<Encoding, NameLength> name;
/* 34:1C:2C */ SymbolChatT<IsBigEndian> spec;
/* 70:58:68 */
} __packed__;
using SaveFileSymbolChatEntryPC = SaveFileSymbolChatEntryT<false, TextEncoding::UTF16, 0x18>;
using SaveFileSymbolChatEntryGC = SaveFileSymbolChatEntryT<true, TextEncoding::MARKED, 0x18>;
using SaveFileSymbolChatEntryBB = SaveFileSymbolChatEntryT<false, TextEncoding::UTF16, 0x14>;
check_struct_size(SaveFileSymbolChatEntryPC, 0x70);
check_struct_size(SaveFileSymbolChatEntryGC, 0x58);
check_struct_size(SaveFileSymbolChatEntryBB, 0x68);
PSOBBTeamMembership() = default;
} __attribute__((packed));
template <bool IsBigEndian>
struct WordSelectMessageT {
using U16T = std::conditional_t<IsBigEndian, be_uint16_t, le_uint16_t>;
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
struct PSOBBBaseSystemFile {
/* 0000 */ PSOBBMinimalSystemFile base;
/* 0114 */ parray<uint8_t, 0x016C> key_config;
/* 0280 */ parray<uint8_t, 0x0038> joystick_config;
/* 02B8 */
U16T num_tokens = 0;
U16T target_type = 0;
parray<U16T, 8> tokens;
U32T numeric_parameter = 0;
U32T unknown_a4 = 0;
static const std::array<uint8_t, 0x016C> DEFAULT_KEY_CONFIG;
static const std::array<uint8_t, 0x0038> DEFAULT_JOYSTICK_CONFIG;
operator WordSelectMessageT<!IsBigEndian>() const {
WordSelectMessageT<!IsBigEndian> ret;
ret.num_tokens = this->num_tokens.load();
ret.target_type = this->target_type.load();
for (size_t z = 0; z < this->tokens.size(); z++) {
ret.tokens[z] = this->tokens[z].load();
}
ret.numeric_parameter = this->numeric_parameter.load();
ret.unknown_a4 = this->unknown_a4.load();
return ret;
}
} __packed__;
using WordSelectMessage = WordSelectMessageT<false>;
using WordSelectMessageBE = WordSelectMessageT<true>;
check_struct_size(WordSelectMessage, 0x1C);
check_struct_size(WordSelectMessageBE, 0x1C);
PSOBBBaseSystemFile();
} __attribute__((packed));
template <bool IsBigEndian, TextEncoding Encoding>
struct SaveFileChatShortcutEntryT {
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
struct PSOBBFullSystemFile {
/* 0000 */ PSOBBBaseSystemFile base;
/* 02B8 */ PSOBBTeamMembership team_membership;
/* 0AF0 */
union Definition {
pstring<Encoding, 0x50> text;
WordSelectMessageT<IsBigEndian> word_select;
SymbolChatT<IsBigEndian> symbol_chat;
PSOBBFullSystemFile() = default;
} __attribute__((packed));
Definition() : text() {}
Definition(const Definition& other) : text(other.text) {}
Definition& operator=(const Definition& other) {
this->text = other.text;
return *this;
}
} __packed__;
struct PSOBBCharacterFile {
struct SymbolChatEntry {
/* 00 */ le_uint32_t present = 0;
/* 04 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x14> name;
/* 2C */ SymbolChat data;
/* 68 */
} __attribute__((packed));
/* GC:BB */
/* 00:00 */ U32T type; // 1 = text, 2 = word select, 3 = symbol chat
/* 04:04 */ Definition definition;
/* 54:A4 */
struct DefaultSymbolChatEntry {
std::array<const char*, 8> language_to_name;
uint32_t spec;
std::array<uint16_t, 4> corner_objects;
std::array<SymbolChat::FacePart, 12> face_parts;
SymbolChatEntry to_entry(uint8_t language) const;
};
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataBB disp;
/* 04DC */ le_uint32_t flags = 0;
/* 04E0 */ le_uint32_t creation_timestamp = 0;
/* 04E4 */ le_uint32_t signature = 0xC87ED5B1;
/* 04E8 */ le_uint32_t play_time_seconds = 0;
/* 04EC */ le_uint32_t option_flags = 0x00040058;
/* 04F0 */ parray<uint8_t, 4> unknown_a2;
/* 04F4 */ QuestFlags quest_flags;
/* 06F4 */ le_uint32_t death_count = 0;
/* 06F8 */ PlayerBank bank;
/* 19C0 */ GuildCardBB guild_card;
/* 1AC8 */ le_uint32_t unknown_a3 = 0;
/* 1ACC */ parray<SymbolChatEntry, 12> symbol_chats;
/* 1FAC */ parray<uint8_t, 0x0A40> shortcuts;
/* 29EC */ pstring<TextEncoding::UTF16, 0x00AC> auto_reply;
/* 2B44 */ pstring<TextEncoding::UTF16, 0x00AC> info_board;
/* 2C9C */ PlayerRecords_Battle<false> battle_records;
/* 2CB4 */ parray<uint8_t, 4> unknown_a4;
/* 2CB8 */ PlayerRecordsBB_Challenge challenge_records;
/* 2DF8 */ parray<le_uint16_t, 0x0014> tech_menu_config;
/* 2E20 */ ChoiceSearchConfig choice_search_config;
/* 2E38 */ parray<uint8_t, 0x0010> unknown_a6;
/* 2E48 */ parray<le_uint32_t, 0x0010> quest_counters;
/* 2E88 */ parray<uint8_t, 0x1C> unknown_a7;
/* 2EA4 */
static const std::array<DefaultSymbolChatEntry, 6> DEFAULT_SYMBOL_CHATS;
static const std::array<uint16_t, 20> DEFAULT_TECH_MENU_CONFIG;
PSOBBCharacterFile() = default;
PlayerDispDataBBPreview to_preview() const;
static std::shared_ptr<PSOBBCharacterFile> create_from_config(
uint32_t guild_card_number,
uint8_t language,
const PlayerVisualConfig& visual,
const std::string& name,
std::shared_ptr<const LevelTable> level_table);
static std::shared_ptr<PSOBBCharacterFile> create_from_preview(
uint32_t guild_card_number,
uint8_t language,
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
void add_item(const ItemData& item, const ItemData::StackLimits& limits);
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits);
void add_meseta(uint32_t amount);
void remove_meseta(uint32_t amount, bool allow_overdraft);
uint8_t get_technique_level(uint8_t which) const; // Returns FF or 00-1D
void set_technique_level(uint8_t which, uint8_t level);
enum class MaterialType : int8_t {
HP = -2,
TP = -1,
POWER = 0,
MIND = 1,
EVADE = 2,
DEF = 3,
LUCK = 4,
};
uint8_t get_material_usage(MaterialType which) const;
void set_material_usage(MaterialType which, uint8_t usage);
void clear_all_material_usage();
} __attribute__((packed));
struct PSOBBGuildCardFile {
struct Entry {
/* 0000 */ GuildCardBB data;
/* 0108 */ pstring<TextEncoding::UTF16, 0x58> comment;
/* 01B8 */ parray<uint8_t, 0x4> unknown_a1;
/* 01BC */
void clear();
} __attribute__((packed));
/* 0000 */ PSOBBMinimalSystemFile system_file;
/* 0114 */ parray<GuildCardBB, 0x1C> blocked;
/* 1DF4 */ parray<uint8_t, 0x180> unknown_a2;
/* 1F74 */ parray<Entry, 0x69> entries;
/* D590 */
PSOBBGuildCardFile() = default;
uint32_t checksum() const;
} __attribute__((packed));
struct PSOGCSaveFileSymbolChatEntry {
/* 00 */ be_uint32_t present;
/* 04 */ pstring<TextEncoding::SJIS, 0x18> name;
/* 1C */ be_uint32_t spec;
struct CornerObject {
uint8_t type;
uint8_t flags_color;
} __attribute__((packed));
/* 20 */ parray<CornerObject, 4> corner_objects;
struct FacePart {
uint8_t type;
uint8_t x;
uint8_t y;
uint8_t flags;
} __attribute__((packed));
/* 28 */ parray<FacePart, 12> face_parts;
/* 58 */
} __attribute__((packed));
struct PSOPCSaveFileSymbolChatEntry {
/* 00 */ le_uint32_t present;
/* 04 */ pstring<TextEncoding::UTF16, 0x18> name;
/* 34 */ SymbolChat data;
/* 70 */
} __attribute__((packed));
struct PSOGCSaveFileChatShortcutEntry {
/* 00 */ be_uint32_t present_type;
/* 04 */ parray<uint8_t, 0x50> definition;
/* 54 */
} __attribute__((packed));
template <bool RetIsBigEndian, TextEncoding RetEncoding>
SaveFileChatShortcutEntryT<RetIsBigEndian, RetEncoding> convert(uint8_t language) const {
SaveFileChatShortcutEntryT<RetIsBigEndian, RetEncoding> ret;
ret.type = this->type.load();
switch (ret.type) {
case 1:
ret.definition.text.encode(this->definition.text.decode(language), language);
break;
case 2:
// TODO: We should translate the message across PSO versions if
// possible, but this is a lossy process :|
ret.definition.word_select = this->definition.word_select;
break;
case 3:
ret.definition.symbol_chat = this->definition.symbol_chat;
break;
}
return ret;
}
} __packed__;
using SaveFileShortcutEntryGC = SaveFileChatShortcutEntryT<true, TextEncoding::MARKED>;
using SaveFileShortcutEntryBB = SaveFileChatShortcutEntryT<false, TextEncoding::UTF16>;
check_struct_size(SaveFileShortcutEntryGC, 0x54);
check_struct_size(SaveFileShortcutEntryBB, 0xA4);
struct PSOGCCharacterFile {
/* 00000 */ be_uint32_t checksum;
@@ -315,18 +218,18 @@ struct PSOGCCharacterFile {
// This structure is internally split into two by the game. The offsets here
// are relative to the start of this structure (first column), and relative
// to the start of the second internal structure (second column).
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 0000:---- */ PlayerInventoryBE inventory;
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
// Known bits in the flags field:
// 00000001: Character was not saved after disconnecting (and the message
// about items being deleted is shown in the select menu)
// 00000002: Used for something, but it's not known what it does
/* 041C:0000 */ be_uint32_t flags;
/* 0420:0004 */ be_uint32_t creation_timestamp;
/* 041C:0000 */ be_uint32_t flags = 0;
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
// The signature field holds the value 0xA205B064, which is 2718281828 in
// decimal - approximately e * 10^9. It's unknown why Sega chose this value.
/* 0424:0008 */ be_uint32_t signature;
/* 0428:000C */ be_uint32_t play_time_seconds;
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
/* 0428:000C */ be_uint32_t play_time_seconds = 0;
// This field is a collection of several flags and small values. The known
// fields are:
// ------zA BCDEFG-- HHHIIIJJ KLMNOPQR
@@ -344,32 +247,32 @@ struct PSOGCCharacterFile {
// P = Cursor position (0 = saved; 1 = non-saved)
// Q = Button config (0 = normal; 1 = L/R reversed)
// R = Map direction (0 = non-fixed; 1 = fixed)
/* 042C:0010 */ be_uint32_t option_flags;
/* 0430:0014 */ be_uint32_t save_count;
/* 0434:0018 */ parray<uint8_t, 0x1C> unknown_a4;
/* 0450:0034 */ parray<uint8_t, 0x10> unknown_a5;
/* 042C:0010 */ be_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ be_uint32_t save_count = 0;
/* 0434:0018 */ parray<uint8_t, 0x1C> unknown_a2;
/* 0450:0034 */ parray<uint8_t, 0x10> unknown_a3;
/* 0460:0044 */ QuestFlags quest_flags;
/* 0660:0244 */ be_uint32_t death_count;
/* 0664:0248 */ PlayerBank bank;
/* 192C:1510 */ GuildCardGC guild_card;
/* 19BC:15A0 */ parray<PSOGCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 1DDC:19C0 */ parray<PSOGCSaveFileChatShortcutEntry, 20> chat_shortcuts;
/* 246C:2050 */ pstring<TextEncoding::SJIS, 0xAC> auto_reply;
/* 2518:20FC */ pstring<TextEncoding::SJIS, 0xAC> info_board;
/* 25C4:21A8 */ PlayerRecords_Battle<true> battle_records;
/* 25DC:21C0 */ parray<uint8_t, 4> unknown_a2;
/* 25E0:21C4 */ PlayerRecordsV3_Challenge<true> challenge_records;
/* 0660:0244 */ be_uint32_t death_count = 0;
/* 0664:0248 */ PlayerBankBE bank;
/* 192C:1510 */ GuildCardGCBE guild_card;
/* 19BC:15A0 */ parray<SaveFileSymbolChatEntryGC, 12> symbol_chats;
/* 1DDC:19C0 */ parray<SaveFileShortcutEntryGC, 20> shortcuts;
/* 246C:2050 */ pstring<TextEncoding::MARKED, 0xAC> auto_reply;
/* 2518:20FC */ pstring<TextEncoding::MARKED, 0xAC> info_board;
/* 25C4:21A8 */ PlayerRecordsBattleBE battle_records;
/* 25DC:21C0 */ parray<uint8_t, 4> unknown_a4;
/* 25E0:21C4 */ PlayerRecordsChallengeV3BE challenge_records;
/* 26E0:22C4 */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
/* 2708:22EC */ ChoiceSearchConfig choice_search_config;
/* 2708:22EC */ ChoiceSearchConfigBE choice_search_config;
/* 2720:2304 */ parray<uint8_t, 0x10> unknown_a6;
/* 2730:2314 */ parray<be_uint32_t, 0x10> quest_counters;
/* 2770:2354 */ PlayerRecords_Battle<true> offline_battle_records;
/* 2788:236C */ parray<uint8_t, 4> unknown_f5;
/* 278C:2370 */ be_uint32_t unknown_f6;
/* 2790:2374 */ be_uint32_t unknown_f7;
/* 2794:2378 */ be_uint32_t unknown_f8;
/* 2770:2354 */ PlayerRecordsBattleBE offline_battle_records;
/* 2788:236C */ parray<uint8_t, 4> unknown_a7;
/* 278C:2370 */ be_uint32_t unknown_f6 = 0;
/* 2790:2374 */ be_uint32_t unknown_f7 = 0;
/* 2794:2378 */ be_uint32_t unknown_f8 = 0;
/* 2798:237C */
} __attribute__((packed));
} __packed_ws__(Character, 0x2798);
/* 00004 */ parray<Character, 7> characters;
/* 1152C */ pstring<TextEncoding::ASCII, 0x10> serial_number; // As %08X (not decimal)
/* 1153C */ pstring<TextEncoding::ASCII, 0x10> access_key;
@@ -378,7 +281,7 @@ struct PSOGCCharacterFile {
/* 11564 */ be_uint32_t save_count;
/* 11568 */ be_uint32_t round2_seed;
/* 1156C */
} __attribute__((packed));
} __packed_ws__(PSOGCCharacterFile, 0x1156C);
struct PSOGCEp3CharacterFile {
/* 00000 */ be_uint32_t checksum; // crc32 of this field (as 0) through end of struct
@@ -408,22 +311,22 @@ struct PSOGCEp3CharacterFile {
// remove the bank in Ep3 because they would have to change too much code.
/* 0864:0448 */ be_uint32_t num_bank_items;
/* 0868:044C */ be_uint32_t bank_meseta;
/* 086C:0450 */ parray<PlayerBankItem, 4> bank_items;
/* 08CC:04B0 */ GuildCardGC guild_card;
/* 095C:0540 */ parray<PSOGCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 0D7C:0960 */ parray<PSOGCSaveFileChatShortcutEntry, 20> chat_shortcuts;
/* 140C:0FF0 */ pstring<TextEncoding::SJIS, 0xAC> auto_reply;
/* 14B8:109C */ pstring<TextEncoding::SJIS, 0xAC> info_board;
/* 086C:0450 */ parray<PlayerBankItemBE, 4> bank_items;
/* 08CC:04B0 */ GuildCardGCBE guild_card;
/* 095C:0540 */ parray<SaveFileSymbolChatEntryGC, 12> symbol_chats;
/* 0D7C:0960 */ parray<SaveFileShortcutEntryGC, 20> chat_shortcuts;
/* 140C:0FF0 */ pstring<TextEncoding::MARKED, 0xAC> auto_reply;
/* 14B8:109C */ pstring<TextEncoding::MARKED, 0xAC> info_board;
// In this struct, place_counts[0] is win_count and [1] is loss_count
/* 1564:1148 */ PlayerRecords_Battle<true> battle_records;
/* 1564:1148 */ PlayerRecordsBattleBE battle_records;
/* 157C:1160 */ parray<uint8_t, 4> unknown_a10;
/* 1580:1164 */ PlayerRecordsV3_Challenge<true>::Stats challenge_record_stats;
/* 1580:1164 */ PlayerRecordsChallengeV3BE::Stats challenge_record_stats;
/* 1658:123C */ Episode3::PlayerConfig ep3_config;
/* 39A8:358C */ be_uint32_t unknown_a11;
/* 39AC:3590 */ be_uint32_t unknown_a12;
/* 39B0:3594 */ be_uint32_t unknown_a13;
/* 39B4:3598 */
} __attribute__((packed));
} __packed_ws__(Character, 0x39B4);
/* 00004 */ parray<Character, 7> characters;
/* 193F0 */ pstring<TextEncoding::ASCII, 0x10> serial_number; // As %08X (not decimal)
/* 19400 */ pstring<TextEncoding::ASCII, 0x10> access_key; // As 12 ASCII characters (decimal)
@@ -446,39 +349,26 @@ struct PSOGCEp3CharacterFile {
/* 1942C */ parray<uint8_t, 0x80> card_rank_override_flags;
/* 194AC */ be_uint32_t round2_seed;
/* 194B0 */
} __attribute__((packed));
} __packed_ws__(PSOGCEp3CharacterFile, 0x194B0);
struct PSOGCGuildCardFile {
/* 0000 */ be_uint32_t checksum;
/* 0004 */ parray<uint8_t, 0xC0> unknown_a1;
struct GuildCardBE {
// Note: This struct (up through offset 0x90) is identical to GuildCardGC
// except for the 32-bit fields, which are big-endian here.
/* 0000 */ be_uint32_t player_tag; // == 0x00000001 (not 0x00010000)
/* 0004 */ be_uint32_t guild_card_number;
/* 0008 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 0020 */ pstring<TextEncoding::MARKED, 0x6C> description;
/* 008C */ uint8_t present;
/* 008D */ uint8_t language;
/* 008E */ uint8_t section_id;
/* 008F */ uint8_t char_class;
/* 0090 */
} __attribute__((packed));
struct GuildCardEntry {
/* 0000 */ GuildCardBE base;
/* 0000 */ GuildCardGCBE base;
/* 0090 */ uint8_t unknown_a1;
/* 0091 */ uint8_t unknown_a2;
/* 0092 */ uint8_t unknown_a3;
/* 0093 */ uint8_t unknown_a4;
/* 0094 */ pstring<TextEncoding::MARKED, 0x6C> comment;
/* 0100 */
} __attribute__((packed));
} __packed_ws__(GuildCardEntry, 0x100);
/* 00C4 */ parray<GuildCardEntry, 0xD2> entries;
/* D2C4 */ parray<GuildCardBE, 0x1C> blocked_senders;
/* D2C4 */ parray<GuildCardGCBE, 0x1C> blocked_senders;
/* E284 */ be_uint32_t creation_timestamp;
/* E288 */ be_uint32_t round2_seed;
/* E28C */
} __attribute__((packed));
} __packed_ws__(PSOGCGuildCardFile, 0xE28C);
struct PSOGCSnapshotFile {
/* 00000 */ be_uint32_t checksum;
@@ -497,7 +387,7 @@ struct PSOGCSnapshotFile {
bool checksum_correct() const;
Image decode_image() const;
} __attribute__((packed));
} __packed_ws__(PSOGCSnapshotFile, 0x1818C);
template <bool IsBigEndian>
std::string decrypt_data_section(const void* data_section, size_t size, uint32_t round1_seed, size_t max_decrypt_bytes = 0) {
@@ -681,7 +571,7 @@ struct PSOPCCreationTimeFile { // PSO______FLS
/* 0624 */ le_uint32_t creation_timestamp;
/* 0628 */ parray<uint8_t, 0xDD8> unused2;
/* 1400 */
} __attribute__((packed));
} __packed_ws__(PSOPCCreationTimeFile, 0x1400);
struct PSOPCSystemFile { // PSO______COM
/* 0000 */ le_uint32_t checksum;
@@ -696,7 +586,7 @@ struct PSOPCSystemFile { // PSO______COM
/* 012C */ le_uint32_t round1_seed;
/* 0130 */ parray<uint8_t, 0xD0> end_padding;
/* 0200 */
} __attribute__((packed));
} __packed_ws__(PSOPCSystemFile, 0x200);
struct PSOPCGuildCardFile { // PSO______GUD
/* 0000 */ le_uint32_t checksum;
@@ -706,7 +596,7 @@ struct PSOPCGuildCardFile { // PSO______GUD
/* 7988 */ le_uint32_t round2_seed;
/* 798C */ parray<uint8_t, 0x74> end_padding;
/* 7A00 */
} __attribute__((packed));
} __packed_ws__(PSOPCGuildCardFile, 0x7A00);
struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
/* 00000 */ le_uint32_t signature; // 'CAEN' (stored as 4E 45 41 43)
@@ -729,7 +619,7 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
// TODO: Figure out what this is. On GC, this is where the bank data goes.
/* 0438 */ parray<uint8_t, 0x7D4> unknown_a2;
/* 0C0C */ GuildCardPC guild_card;
/* 0CFC */ parray<PSOPCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 0CFC */ parray<SaveFileSymbolChatEntryPC, 12> symbol_chats;
// TODO: Figure out what this is. On GC, this is where chat shortcuts and
// challenge/battle records go.
/* 123C */ parray<uint8_t, 0xAA0> unknown_a3;
@@ -739,14 +629,168 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
/* 1D40 */ pstring<TextEncoding::ASCII, 0x10> access_key; // As decimal
/* 1D50 */ le_uint32_t round2_seed;
/* 1D54 */
} __attribute__((packed));
} __packed_ws__(Character, 0x1D54);
/* 0004 */ Character character;
/* 1D58 */ parray<uint8_t, 0x3C> unused;
/* 1D94 */
} __attribute__((packed));
} __packed_ws__(CharacterEntry, 0x1D94);
/* 00440 */ parray<CharacterEntry, 0x80> entries;
/* ECE40 */
} __attribute__((packed));
} __packed_ws__(PSOPCCharacterFile, 0xECE40);
struct PSOBBMinimalSystemFile {
/* 0000 */ be_uint32_t checksum = 0;
/* 0004 */ be_int16_t music_volume = 0;
/* 0006 */ int8_t sound_volume = 0;
/* 0007 */ uint8_t language = 0;
/* 0008 */ be_int32_t server_time_delta_frames = 1728000;
/* 000C */ be_uint16_t udp_behavior = 0; // 0 = auto, 1 = on, 2 = off
/* 000E */ be_uint16_t surround_sound_enabled = 0;
/* 0010 */ parray<uint8_t, 0x0100> event_flags;
/* 0110 */ le_uint32_t creation_timestamp = 0;
/* 0114 */
} __packed_ws__(PSOBBMinimalSystemFile, 0x114);
struct PSOBBTeamMembership {
/* 0000 */ le_uint32_t team_master_guild_card_number = 0;
/* 0004 */ le_uint32_t team_id = 0;
/* 0008 */ le_uint32_t unknown_a5 = 0;
/* 000C */ le_uint32_t unknown_a6 = 0;
/* 0010 */ uint8_t privilege_level = 0;
/* 0011 */ uint8_t unknown_a7 = 0;
/* 0012 */ uint8_t unknown_a8 = 0;
/* 0013 */ uint8_t unknown_a9 = 0;
/* 0014 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> team_name;
/* 0034 */ parray<le_uint16_t, 0x20 * 0x20> flag_data;
/* 0834 */ le_uint32_t reward_flags = 0;
/* 0838 */
PSOBBTeamMembership() = default;
} __packed_ws__(PSOBBTeamMembership, 0x838);
struct PSOBBBaseSystemFile {
/* 0000 */ PSOBBMinimalSystemFile base;
/* 0114 */ parray<uint8_t, 0x016C> key_config;
/* 0280 */ parray<uint8_t, 0x0038> joystick_config;
/* 02B8 */
static const std::array<uint8_t, 0x016C> DEFAULT_KEY_CONFIG;
static const std::array<uint8_t, 0x0038> DEFAULT_JOYSTICK_CONFIG;
PSOBBBaseSystemFile();
} __packed_ws__(PSOBBBaseSystemFile, 0x2B8);
struct PSOBBFullSystemFile {
/* 0000 */ PSOBBBaseSystemFile base;
/* 02B8 */ PSOBBTeamMembership team_membership;
/* 0AF0 */
PSOBBFullSystemFile() = default;
} __packed_ws__(PSOBBFullSystemFile, 0xAF0);
struct PSOBBCharacterFile {
struct DefaultSymbolChatEntry {
std::array<const char*, 8> language_to_name;
uint32_t spec;
std::array<uint16_t, 4> corner_objects;
std::array<SymbolChatFacePart, 12> face_parts;
SaveFileSymbolChatEntryBB to_entry(uint8_t language) const;
};
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataBB disp;
/* 04DC */ le_uint32_t flags = 0;
/* 04E0 */ le_uint32_t creation_timestamp = 0;
/* 04E4 */ le_uint32_t signature = 0xC87ED5B1;
/* 04E8 */ le_uint32_t play_time_seconds = 0;
/* 04EC */ le_uint32_t option_flags = 0x00040058;
/* 04F0 */ le_uint32_t save_count = 0;
/* 04F4 */ QuestFlags quest_flags;
/* 06F4 */ le_uint32_t death_count = 0;
/* 06F8 */ PlayerBank bank;
/* 19C0 */ GuildCardBB guild_card;
/* 1AC8 */ le_uint32_t unknown_a3 = 0;
/* 1ACC */ parray<SaveFileSymbolChatEntryBB, 0x0C> symbol_chats;
/* 1FAC */ parray<SaveFileShortcutEntryBB, 0x10> shortcuts;
/* 29EC */ pstring<TextEncoding::UTF16, 0x00AC> auto_reply;
/* 2B44 */ pstring<TextEncoding::UTF16, 0x00AC> info_board;
/* 2C9C */ PlayerRecordsBattle battle_records;
/* 2CB4 */ parray<uint8_t, 4> unknown_a4;
/* 2CB8 */ PlayerRecordsChallengeBB challenge_records;
/* 2DF8 */ parray<le_uint16_t, 0x0014> tech_menu_shortcut_entries;
/* 2E20 */ ChoiceSearchConfig choice_search_config;
/* 2E38 */ parray<uint8_t, 0x0010> unknown_a6;
/* 2E48 */ parray<le_uint32_t, 0x0010> quest_counters;
/* 2E88 */ PlayerRecordsBattle offline_battle_records;
/* 2EA0 */ parray<uint8_t, 4> unknown_a7;
/* 2EA4 */
static const std::array<DefaultSymbolChatEntry, 6> DEFAULT_SYMBOL_CHATS;
static const std::array<uint16_t, 20> DEFAULT_TECH_MENU_CONFIG;
PSOBBCharacterFile() = default;
PlayerDispDataBBPreview to_preview() const;
static std::shared_ptr<PSOBBCharacterFile> create_from_config(
uint32_t guild_card_number,
uint8_t language,
const PlayerVisualConfig& visual,
const std::string& name,
std::shared_ptr<const LevelTable> level_table);
static std::shared_ptr<PSOBBCharacterFile> create_from_preview(
uint32_t guild_card_number,
uint8_t language,
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
static std::shared_ptr<PSOBBCharacterFile> create_from_gc(const PSOGCCharacterFile::Character& gc_char);
void add_item(const ItemData& item, const ItemData::StackLimits& limits);
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits);
void add_meseta(uint32_t amount);
void remove_meseta(uint32_t amount, bool allow_overdraft);
uint8_t get_technique_level(uint8_t which) const; // Returns FF or 00-1D
void set_technique_level(uint8_t which, uint8_t level);
enum class MaterialType : int8_t {
HP = -2,
TP = -1,
POWER = 0,
MIND = 1,
EVADE = 2,
DEF = 3,
LUCK = 4,
};
uint8_t get_material_usage(MaterialType which) const;
void set_material_usage(MaterialType which, uint8_t usage);
void clear_all_material_usage();
PSOGCCharacterFile::Character to_gc() const;
} __packed_ws__(PSOBBCharacterFile, 0x2EA4);
struct PSOBBGuildCardFile {
struct Entry {
/* 0000 */ GuildCardBB data;
/* 0108 */ pstring<TextEncoding::UTF16, 0x58> comment;
/* 01B8 */ parray<uint8_t, 0x4> unknown_a1;
/* 01BC */
void clear();
} __packed_ws__(Entry, 0x1BC);
/* 0000 */ PSOBBMinimalSystemFile system_file;
/* 0114 */ parray<GuildCardBB, 0x1C> blocked;
/* 1DF4 */ parray<uint8_t, 0x180> unknown_a2;
/* 1F74 */ parray<Entry, 0x69> entries;
/* D590 */
PSOBBGuildCardFile() = default;
uint32_t checksum() const;
} __packed_ws__(PSOBBGuildCardFile, 0xD590);
// This format is specific to newserv and is no longer used, but remains here
// for backward compatibility.
@@ -756,11 +800,11 @@ struct LegacySavedPlayerDataBB { // .nsc file format
/* 0000 */ be_uint64_t signature = SIGNATURE_V1;
/* 0008 */ parray<uint8_t, 0x20> unused;
/* 0028 */ PlayerRecords_Battle<false> battle_records;
/* 0028 */ PlayerRecordsBattle battle_records;
/* 0040 */ PlayerDispDataBBPreview preview;
/* 00BC */ pstring<TextEncoding::UTF16, 0x00AC> auto_reply;
/* 0214 */ PlayerBank bank;
/* 14DC */ PlayerRecordsBB_Challenge challenge_records;
/* 14DC */ PlayerRecordsChallengeBB challenge_records;
/* 161C */ PlayerDispDataBB disp;
/* 17AC */ pstring<TextEncoding::UTF16, 0x0058> guild_card_description;
/* 185C */ pstring<TextEncoding::UTF16, 0x00AC> info_board;
@@ -769,9 +813,9 @@ struct LegacySavedPlayerDataBB { // .nsc file format
/* 1D04 */ QuestFlags quest_flags;
/* 1F04 */ le_uint32_t death_count;
/* 1F08 */ parray<le_uint32_t, 0x0016> quest_counters;
/* 1F60 */ parray<le_uint16_t, 0x0014> tech_menu_config;
/* 1F60 */ parray<le_uint16_t, 0x0014> tech_menu_shortcut_entries;
/* 1F88 */
} __attribute__((packed));
} __packed_ws__(LegacySavedPlayerDataBB, 0x1F88);
// This format is specific to newserv and is no longer used, but remains here
// for backward compatibility.
@@ -784,10 +828,10 @@ struct LegacySavedAccountDataBB { // .nsa file format
/* D648 */ PSOBBFullSystemFile system_file;
/* E138 */ le_uint32_t unused;
/* E13C */ le_uint32_t option_flags;
/* E140 */ parray<uint8_t, 0x0A40> shortcuts;
/* EB80 */ parray<PSOBBCharacterFile::SymbolChatEntry, 12> symbol_chats;
/* E140 */ parray<SaveFileShortcutEntryBB, 0x10> shortcuts;
/* EB80 */ parray<SaveFileSymbolChatEntryBB, 0x0C> symbol_chats;
/* F060 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> team_name;
/* F080 */
} __attribute__((packed));
} __packed_ws__(LegacySavedAccountDataBB, 0xF080);
std::string encode_psobb_hangame_credentials(const std::string& user_id, const std::string& token, const std::string& unused = "");
+55 -24
View File
@@ -155,11 +155,11 @@ static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyrigh
static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.";
static const char* bb_pm_server_copyright = "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM.";
S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4>
S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4>
prepare_server_init_contents_console(
uint32_t server_key, uint32_t client_key, uint8_t flags) {
bool initial_connection = (flags & SendServerInitFlag::IS_INITIAL_CONNECTION);
S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4> cmd;
S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4> cmd;
cmd.basic_cmd.copyright.encode(initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright);
cmd.basic_cmd.server_key = server_key;
cmd.basic_cmd.client_key = client_key;
@@ -205,13 +205,13 @@ void send_server_init_dc_pc_v3(shared_ptr<Client> c, uint8_t flags) {
}
}
S_ServerInitWithAfterMessage_BB_03_9B<0xB4>
S_ServerInitWithAfterMessageT_BB_03_9B<0xB4>
prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key,
uint8_t flags) {
bool use_secondary_message = (flags & SendServerInitFlag::USE_SECONDARY_MESSAGE);
S_ServerInitWithAfterMessage_BB_03_9B<0xB4> cmd;
S_ServerInitWithAfterMessageT_BB_03_9B<0xB4> cmd;
cmd.basic_cmd.copyright.encode(use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright);
cmd.basic_cmd.server_key = server_key;
cmd.basic_cmd.client_key = client_key;
@@ -534,7 +534,7 @@ bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t
}
void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
S_Reconnect_19 cmd = {{address, port, 0}};
S_Reconnect_19 cmd = {address, port, 0};
send_command_t(c, is_patch(c->version()) ? 0x14 : 0x19, 0x00, cmd);
}
@@ -1056,7 +1056,7 @@ void send_simple_mail(shared_ptr<ServerState> s, uint32_t from_guild_card_number
template <TextEncoding NameEncoding, TextEncoding MessageEncoding>
void send_info_board_t(shared_ptr<Client> c) {
vector<S_InfoBoardEntry_D8<NameEncoding, MessageEncoding>> entries;
vector<S_InfoBoardEntryT_D8<NameEncoding, MessageEncoding>> entries;
auto l = c->require_lobby();
for (const auto& other_c : l->clients) {
if (!other_c.get()) {
@@ -1125,7 +1125,7 @@ void send_card_search_result_t(
auto s = c->require_server_state();
string port_name = lobby_port_name_for_version(c->version());
S_GuildCardSearchResult<CommandHeaderT, Encoding> cmd;
S_GuildCardSearchResultT<CommandHeaderT, Encoding> cmd;
cmd.player_tag = 0x00010000;
cmd.searcher_guild_card_number = c->license->serial_number;
cmd.result_guild_card_number = result->license->serial_number;
@@ -1413,7 +1413,7 @@ void send_game_menu_t(
bool show_tournaments_only) {
auto s = c->require_server_state();
vector<S_GameMenuEntry<Encoding>> entries;
vector<S_GameMenuEntryT<Encoding>> entries;
{
auto& e = entries.emplace_back();
e.menu_id = MenuID::GAME;
@@ -1770,8 +1770,8 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
auto& p = cmd.players[z];
populate_lobby_data_for_client(p.lobby_data, wc, c);
p.inventory = wc_p->inventory;
p.inventory.encode_for_client(c);
p.disp = wc_p->disp.to_dcpcv3(c->language(), p.inventory.language);
p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
p.disp = wc_p->disp.to_dcpcv3<false>(c->language(), p.inventory.language);
p.disp.enforce_lobby_join_limits_for_version(c->version());
auto& e = cmd.entries[z];
@@ -1813,7 +1813,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
auto& p = cmd.players[client_id];
p.lobby_data = entry.lobby_data;
p.inventory = entry.inventory;
p.inventory.encode_for_client(c);
p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
p.disp = entry.disp;
p.disp.enforce_lobby_join_limits_for_version(c->version());
@@ -1840,7 +1840,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
auto& cmd_e = cmd.entries[z];
populate_lobby_data_for_client(cmd_p.lobby_data, other_c, c);
cmd_p.inventory = other_p->inventory;
cmd_p.disp = other_p->disp.to_dcpcv3(c->language(), cmd_p.inventory.language);
cmd_p.disp = other_p->disp.to_dcpcv3<false>(c->language(), cmd_p.inventory.language);
cmd_p.disp.enforce_lobby_join_limits_for_version(c->version());
cmd_e.player_tag = 0x00010000;
@@ -1960,7 +1960,7 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
auto other_p = l->clients[x]->character();
auto& cmd_p = cmd.players_ep3[x];
cmd_p.inventory = other_p->inventory;
cmd_p.inventory.encode_for_client(c);
cmd_p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
cmd_p.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(other_p->disp, c->language(), other_p->inventory.language);
cmd_p.disp.enforce_lobby_join_limits_for_version(c->version());
if (s->version_name_colors) {
@@ -2048,7 +2048,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
lobby_block = l->block;
}
S_JoinLobby<LobbyFlags, LobbyDataT, DispDataT> cmd;
S_JoinLobbyT<LobbyFlags, LobbyDataT, DispDataT> cmd;
cmd.lobby_flags.client_id = c->lobby_client_id;
cmd.lobby_flags.leader_id = l->leader_id;
cmd.lobby_flags.disable_udp = 0x01;
@@ -2076,7 +2076,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
e.inventory.encode_for_client(c);
e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) {
e.disp = convert_player_disp_data<DispDataT>(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language);
} else {
@@ -2151,7 +2151,7 @@ void send_join_lobby_xb(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cl
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
e.inventory.encode_for_client(c);
e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
e.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits_for_version(c->version());
if (s->version_name_colors) {
@@ -2199,7 +2199,7 @@ void send_join_lobby_dc_nte(shared_ptr<Client> c, shared_ptr<Lobby> l,
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
e.inventory.encode_for_client(c);
e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) {
e.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language);
} else {
@@ -2319,8 +2319,39 @@ void send_self_leave_notification(shared_ptr<Client> c) {
send_command_t(c, 0x69, c->lobby_client_id, cmd);
}
void send_get_player_info(shared_ptr<Client> c) {
send_command(c, (c->version() == Version::DC_NTE) ? 0x8D : 0x95, 0x00);
static bool send_get_extended_player_info(shared_ptr<Client> c) {
// TODO: Support extended player info on other versions.
if (c->version() != Version::GC_V3) {
return false;
}
auto s = c->require_server_state();
if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) ||
c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY)) {
return false;
}
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c)]() {
auto c = wc.lock();
if (!c) {
return;
}
try {
auto s = c->require_server_state();
auto fn = s->function_code_index->get_patch("GetExtendedPlayerInfo", c->config.specific_version);
send_function_call(c, fn);
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
} catch (const exception& e) {
c->log.warning("Failed to send extended player info request: %s", e.what());
send_get_player_info(c, false);
}
});
return true;
}
void send_get_player_info(shared_ptr<Client> c, bool request_extended) {
if (!request_extended || !send_get_extended_player_info(c)) {
send_command(c, (c->version() == Version::DC_NTE) ? 0x8D : 0x95, 0x00);
}
}
////////////////////////////////////////////////////////////////////////////////
@@ -3151,7 +3182,7 @@ template <typename RulesT>
void send_ep3_tournament_details_t(
shared_ptr<Client> c,
shared_ptr<const Episode3::Tournament> tourn) {
S_TournamentGameDetailsBase_Ep3_E3<RulesT> cmd;
S_TournamentGameDetailsBaseT_Ep3_E3<RulesT> cmd;
auto vm = tourn->get_map()->version(c->language());
cmd.name.encode(tourn->get_name(), c->language());
cmd.map_name.encode(vm->map->name.decode(vm->language), c->language());
@@ -3203,7 +3234,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
auto tourn = tourn_match ? tourn_match->tournament.lock() : nullptr;
if (tourn) {
S_TournamentGameDetailsBase_Ep3_E3<RulesT> cmd;
S_TournamentGameDetailsBaseT_Ep3_E3<RulesT> cmd;
cmd.name.encode(l->name, c->language());
auto vm = tourn->get_map()->version(c->language());
@@ -3222,7 +3253,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
if (primary_lobby) {
auto serial_number_to_client = primary_lobby->clients_by_serial_number();
using TeamEntryT = typename S_TournamentGameDetailsBase_Ep3_E3<RulesT>::TeamEntry;
using TeamEntryT = typename S_TournamentGameDetailsBaseT_Ep3_E3<RulesT>::TeamEntry;
auto describe_team = [&](TeamEntryT& team_entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
team_entry.team_name.encode(team->name, c->language());
for (size_t z = 0; z < team->players.size(); z++) {
@@ -3263,7 +3294,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
send_command_t(c, 0xE3, flag, cmd);
} else {
S_GameInformationBase_Ep3_E1<RulesT> cmd;
S_GameInformationBaseT_Ep3_E1<RulesT> cmd;
cmd.game_name.encode(l->name, c->language());
if (primary_lobby) {
size_t num_players = 0;
@@ -3292,7 +3323,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
// spectator count in the info window object. To account for this, we send
// a mostly-blank E3 to set the spectator count, followed by an E1 with
// the correct data.
S_TournamentGameDetailsBase_Ep3_E3<RulesT> cmd_E3;
S_TournamentGameDetailsBaseT_Ep3_E3<RulesT> cmd_E3;
cmd_E3.num_spectators = num_spectators;
send_command_t(c, 0xE3, 0x04, cmd_E3);
+3 -3
View File
@@ -119,10 +119,10 @@ enum SendServerInitFlag {
USE_SECONDARY_MESSAGE = 0x02,
};
S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4>
S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4>
prepare_server_init_contents_console(
uint32_t server_key, uint32_t client_key, uint8_t flags);
S_ServerInitWithAfterMessage_BB_03_9B<0xB4>
S_ServerInitWithAfterMessageT_BB_03_9B<0xB4>
prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key,
@@ -287,7 +287,7 @@ void send_update_lobby_data_bb(std::shared_ptr<Client> c);
void send_player_join_notification(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
void send_player_leave_notification(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
void send_self_leave_notification(std::shared_ptr<Client> c);
void send_get_player_info(std::shared_ptr<Client> c);
void send_get_player_info(std::shared_ptr<Client> c, bool request_extended = false);
void send_execute_item_trade(std::shared_ptr<Client> c, const std::vector<ItemData>& items);
void send_execute_card_trade(std::shared_ptr<Client> c, const std::vector<std::pair<uint32_t, uint32_t>>& card_to_count);
+29 -2
View File
@@ -11,6 +11,15 @@
#include <stdexcept>
#include <string>
#define __packed__ __attribute__((packed))
#define check_struct_size(StructT, Size) \
static_assert(sizeof(StructT) >= Size, "Structure size is too small"); \
static_assert(sizeof(StructT) <= Size, "Structure size is too large")
#define __packed_ws__(StructT, Size) \
__packed__; \
check_struct_size(StructT, Size)
// Conversion functions
class TextTranscoder {
@@ -112,6 +121,12 @@ struct parray {
this->operator=(std::move(other));
}
template <typename FromT>
requires std::is_convertible_v<FromT, ItemT>
parray(const parray<FromT, Count>& other) {
this->operator=(other);
}
template <size_t OtherCount>
parray(const parray<ItemT, OtherCount>& s) {
this->operator=(s);
@@ -203,6 +218,18 @@ struct parray {
return *this;
}
template <typename FromT>
requires std::is_convertible_v<FromT, ItemT>
parray& operator=(const parray<FromT, Count>& s) {
for (size_t x = 0; x < Count; x++) {
const FromT& src_item = s.items[x];
ItemT& dest_item = this->items[x];
static_assert(!std::is_const_v<ItemT>, "ItemT is const");
dest_item = src_item;
}
return *this;
}
template <size_t OtherCount>
parray& operator=(const parray<ItemT, OtherCount>& s) {
if (OtherCount <= Count) {
@@ -267,7 +294,7 @@ struct parray {
}
return true;
}
} __attribute__((packed));
} __packed__;
template <typename ItemT, size_t Count>
struct bcarray {
@@ -672,7 +699,7 @@ struct pstring {
// Note: The contents of a pstring do not have to be null-terminated, so there
// is no .c_str() function.
} __attribute__((packed));
} __packed__;
// Helper functions
+10 -5
View File
@@ -32,7 +32,7 @@ static vector<vector<RetT>> read_indirect_table(const StringReader& base_r, size
}
template <bool IsBigEndian>
struct NonWindowsRoot {
struct NonWindowsRootT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T strings_table;
U32T table1;
@@ -41,7 +41,12 @@ struct NonWindowsRoot {
U32T table4;
U32T article_types_table;
U32T table6;
} __attribute__((packed));
} __packed__;
using NonWindowsRoot = NonWindowsRootT<false>;
using NonWindowsRootBE = NonWindowsRootT<true>;
check_struct_size(NonWindowsRoot, 0x1C);
check_struct_size(NonWindowsRootBE, 0x1C);
struct PCV2Root {
le_uint32_t unknown_a1;
@@ -52,7 +57,7 @@ struct PCV2Root {
le_uint32_t table4;
le_uint32_t article_types_table;
le_uint32_t table6;
} __attribute__((packed));
} __packed_ws__(PCV2Root, 0x20);
struct BBRoot {
le_uint32_t table1;
@@ -61,7 +66,7 @@ struct BBRoot {
le_uint32_t table4;
le_uint32_t article_types_table;
le_uint32_t table6;
} __attribute__((packed));
} __packed_ws__(BBRoot, 0x18);
template <bool IsBigEndian, size_t StringTableCount, size_t TokenCount>
void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) {
@@ -70,7 +75,7 @@ void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis)
StringReader r(data);
uint32_t root_offset = r.pget<U32T>(r.size() - 0x10);
const auto& root = r.pget<NonWindowsRoot<IsBigEndian>>(root_offset);
const auto& root = r.pget<NonWindowsRootT<IsBigEndian>>(root_offset);
{
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T) * StringTableCount);
@@ -0,0 +1,13 @@
# r3 = dest ptr
# r4 = src ptr
# r5 = size
# Clobbers r3, r4, r5
addi r5, r5, 3
rlwinm r5, r5, 30, 2, 31 # r5 = number of words to copy
mtctr r5
subi r3, r3, 4 # r3 = r3 - 4 (so we can use stwu)
subi r4, r4, 4 # r4 = r4 - 4 (so we can use lwzu)
copy_word_again:
lwzu r5, [r4 + 4]
stwu [r3 + 4], r5
bdnz copy_word_again
@@ -0,0 +1,19 @@
# .meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_addr
data:
.data 0x803DB0E0 # malloc9
.data 0x802021D0 # get_character_file
.data 0x802021AC # get_selected_character_file_index
.data 0x805C4D80 # root_protocol (anchor: send_05)
.data 0x803DB138 # free9
.data 0x800787B0 # TProtocol_wait_send_drain
get_data_addr:
.include GetExtendedPlayerInfoGC
@@ -0,0 +1,19 @@
# .meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_addr
data:
.data 0x803DB138 # malloc9
.data 0x802021D0 # get_character_file
.data 0x802021AC # get_selected_character_file_index
.data 0x805CBD60 # root_protocol (anchor: send_05)
.data 0x803DB190 # free9
.data 0x800787B0 # TProtocol_wait_send_drain
get_data_addr:
.include GetExtendedPlayerInfoGC
@@ -0,0 +1,19 @@
# .meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_addr
data:
.data 0x803DE838 # malloc9
.data 0x80202BA0 # get_character_file
.data 0x80202B7C # get_selected_character_file_index
.data 0x805D5580 # root_protocol (anchor: send_05)
.data 0x803DE890 # free9
.data 0x8007889C # TProtocol_wait_send_drain
get_data_addr:
.include GetExtendedPlayerInfoGC
@@ -0,0 +1,19 @@
# .meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_addr
data:
.data 0x803D9E38 # malloc9
.data 0x802019D4 # get_character_file
.data 0x802019B0 # get_selected_character_file_index
.data 0x805C4488 # root_protocol (anchor: send_05)
.data 0x803D9E90 # free9
.data 0x8007848C # TProtocol_wait_send_drain
get_data_addr:
.include GetExtendedPlayerInfoGC
@@ -0,0 +1,19 @@
# .meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_addr
data:
.data 0x803DC818 # malloc9
.data 0x80202248 # get_character_file
.data 0x80202224 # get_selected_character_file_index
.data 0x805CEA50 # root_protocol (anchor: send_05)
.data 0x803DC870 # free9
.data 0x800785F0 # TProtocol_wait_send_drain
get_data_addr:
.include GetExtendedPlayerInfoGC
@@ -0,0 +1,19 @@
# .meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_addr
data:
.data 0x803DE6B8 # malloc9
.data 0x801FD950 # get_character_file
.data 0x80222C0C # get_selected_character_file_index
.data 0x805D5ED0 # root_protocol (anchor: send_05)
.data 0x803DE710 # free9
.data 0x80078748 # TProtocol_wait_send_drain
get_data_addr:
.include GetExtendedPlayerInfoGC
@@ -0,0 +1,19 @@
# .meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_addr
data:
.data 0x803DE468 # malloc9
.data 0x8020286C # get_character_file
.data 0x80202848 # get_selected_character_file_index
.data 0x805D5C70 # root_protocol (anchor: send_05)
.data 0x803DE4C0 # free9
.data 0x800786A0 # TProtocol_wait_send_drain
get_data_addr:
.include GetExtendedPlayerInfoGC
@@ -0,0 +1,19 @@
# .meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_addr
data:
.data 0x803DD328 # malloc9
.data 0x80202AB4 # get_character_file
.data 0x80202A90 # get_selected_character_file_index
.data 0x805D17C0 # root_protocol (anchor: send_05)
.data 0x803DD380 # free9
.data 0x80078820 # TProtocol_wait_send_drain
get_data_addr:
.include GetExtendedPlayerInfoGC
@@ -0,0 +1,81 @@
stwu [r1 - 0x20], r1
stw [r1 + 0x24], r12
stw [r1 + 0x08], r31
stw [r1 + 0x0C], r30
stw [r1 + 0x10], r29
stw [r1 + 0x14], r28
mflr r30
li r3, 0x279C
lwz r0, [r30]
mtctr r0
bctrl # malloc9
mr. r31, r3
beq malloc9_failed
lis r0, 0x3000
ori r0, r0, 0x9C27
stw [r31], r0 # header = 30 00 9C 27
lwz r0, [r30 + 0x04]
mtctr r0
bctrl # get_character_file
mr r28, r3
lwz r0, [r30 + 0x08]
mtctr r0
bctrl # get_selected_character_file_index
mulli r3, r3, 0x2798
addi r3, r3, 4
add r4, r3, r28 # r4 = &character_file->characters[selected_char_file_index]
addi r3, r31, 4
li r5, 0x2798
bl memcpy
mr r28, r31
li r29, 0x279C
send_again:
lwz r3, [r30 + 0x0C]
lwz r0, [r30 + 0x14]
mtctr r0
bctrl # TProtocol_wait_send_drain(root_protocol)
mr. r0, r3
bne drain_failed
lwz r3, [r30 + 0x0C]
lwz r3, [r3] # root_protocol
mr r4, r28
mr r5, r29
cmplwi r5, 0x05B4
ble skip_adjust_size
li r5, 0x05B4
skip_adjust_size:
add r28, r28, r5
sub r29, r29, r5
lwz r12, [r3 + 0x18]
lwz r12, [r12 + 0x28]
mtctr r12
bctrl # root_protocol->send(&cmd, sizeof(cmd))
cmplwi r29, 0
bne send_again
drain_failed:
mr r3, r31
lwz r0, [r30 + 0x10]
mtctr r0
bctrl # free9
li r3, 1
malloc9_failed:
lwz r28, [r1 + 0x14]
lwz r29, [r1 + 0x10]
lwz r30, [r1 + 0x0C]
lwz r31, [r1 + 0x08]
lwz r0, [r1 + 0x24]
addi r1, r1, 0x20
mtlr r0
blr
memcpy:
.include CopyDataWords
blr