use .psochar format for BB characters
This commit is contained in:
@@ -3,8 +3,15 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "PSOProtocol.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Originally there was going to be a language-based header, but then I decided
|
||||
// against it. This string was already in use for that parser, so I didn't
|
||||
// bother changing it.
|
||||
const char* LegacySavedAccountDataBB::SIGNATURE = "newserv account file format; 7 sections present; sequential;";
|
||||
|
||||
ShuffleTables::ShuffleTables(PSOV2Encryption& crypt) {
|
||||
for (size_t x = 0; x < 0x100; x++) {
|
||||
this->forward_table[x] = x;
|
||||
@@ -189,3 +196,281 @@ void PSOBBGuildCardFile::Entry::clear() {
|
||||
uint32_t PSOBBGuildCardFile::checksum() const {
|
||||
return crc32(this, sizeof(*this));
|
||||
}
|
||||
|
||||
PSOBBSystemFile::PSOBBSystemFile(uint32_t guild_card_number)
|
||||
: PSOBBSystemFile() {
|
||||
// This field is based on 1/1/2000, not 1/1/1970, so adjust appropriately
|
||||
this->base.creation_timestamp = (now() - 946684800000000ULL) / 1000000;
|
||||
for (size_t z = 0; z < PSOBBSystemFile::DEFAULT_KEY_CONFIG.size(); z++) {
|
||||
this->key_config[z] = PSOBBSystemFile::DEFAULT_KEY_CONFIG[z];
|
||||
}
|
||||
for (size_t z = 0; z < PSOBBSystemFile::DEFAULT_JOYSTICK_CONFIG.size(); z++) {
|
||||
this->joystick_config[z] = PSOBBSystemFile::DEFAULT_JOYSTICK_CONFIG[z];
|
||||
}
|
||||
this->guild_card_number = guild_card_number;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_preview(
|
||||
uint32_t guild_card_number,
|
||||
uint8_t language,
|
||||
const PlayerDispDataBBPreview& preview,
|
||||
shared_ptr<const LevelTable> level_table) {
|
||||
shared_ptr<PSOBBCharacterFile> ret(new PSOBBCharacterFile());
|
||||
ret->disp.apply_preview(preview);
|
||||
ret->disp.stats.reset_to_base(ret->disp.visual.char_class, level_table);
|
||||
ret->inventory.language = language;
|
||||
ret->guild_card.guild_card_number = guild_card_number;
|
||||
ret->guild_card.name = ret->disp.name;
|
||||
ret->guild_card.present = 1;
|
||||
ret->guild_card.language = ret->inventory.language;
|
||||
ret->guild_card.section_id = ret->disp.visual.section_id;
|
||||
ret->guild_card.char_class = ret->disp.visual.char_class;
|
||||
for (size_t z = 0; z < PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS.size(); z++) {
|
||||
ret->symbol_chats[z] = PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS[z].to_entry();
|
||||
}
|
||||
for (size_t z = 0; z < PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG.size(); z++) {
|
||||
ret->tech_menu_config[z] = PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG[z];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PSOBBCharacterFile::SymbolChatEntry PSOBBCharacterFile::DefaultSymbolChatEntry::to_entry() const {
|
||||
SymbolChatEntry ret;
|
||||
ret.present = 1;
|
||||
ret.name.encode(this->name, 1);
|
||||
ret.data.spec = this->spec;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
ret.data.corner_objects[z] = this->corner_objects[z];
|
||||
}
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
ret.data.face_parts[z] = this->face_parts[z];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: Eliminate duplication between this function and the parallel function
|
||||
// in PlayerBank
|
||||
void PSOBBCharacterFile::add_item(const ItemData& item) {
|
||||
uint32_t pid = item.primary_identifier();
|
||||
|
||||
// Annoyingly, meseta is in the disp data, not in the inventory struct. If the
|
||||
// item is meseta, we have to modify disp instead.
|
||||
if (pid == MESETA_IDENTIFIER) {
|
||||
this->add_meseta(item.data2d);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle combinable items
|
||||
size_t combine_max = item.max_stack_size();
|
||||
if (combine_max > 1) {
|
||||
// Get the item index if there's already a stack of the same item in the
|
||||
// player's inventory
|
||||
size_t y;
|
||||
for (y = 0; y < this->inventory.num_items; y++) {
|
||||
if (this->inventory.items[y].data.primary_identifier() == item.primary_identifier()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found an existing stack, add it to the total and return
|
||||
if (y < this->inventory.num_items) {
|
||||
size_t new_stack_size = this->inventory.items[y].data.data1[5] + item.data1[5];
|
||||
if (new_stack_size > combine_max) {
|
||||
throw out_of_range("stack is too large");
|
||||
}
|
||||
this->inventory.items[y].data.data1[5] = new_stack_size;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, then it's not meseta and not a combine item, so it needs to
|
||||
// go into an empty inventory slot
|
||||
if (this->inventory.num_items >= 30) {
|
||||
throw out_of_range("inventory is full");
|
||||
}
|
||||
auto& inv_item = this->inventory.items[this->inventory.num_items];
|
||||
inv_item.present = 1;
|
||||
inv_item.unknown_a1 = 0;
|
||||
inv_item.flags = 0;
|
||||
inv_item.data = item;
|
||||
this->inventory.num_items++;
|
||||
}
|
||||
|
||||
// TODO: Eliminate code duplication between this function and the parallel
|
||||
// function in PlayerBank
|
||||
ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, bool allow_meseta_overdraft) {
|
||||
ItemData ret;
|
||||
|
||||
// If we're removing meseta (signaled by an invalid item ID), then create a
|
||||
// meseta item.
|
||||
if (item_id == 0xFFFFFFFF) {
|
||||
this->remove_meseta(amount, allow_meseta_overdraft);
|
||||
ret.data1[0] = 0x04;
|
||||
ret.data2d = amount;
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t index = this->inventory.find_item(item_id);
|
||||
auto& inventory_item = this->inventory.items[index];
|
||||
|
||||
// If the item is a combine item and are we removing less than we have of it,
|
||||
// then create a new item and reduce the amount of the existing stack. Note
|
||||
// that passing amount == 0 means to remove the entire stack, so this only
|
||||
// applies if amount is nonzero.
|
||||
if (amount && (inventory_item.data.stack_size() > 1) &&
|
||||
(amount < inventory_item.data.data1[5])) {
|
||||
ret = inventory_item.data;
|
||||
ret.data1[5] = amount;
|
||||
ret.id = 0xFFFFFFFF;
|
||||
inventory_item.data.data1[5] -= amount;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// If we get here, then it's not meseta, and either it's not a combine item or
|
||||
// we're removing the entire stack. Delete the item from the inventory slot
|
||||
// and return the deleted item.
|
||||
ret = inventory_item.data;
|
||||
this->inventory.num_items--;
|
||||
for (size_t x = index; x < this->inventory.num_items; x++) {
|
||||
this->inventory.items[x] = this->inventory.items[x + 1];
|
||||
}
|
||||
auto& last_item = this->inventory.items[this->inventory.num_items];
|
||||
last_item.present = 0;
|
||||
last_item.unknown_a1 = 0;
|
||||
last_item.flags = 0;
|
||||
last_item.data.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::add_meseta(uint32_t amount) {
|
||||
this->disp.stats.meseta = min<size_t>(static_cast<size_t>(this->disp.stats.meseta) + amount, 999999);
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::remove_meseta(uint32_t amount, bool allow_overdraft) {
|
||||
if (amount <= this->disp.stats.meseta) {
|
||||
this->disp.stats.meseta -= amount;
|
||||
} else if (allow_overdraft) {
|
||||
this->disp.stats.meseta = 0;
|
||||
} else {
|
||||
throw out_of_range("player does not have enough meseta");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PSOBBCharacterFile::get_technique_level(uint8_t which) const {
|
||||
return (this->disp.technique_levels_v1[which] == 0xFF)
|
||||
? 0xFF
|
||||
: (this->disp.technique_levels_v1[which] + this->inventory.items[which].extension_data1);
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::set_technique_level(uint8_t which, uint8_t level) {
|
||||
if (level == 0xFF) {
|
||||
this->disp.technique_levels_v1[which] = 0xFF;
|
||||
this->inventory.items[which].extension_data1 = 0x00;
|
||||
} else if (level <= 0x0E) {
|
||||
this->disp.technique_levels_v1[which] = level;
|
||||
this->inventory.items[which].extension_data1 = 0x00;
|
||||
} else {
|
||||
this->disp.technique_levels_v1[which] = 0x0E;
|
||||
this->inventory.items[which].extension_data1 = level - 0x0E;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PSOBBCharacterFile::get_material_usage(MaterialType which) const {
|
||||
switch (which) {
|
||||
case MaterialType::HP:
|
||||
return this->inventory.hp_from_materials >> 1;
|
||||
case MaterialType::TP:
|
||||
return this->inventory.tp_from_materials >> 1;
|
||||
case MaterialType::POWER:
|
||||
case MaterialType::MIND:
|
||||
case MaterialType::EVADE:
|
||||
case MaterialType::DEF:
|
||||
case MaterialType::LUCK:
|
||||
return this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2;
|
||||
default:
|
||||
throw logic_error("invalid material type");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::set_material_usage(MaterialType which, uint8_t usage) {
|
||||
switch (which) {
|
||||
case MaterialType::HP:
|
||||
this->inventory.hp_from_materials = usage << 1;
|
||||
break;
|
||||
case MaterialType::TP:
|
||||
this->inventory.tp_from_materials = usage << 1;
|
||||
break;
|
||||
case MaterialType::POWER:
|
||||
case MaterialType::MIND:
|
||||
case MaterialType::EVADE:
|
||||
case MaterialType::DEF:
|
||||
case MaterialType::LUCK:
|
||||
this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2 = usage;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid material type");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::clear_all_material_usage() {
|
||||
this->inventory.hp_from_materials = 0;
|
||||
this->inventory.tp_from_materials = 0;
|
||||
for (size_t z = 0; z < 5; z++) {
|
||||
this->inventory.items[z + 8].extension_data2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::print_inventory(FILE* stream, GameVersion version, shared_ptr<const ItemNameIndex> name_index) const {
|
||||
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.stats.meseta.load());
|
||||
fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items);
|
||||
for (size_t x = 0; x < this->inventory.num_items; x++) {
|
||||
const auto& item = this->inventory.items[x];
|
||||
auto name = name_index->describe_item(version, item.data);
|
||||
auto hex = item.data.hex();
|
||||
fprintf(stream, "[PlayerInventory] %2zu: %s (%s)\n", x, hex.c_str(), name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<PSOBBCharacterFile::DefaultSymbolChatEntry, 6> PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS = {
|
||||
DefaultSymbolChatEntry{"\tEHello", 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{"\tEGood-bye", 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{"\tEHurrah!", 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{"\tECrying", 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{"\tEI\'m angry!", 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{"\tEHelp me!", 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}}},
|
||||
};
|
||||
|
||||
const std::array<uint16_t, 20> PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG = {
|
||||
0x0000, 0x0006, 0x0003, 0x0001, 0x0007, 0x0004, 0x0002, 0x0008, 0x0005, 0x0009,
|
||||
0x0012, 0x000F, 0x0010, 0x0011, 0x000D, 0x000A, 0x000B, 0x000C, 0x000E, 0x0000};
|
||||
|
||||
const std::array<uint8_t, 0x016C> PSOBBSystemFile::DEFAULT_KEY_CONFIG = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
|
||||
|
||||
const std::array<uint8_t, 0x0038> PSOBBSystemFile::DEFAULT_JOYSTICK_CONFIG = {
|
||||
0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
|
||||
|
||||
Reference in New Issue
Block a user