Skip to content

Commit

Permalink
implement v1-encoding for v2 items
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzziqersoftware committed Nov 5, 2023
1 parent 25c0aa2 commit e8d605a
Show file tree
Hide file tree
Showing 14 changed files with 383 additions and 168 deletions.
4 changes: 2 additions & 2 deletions src/ChatCommands.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1316,8 +1316,8 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
send_text_message(ses->client_channel, "$C7Next drop:\n" + name);

} else {
send_drop_stacked_item(ses->client_channel, item, ses->area, ses->x, ses->z);
send_drop_stacked_item(ses->server_channel, item, ses->area, ses->x, ses->z);
send_drop_stacked_item(s, ses->client_channel, item, ses->area, ses->x, ses->z);
send_drop_stacked_item(s, ses->server_channel, item, ses->area, ses->x, ses->z);

string name = s->describe_item(ses->version(), item, true);
send_text_message(ses->client_channel, "$C7Item created:\n" + name);
Expand Down
225 changes: 179 additions & 46 deletions src/ItemData.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <map>

#include "ItemParameterTable.hh"
#include "StaticGameData.hh"

using namespace std;
Expand Down Expand Up @@ -279,61 +280,193 @@ void ItemData::add_mag_photon_blast(uint8_t pb_num) {
}
}

