implement full character backups on GC
This commit is contained in:
@@ -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
@@ -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
@@ -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");
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user