void ItemData::decode_if_mag(GameVersion from_version) {
if (this->data1[0] != 2) {
return;
}
void ItemData::decode_for_version(GameVersion from_version) {
bool is_v2 = (from_version == GameVersion::DC) || (from_version == GameVersion::PC);

uint8_t encoded_v2_data = this->get_encoded_v2_data();
bool should_decode_v2_data = is_v2 && (encoded_v2_data != 0x00) && this->has_encoded_v2_data();

switch (this->data1[0]) {
case 0x00:
if (should_decode_v2_data) {
this->data1[5] = ((encoded_v2_data < 0x89) || (this->data1[1] == 0x0F)) ? 0x00 : this->data1[1];
this->data1[1] = encoded_v2_data;
}
break;

case 0x01:
if (should_decode_v2_data) {
this->data1[3] = 0x00;
this->data1[2] = encoded_v2_data;
}
break;

case 0x02:
if (should_decode_v2_data) {
this->data1[2] = 0xC8;
this->data1[1] = encoded_v2_data + 0x2B;
}

if (from_version == GameVersion::GC) {
// PSO GC erroneously byteswaps the data2d field, even though it's actually
// just four individual bytes, so we correct for that here.
this->data2d = bswap32(this->data2d);

} else if (from_version == GameVersion::DC || from_version == GameVersion::PC) {
// PSO PC encodes mags in a tediously annoying manner. The first four bytes are the same, but then...
// V2: pHHHHHHHHHHHHHHc pIIIIIIIIIIIIIIc JJJJJJJJJJJJJJJc KKKKKKKKKKKKKKKc QQQQQQQQ QQQQQQQQ YYYYYYYY pYYYYYYY
// V3: HHHHHHHHHHHHHHHH IIIIIIIIIIIIIIII JJJJJJJJJJJJJJJJ KKKKKKKKKKKKKKKK YYYYYYYY QQQQQQQQ PPPPPPPP CCCCCCCC
// c = color in V2 (4 bits; low bit first)
// C = color in V3
// p = PB flag bits in V2 (3 bits; ordered 1, 2, 0)
// P = PB flag bits in V3
// H, I, J, K = DEF, POW, DEX, MIND
// Q = IQ (little-endian in V2)
// Y = synchro (little-endian in V2)

// Order is important; data2[0] must not be written before data2w[0] is read
this->data2[1] = this->data2w[0]; // IQ
this->data2[0] = this->data2w[1] & 0x7FFF; // Synchro
this->data2[2] = ((this->data2[3] >> 7) & 1) | ((this->data1w[2] >> 14) & 2) | ((this->data1w[3] >> 13) & 4); // PB flags
this->data2[3] = (this->data1w[2] & 1) | ((this->data1w[3] & 1) << 1) | ((this->data1w[4] & 1) << 2) | ((this->data1w[5] & 1) << 3); // Color
this->data1w[2] &= 0x7FFE;
this->data1w[3] &= 0x7FFE;
this->data1w[4] &= 0xFFFE;
this->data1w[5] &= 0xFFFE;
}
break;

case 0x03:
if (should_decode_v2_data) {
this->data1[3] = 0x00;
if (this->data1[1] == 0x02) {
this->data1[2] = encoded_v2_data + 0x0E;
} else if (this->data1[1] == 0x0D) {
if (this->data1[2] == 0x06) {
this->data1[1] = 0x0E;
this->data1[2] = encoded_v2_data + -1;
} else if (this->data1[2] == 0x07) {
this->data1[1] = this->data1[6];
this->data1[2] = encoded_v2_data + -1;
this->data1[6] = 0x00;
}
}
}
break;

case 0x04:
break;

if (from_version == GameVersion::GC) {
// PSO GC erroneously byteswaps the data2d field, even though it's actually
// just four individual bytes, so we correct for that here.
this->data2d = bswap32(this->data2d);

} else if (from_version == GameVersion::DC || from_version == GameVersion::PC) {
// PSO PC encodes mags in a tediously annoying manner. The first four bytes are the same, but then...
// V2: pHHHHHHHHHHHHHHc pIIIIIIIIIIIIIIc JJJJJJJJJJJJJJJc KKKKKKKKKKKKKKKc QQQQQQQQ QQQQQQQQ YYYYYYYY pYYYYYYY
// V3: HHHHHHHHHHHHHHHH IIIIIIIIIIIIIIII JJJJJJJJJJJJJJJJ KKKKKKKKKKKKKKKK YYYYYYYY QQQQQQQQ PPPPPPPP CCCCCCCC
// c = color in V2 (4 bits; low bit first)
// C = color in V3
// p = PB flag bits in V2 (3 bits; ordered 1, 2, 0)
// P = PB flag bits in V3
// H, I, J, K = DEF, POW, DEX, MIND
// Q = IQ (little-endian in V2)
// Y = synchro (little-endian in V2)

// Order is important; data2[0] must not be written before data2w[0] is read
this->data2[1] = this->data2w[0]; // IQ
this->data2[0] = this->data2w[1] & 0x7FFF; // Synchro
this->data2[2] = ((this->data2[3] >> 7) & 1) | ((this->data1w[2] >> 14) & 2) | ((this->data1w[3] >> 13) & 4); // PB flags
this->data2[3] = (this->data1w[2] & 1) | ((this->data1w[3] & 1) << 1) | ((this->data1w[4] & 1) << 2) | ((this->data1w[5] & 1) << 3); // Color
this->data1w[2] &= 0x7FFE;
this->data1w[3] &= 0x7FFE;
this->data1w[4] &= 0xFFFE;
this->data1w[5] &= 0xFFFE;
default:
throw runtime_error("invalid item class");
}
}

void ItemData::encode_if_mag(GameVersion to_version) {
if (this->data1[0] != 2) {
return;
void ItemData::encode_for_version(GameVersion to_version, shared_ptr<const ItemParameterTable> item_parameter_table) {
bool is_v2 = (to_version == GameVersion::DC) || (to_version == GameVersion::PC);
bool should_encode_v2_data = is_v2 && !this->has_encoded_v2_data();

switch (this->data1[0]) {
case 0x00:
if (should_encode_v2_data && (this->data1[1] > 0x26)) {
if (this->data1[1] < 0x89) {
this->data1[5] = this->data1[1];
this->data1[1] = item_parameter_table->get_weapon_v1_replacement(this->data1[1]);
if (this->data1[1] == 0x00) {
this->data1[1] = 0x0F;
}
} else {
uint8_t data1_5 = this->data1[5];
if (this->data1[1] > data1_5) {
this->data1[5] = this->data1[1];
this->data1[1] = (data1_5 != 0) ? data1_5 : 0x0F;
}
}
}
break;

case 0x01: {
static const array<uint8_t, 4> armor_limits = {0x00, 0x29, 0x27, 0x44};
if (should_encode_v2_data && (this->data1[2] >= armor_limits[this->data1[1]])) {
this->data1[3] = this->data1[2];
this->data1[2] = 0x00;
}
break;
}

case 0x02:
if (should_encode_v2_data && (this->data1[1] > 0x2B)) {
this->data1[2] = this->data1[1] - 0x2B - 0x38;
this->data1[1] = 0x00;
}

// This logic is the inverse of the corresponding logic in
// decode_for_version; see that function for a description of what's
// going on here.
if (to_version == GameVersion::GC) {
this->data2d = bswap32(this->data2d);
} else if (to_version == GameVersion::DC || to_version == GameVersion::PC) {
this->data1w[2] = (this->data1w[2] & 0x7FFE) | ((this->data2[2] << 14) & 0x8000) | (this->data2[3] & 1);
this->data1w[3] = (this->data1w[3] & 0x7FFE) | ((this->data2[2] << 13) & 0x8000) | ((this->data2[3] >> 1) & 1);
this->data1w[4] = (this->data1w[4] & 0xFFFE) | ((this->data2[3] >> 2) & 1);
this->data1w[5] = (this->data1w[5] & 0xFFFE) | ((this->data2[3] >> 3) & 1);
// Order is important; data2w[0] must not be written before data2[0] is read
this->data2w[1] = this->data2[0] | ((this->data2[2] << 15) & 0x8000);
this->data2w[0] = this->data2[1];
}
break;

case 0x03:
if (should_encode_v2_data) {
if (this->data1[1] == 2) {
if (this->data1[2] > 0x0E) {
this->data1[3] = this->data1[2] - 0x0E;
this->data1[2] %= 0x0F;
}
} else if (this->data1[1] == 0x0E) {
this->data1[3] = this->data1[2] + 1;
this->data1[1] = 0x0D;
this->data1[2] = 0x06;
} else if (this->data1[1] > 0x0E) {
this->data1[6] = this->data1[1];
this->data1[3] = this->data1[2] + 0x01;
this->data1[1] = 0x0D;
this->data1[2] = 0x07;
}
}
break;

case 0x04:
break;

default:
throw runtime_error("invalid item class");
}
}

// This function is the inverse of decode_v2_mag; see that function for a
// description of what's going on here.
if (to_version == GameVersion::GC) {
this->data2d = bswap32(this->data2d);

} else if (to_version == GameVersion::DC || to_version == GameVersion::PC) {
this->data1w[2] = (this->data1w[2] & 0x7FFE) | ((this->data2[2] << 14) & 0x8000) | (this->data2[3] & 1);
this->data1w[3] = (this->data1w[3] & 0x7FFE) | ((this->data2[2] << 13) & 0x8000) | ((this->data2[3] >> 1) & 1);
this->data1w[4] = (this->data1w[4] & 0xFFFE) | ((this->data2[3] >> 2) & 1);
this->data1w[5] = (this->data1w[5] & 0xFFFE) | ((this->data2[3] >> 3) & 1);
// Order is important; data2w[0] must not be written before data2[0] is read
this->data2w[1] = this->data2[0] | ((this->data2[2] << 15) & 0x8000);
this->data2w[0] = this->data2[1];
uint8_t ItemData::get_encoded_v2_data() const {
switch (this->data1[0]) {
case 0x00:
return this->data1[5];
case 0x01:
case 0x03:
return this->data1[3];
case 0x02:
if (this->data1[2] > 0xC8) {
return this->data1[2] + 0x38;
}
return 0x00;
default:
return 0x00;
}
}

bool ItemData::has_encoded_v2_data() const {
return (this->data1[0] == 0)
? (this->data1[1] < this->get_encoded_v2_data())
: (this->get_encoded_v2_data() != 0);
}

uint16_t ItemData::get_sealed_item_kill_count() const {
return ((this->data1[10] << 8) | this->data1[11]) & 0x7FFF;
}
Expand Down
10 changes: 7 additions & 3 deletions src/ItemData.hh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

constexpr uint32_t MESETA_IDENTIFIER = 0x00040000;

class ItemParameterTable;

struct ItemMagStats {
uint16_t iq;
uint16_t synchro;
Expand Down Expand Up @@ -86,7 +88,7 @@ struct ItemData { // 0x14 bytes
// Related note: PSO V2 has an annoyingly complicated format for mags that
// doesn't match the above table. We decode this upon receipt and encode it
// imemdiately before sending when interacting with V2 clients; see the
// implementation of decode_if_mag() for details.
// implementation of decode_for_version() for details.

union {
parray<uint8_t, 12> data1;
Expand Down Expand Up @@ -130,8 +132,10 @@ struct ItemData { // 0x14 bytes
uint8_t mag_photon_blast_for_slot(uint8_t slot) const;
bool mag_has_photon_blast_in_any_slot(uint8_t pb_num) const;
void add_mag_photon_blast(uint8_t pb_num);
void decode_if_mag(GameVersion version);
void encode_if_mag(GameVersion version);
void decode_for_version(GameVersion version);
void encode_for_version(GameVersion version, std::shared_ptr<const ItemParameterTable> item_parameter_table);
uint8_t get_encoded_v2_data() const;
bool has_encoded_v2_data() const;

uint16_t get_sealed_item_kill_count() const;
void set_sealed_item_kill_count(uint16_t v);
Expand Down
17 changes: 17 additions & 0 deletions src/ItemParameterTable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,23 @@ uint8_t ItemParameterTable::get_max_tech_level(uint8_t char_class, uint8_t tech_
}
}

uint8_t ItemParameterTable::get_weapon_v1_replacement(uint8_t data1_1) const {
uint32_t offset;
if (this->offsets_v2) {
offset = this->offsets_v2->v1_replacement_table;
} else if (this->offsets_v3) {
offset = this->offsets_v3->v1_replacement_table;
} else if (this->offsets_v4) {
offset = this->offsets_v4->v1_replacement_table;
} else {
throw logic_error("table is not v2, v3, or v4");
}

return (data1_1 < this->num_weapon_classes)
? this->r.pget_u8(offset + data1_1)
: 0x00;
}

uint32_t ItemParameterTable::get_item_id(const ItemData& item) const {
switch (item.data1[0]) {
case 0:
Expand Down
5 changes: 3 additions & 2 deletions src/ItemParameterTable.hh
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ public:
uint8_t get_special_stars(uint8_t det) const;
const Special<false>& 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;

uint32_t get_item_id(const ItemData& item) const;
uint8_t get_item_base_stars(const ItemData& item) const;
Expand Down Expand Up @@ -353,7 +354,7 @@ private:
/* 0C / 5A6C */ le_uint32_t unit_table; // -> {count, offset -> [UnitV2]} (last if out of range)
/* 10 / 5A7C */ le_uint32_t tool_table; // -> [{count, offset -> [ToolV2]}](0x10) (last if out of range)
/* 14 / 5A74 */ le_uint32_t mag_table; // -> {count, offset -> [MagV2]}
/* 18 / 3DF8 */ le_uint32_t attack_animation_table; // -> [uint8_t](0x89)
/* 18 / 3DF8 */ le_uint32_t v1_replacement_table; // -> [uint8_t](0x89)
/* 1C / 2E4C */ le_uint32_t photon_color_table; // -> [0x24-byte structs](0x20)
/* 20 / 32CC */ le_uint32_t weapon_range_table; // -> ???
/* 24 / 3E84 */ le_uint32_t weapon_sale_divisor_table; // -> [float](0x89)
Expand All @@ -375,7 +376,7 @@ private:
/* 08 / EFA0 / 1479C */ U32T unit_table; // -> {count, offset -> [UnitV3/UnitV4]} (last if out of range)
/* 0C / EFB0 / 147AC */ U32T tool_table; // -> [{count, offset -> [ToolV3/ToolV4]}](0x1A) (last if out of range)
/* 10 / EFA8 / 147A4 */ U32T mag_table; // -> {count, offset -> [MagV3/MagV4]}
/* 14 / B88C / 0F4B8 */ U32T attack_animation_table; // -> [uint8_t](0xED)
/* 14 / B88C / 0F4B8 */ U32T v1_replacement_table; // -> [uint8_t](0xED)
/* 18 / A7FC / 0DE7C */ U32T photon_color_table; // -> [0x24-byte structs](0x20)
/* 1C / AACC / 0E194 */ U32T weapon_range_table; // -> ???
/* 20 / B938 / 0F5A8 */ U32T weapon_sale_divisor_table; // -> [float](0xED)
Expand Down
Loading

0 comments on commit e8d605a

Please sign in to comment.