implement v1-encoding for v2 items

This commit is contained in:
Martin Michelsen
2023-11-04 21:30:24 -07:00
parent 25c0aa21dc
commit e8d605afe3
14 changed files with 381 additions and 166 deletions
+2 -2
View File
@@ -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); send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
} else { } else {
send_drop_stacked_item(ses->client_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(ses->server_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); string name = s->describe_item(ses->version(), item, true);
send_text_message(ses->client_channel, "$C7Item created:\n" + name); send_text_message(ses->client_channel, "$C7Item created:\n" + name);
+177 -44
View File
@@ -2,6 +2,7 @@
#include <map> #include <map>
#include "ItemParameterTable.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
using namespace std; using namespace std;
@@ -279,61 +280,193 @@ void ItemData::add_mag_photon_blast(uint8_t pb_num) {
} }
} }
void ItemData::decode_if_mag(GameVersion from_version) { void ItemData::decode_for_version(GameVersion from_version) {
if (this->data1[0] != 2) { bool is_v2 = (from_version == GameVersion::DC) || (from_version == GameVersion::PC);
return;
}
if (from_version == GameVersion::GC) { uint8_t encoded_v2_data = this->get_encoded_v2_data();
// PSO GC erroneously byteswaps the data2d field, even though it's actually bool should_decode_v2_data = is_v2 && (encoded_v2_data != 0x00) && this->has_encoded_v2_data();
// 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) { switch (this->data1[0]) {
// PSO PC encodes mags in a tediously annoying manner. The first four bytes are the same, but then... case 0x00:
// V2: pHHHHHHHHHHHHHHc pIIIIIIIIIIIIIIc JJJJJJJJJJJJJJJc KKKKKKKKKKKKKKKc QQQQQQQQ QQQQQQQQ YYYYYYYY pYYYYYYY if (should_decode_v2_data) {
// V3: HHHHHHHHHHHHHHHH IIIIIIIIIIIIIIII JJJJJJJJJJJJJJJJ KKKKKKKKKKKKKKKK YYYYYYYY QQQQQQQQ PPPPPPPP CCCCCCCC this->data1[5] = ((encoded_v2_data < 0x89) || (this->data1[1] == 0x0F)) ? 0x00 : this->data1[1];
// c = color in V2 (4 bits; low bit first) this->data1[1] = encoded_v2_data;
// C = color in V3 }
// p = PB flag bits in V2 (3 bits; ordered 1, 2, 0) break;
// 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 case 0x01:
this->data2[1] = this->data2w[0]; // IQ if (should_decode_v2_data) {
this->data2[0] = this->data2w[1] & 0x7FFF; // Synchro this->data1[3] = 0x00;
this->data2[2] = ((this->data2[3] >> 7) & 1) | ((this->data1w[2] >> 14) & 2) | ((this->data1w[3] >> 13) & 4); // PB flags this->data1[2] = encoded_v2_data;
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; break;
this->data1w[3] &= 0x7FFE;
this->data1w[4] &= 0xFFFE; case 0x02:
this->data1w[5] &= 0xFFFE; 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;
default:
throw runtime_error("invalid item class");
} }
} }
void ItemData::encode_if_mag(GameVersion to_version) { void ItemData::encode_for_version(GameVersion to_version, shared_ptr<const ItemParameterTable> item_parameter_table) {
if (this->data1[0] != 2) { bool is_v2 = (to_version == GameVersion::DC) || (to_version == GameVersion::PC);
return; bool should_encode_v2_data = is_v2 && !this->has_encoded_v2_data();
}
// This function is the inverse of decode_v2_mag; see that function for a switch (this->data1[0]) {
// description of what's going on here. case 0x00:
if (to_version == GameVersion::GC) { if (should_encode_v2_data && (this->data1[1] > 0x26)) {
this->data2d = bswap32(this->data2d); 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;
} else if (to_version == GameVersion::DC || to_version == GameVersion::PC) { case 0x01: {
this->data1w[2] = (this->data1w[2] & 0x7FFE) | ((this->data2[2] << 14) & 0x8000) | (this->data2[3] & 1); static const array<uint8_t, 4> armor_limits = {0x00, 0x29, 0x27, 0x44};
this->data1w[3] = (this->data1w[3] & 0x7FFE) | ((this->data2[2] << 13) & 0x8000) | ((this->data2[3] >> 1) & 1); if (should_encode_v2_data && (this->data1[2] >= armor_limits[this->data1[1]])) {
this->data1w[4] = (this->data1w[4] & 0xFFFE) | ((this->data2[3] >> 2) & 1); this->data1[3] = this->data1[2];
this->data1w[5] = (this->data1w[5] & 0xFFFE) | ((this->data2[3] >> 3) & 1); this->data1[2] = 0x00;
// 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); break;
this->data2w[0] = this->data2[1]; }
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");
} }
} }
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 { uint16_t ItemData::get_sealed_item_kill_count() const {
return ((this->data1[10] << 8) | this->data1[11]) & 0x7FFF; return ((this->data1[10] << 8) | this->data1[11]) & 0x7FFF;
} }
+7 -3
View File
@@ -8,6 +8,8 @@
constexpr uint32_t MESETA_IDENTIFIER = 0x00040000; constexpr uint32_t MESETA_IDENTIFIER = 0x00040000;
class ItemParameterTable;
struct ItemMagStats { struct ItemMagStats {
uint16_t iq; uint16_t iq;
uint16_t synchro; uint16_t synchro;
@@ -86,7 +88,7 @@ struct ItemData { // 0x14 bytes
// Related note: PSO V2 has an annoyingly complicated format for mags that // 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 // doesn't match the above table. We decode this upon receipt and encode it
// imemdiately before sending when interacting with V2 clients; see the // 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 { union {
parray<uint8_t, 12> data1; parray<uint8_t, 12> data1;
@@ -130,8 +132,10 @@ struct ItemData { // 0x14 bytes
uint8_t mag_photon_blast_for_slot(uint8_t slot) const; uint8_t mag_photon_blast_for_slot(uint8_t slot) const;
bool mag_has_photon_blast_in_any_slot(uint8_t pb_num) const; bool mag_has_photon_blast_in_any_slot(uint8_t pb_num) const;
void add_mag_photon_blast(uint8_t pb_num); void add_mag_photon_blast(uint8_t pb_num);
void decode_if_mag(GameVersion version); void decode_for_version(GameVersion version);
void encode_if_mag(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; uint16_t get_sealed_item_kill_count() const;
void set_sealed_item_kill_count(uint16_t v); void set_sealed_item_kill_count(uint16_t v);
+17
View File
@@ -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 { uint32_t ItemParameterTable::get_item_id(const ItemData& item) const {
switch (item.data1[0]) { switch (item.data1[0]) {
case 0: case 0:
+3 -2
View File
@@ -321,6 +321,7 @@ public:
uint8_t get_special_stars(uint8_t det) const; uint8_t get_special_stars(uint8_t det) const;
const Special<false>& get_special(uint8_t special) 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_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; uint32_t get_item_id(const ItemData& item) const;
uint8_t get_item_base_stars(const ItemData& item) const; uint8_t get_item_base_stars(const ItemData& item) const;
@@ -353,7 +354,7 @@ private:
/* 0C / 5A6C */ le_uint32_t unit_table; // -> {count, offset -> [UnitV2]} (last if out of range) /* 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) /* 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]} /* 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) /* 1C / 2E4C */ le_uint32_t photon_color_table; // -> [0x24-byte structs](0x20)
/* 20 / 32CC */ le_uint32_t weapon_range_table; // -> ??? /* 20 / 32CC */ le_uint32_t weapon_range_table; // -> ???
/* 24 / 3E84 */ le_uint32_t weapon_sale_divisor_table; // -> [float](0x89) /* 24 / 3E84 */ le_uint32_t weapon_sale_divisor_table; // -> [float](0x89)
@@ -375,7 +376,7 @@ private:
/* 08 / EFA0 / 1479C */ U32T unit_table; // -> {count, offset -> [UnitV3/UnitV4]} (last if out of range) /* 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) /* 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]} /* 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) /* 18 / A7FC / 0DE7C */ U32T photon_color_table; // -> [0x24-byte structs](0x20)
/* 1C / AACC / 0E194 */ U32T weapon_range_table; // -> ??? /* 1C / AACC / 0E194 */ U32T weapon_range_table; // -> ???
/* 20 / B938 / 0F5A8 */ U32T weapon_sale_divisor_table; // -> [float](0xED) /* 20 / B938 / 0F5A8 */ U32T weapon_sale_divisor_table; // -> [float](0xED)
+46 -2
View File
@@ -227,7 +227,7 @@ The actions are:\n\
Print the name of the item given by DATA (in hex). DATA must not contain\n\ Print the name of the item given by DATA (in hex). DATA must not contain\n\
spaces. If DATA is 20 bytes, newserv assumes it contains an unused item ID\n\ spaces. If DATA is 20 bytes, newserv assumes it contains an unused item ID\n\
field; if it is fewer bytes, up to 16 bytes are used.\n\ field; if it is fewer bytes, up to 16 bytes are used.\n\
encode-item DESCRIPTION\n\ encode-item DESCRIPTION [--pc|--bb]\n\
Encode the description of an item into its corresponding ItemData (hex)\n\ Encode the description of an item into its corresponding ItemData (hex)\n\
representation. If DESCRIPTION contains spaces, it must be quoted, such as\n\ representation. If DESCRIPTION contains spaces, it must be quoted, such as\n\
\"L&K14 COMBAT +10 0/10/15/0/35\".\n\ \"L&K14 COMBAT +10 0/10/15/0/35\".\n\
@@ -1598,14 +1598,58 @@ int main(int argc, char** argv) {
JSON::parse(load_file("system/item-tables/names-v2.json")), JSON::parse(load_file("system/item-tables/names-v2.json")),
JSON::parse(load_file("system/item-tables/names-v3.json")), JSON::parse(load_file("system/item-tables/names-v3.json")),
JSON::parse(load_file("system/item-tables/names-v4.json"))); JSON::parse(load_file("system/item-tables/names-v4.json")));
shared_ptr<string> pmt_data_v2(new string(prs_decompress(load_file("system/item-tables/ItemPMT-v2.prs"))));
auto pmt_v2 = make_shared<ItemParameterTable>(pmt_data_v2, ItemParameterTable::Version::V2);
shared_ptr<string> pmt_data_v3(new string(prs_decompress(load_file("system/item-tables/ItemPMT-gc.prs"))));
auto pmt_v3 = make_shared<ItemParameterTable>(pmt_data_v3, ItemParameterTable::Version::V3);
ItemData item = name_index->parse_item_description(cli_version, input_filename); ItemData item = name_index->parse_item_description(cli_version, input_filename);
string desc = name_index->describe_item(cli_version, item); string desc = name_index->describe_item(cli_version, item);
log_info("Data: %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX", log_info("Data (decoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX",
item.data1[0], item.data1[1], item.data1[2], item.data1[3], item.data1[0], item.data1[1], item.data1[2], item.data1[3],
item.data1[4], item.data1[5], item.data1[6], item.data1[7], item.data1[4], item.data1[5], item.data1[6], item.data1[7],
item.data1[8], item.data1[9], item.data1[10], item.data1[11], item.data1[8], item.data1[9], item.data1[10], item.data1[11],
item.data2[0], item.data2[1], item.data2[2], item.data2[3]); item.data2[0], item.data2[1], item.data2[2], item.data2[3]);
ItemData item_v1 = item;
item_v1.encode_for_version(GameVersion::PC, pmt_v2);
ItemData item_v1_decoded = item_v1;
item_v1_decoded.decode_for_version(GameVersion::PC);
log_info("Data (V1-encoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX",
item_v1.data1[0], item_v1.data1[1], item_v1.data1[2], item_v1.data1[3],
item_v1.data1[4], item_v1.data1[5], item_v1.data1[6], item_v1.data1[7],
item_v1.data1[8], item_v1.data1[9], item_v1.data1[10], item_v1.data1[11],
item_v1.data2[0], item_v1.data2[1], item_v1.data2[2], item_v1.data2[3]);
if (item_v1_decoded != item) {
log_warning("V1-decoded data does not match original data");
log_warning("Data (V1-decoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX",
item_v1_decoded.data1[0], item_v1_decoded.data1[1], item_v1_decoded.data1[2], item_v1_decoded.data1[3],
item_v1_decoded.data1[4], item_v1_decoded.data1[5], item_v1_decoded.data1[6], item_v1_decoded.data1[7],
item_v1_decoded.data1[8], item_v1_decoded.data1[9], item_v1_decoded.data1[10], item_v1_decoded.data1[11],
item_v1_decoded.data2[0], item_v1_decoded.data2[1], item_v1_decoded.data2[2], item_v1_decoded.data2[3]);
}
ItemData item_gc = item;
item_gc.encode_for_version(GameVersion::GC, pmt_v3);
ItemData item_gc_decoded = item_gc;
item_gc_decoded.decode_for_version(GameVersion::GC);
log_info("Data (GC-encoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX",
item_gc.data1[0], item_gc.data1[1], item_gc.data1[2], item_gc.data1[3],
item_gc.data1[4], item_gc.data1[5], item_gc.data1[6], item_gc.data1[7],
item_gc.data1[8], item_gc.data1[9], item_gc.data1[10], item_gc.data1[11],
item_gc.data2[0], item_gc.data2[1], item_gc.data2[2], item_gc.data2[3]);
if (item_gc_decoded != item) {
log_warning("GC-decoded data does not match original data");
log_warning("Data (GC-decoded): %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX",
item_gc_decoded.data1[0], item_gc_decoded.data1[1], item_gc_decoded.data1[2], item_gc_decoded.data1[3],
item_gc_decoded.data1[4], item_gc_decoded.data1[5], item_gc_decoded.data1[6], item_gc_decoded.data1[7],
item_gc_decoded.data1[8], item_gc_decoded.data1[9], item_gc_decoded.data1[10], item_gc_decoded.data1[11],
item_gc_decoded.data2[0], item_gc_decoded.data2[1], item_gc_decoded.data2[2], item_gc_decoded.data2[3]);
}
log_info("Description: %s", desc.c_str()); log_info("Description: %s", desc.c_str());
break; break;
} }
+6 -5
View File
@@ -10,6 +10,7 @@
#include <stdexcept> #include <stdexcept>
#include "ItemData.hh" #include "ItemData.hh"
#include "ItemParameterTable.hh"
#include "Loggers.hh" #include "Loggers.hh"
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
@@ -566,15 +567,15 @@ size_t PlayerInventory::remove_all_items_of_type(uint8_t data1_0, int16_t data1_
return ret; return ret;
} }
void PlayerInventory::decode_mags(GameVersion version) { void PlayerInventory::decode_for_version(GameVersion version) {
for (size_t z = 0; z < this->items.size(); z++) { for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.decode_if_mag(version); this->items[z].data.decode_for_version(version);
} }
} }
void PlayerInventory::encode_mags(GameVersion version) { void PlayerInventory::encode_for_version(GameVersion version, shared_ptr<const ItemParameterTable> item_parameter_table) {
for (size_t z = 0; z < this->items.size(); z++) { for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.encode_if_mag(version); this->items[z].data.encode_for_version(version, item_parameter_table);
} }
} }
@@ -827,7 +828,7 @@ static PlayerInventoryItem make_template_item(bool equipped, uint64_t first_data
static PlayerInventoryItem v2_item(bool equipped, uint64_t first_data, uint64_t second_data) { static PlayerInventoryItem v2_item(bool equipped, uint64_t first_data, uint64_t second_data) {
auto ret = make_template_item(equipped, first_data, second_data); auto ret = make_template_item(equipped, first_data, second_data);
ret.data.decode_if_mag(GameVersion::PC); ret.data.decode_for_version(GameVersion::PC);
return ret; return ret;
} }
+4 -2
View File
@@ -16,6 +16,8 @@
#include "Text.hh" #include "Text.hh"
#include "Version.hh" #include "Version.hh"
class ItemParameterTable;
extern FileContentsCache player_files_cache; extern FileContentsCache player_files_cache;
// PSO V2 stored some extra data in the character structs in a format that I'm // PSO V2 stored some extra data in the character structs in a format that I'm
@@ -79,8 +81,8 @@ struct PlayerInventory {
size_t remove_all_items_of_type(uint8_t data0, int16_t data1 = -1); size_t remove_all_items_of_type(uint8_t data0, int16_t data1 = -1);
void decode_mags(GameVersion version); void decode_for_version(GameVersion version);
void encode_mags(GameVersion version); void encode_for_version(GameVersion version, std::shared_ptr<const ItemParameterTable> item_parameter_table);
} __attribute__((packed)); } __attribute__((packed));
struct PlayerBank { struct PlayerBank {
+6 -4
View File
@@ -877,6 +877,8 @@ static HandlerResult S_V3_BB_DA(shared_ptr<ProxyServer::LinkedSession> ses, uint
} }
static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) { static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
auto s = ses->require_server_state();
if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) {
if ((ses->version() == GameVersion::GC) && (data.size() >= 0x14)) { if ((ses->version() == GameVersion::GC) && (data.size() >= 0x14)) {
if (static_cast<uint8_t>(data[0]) == 0xB6) { if (static_cast<uint8_t>(data[0]) == 0xB6) {
@@ -963,8 +965,8 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
const auto& cmd = check_size_t<G_StandardDropItemRequest_DC_6x60>( const auto& cmd = check_size_t<G_StandardDropItemRequest_DC_6x60>(
data, sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60)); data, sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60));
ses->next_drop_item.id = ses->next_item_id++; ses->next_drop_item.id = ses->next_item_id++;
send_drop_item(ses->server_channel, ses->next_drop_item, true, cmd.area, cmd.x, cmd.z, cmd.entity_id); send_drop_item(s, ses->server_channel, ses->next_drop_item, true, cmd.area, cmd.x, cmd.z, cmd.entity_id);
send_drop_item(ses->client_channel, ses->next_drop_item, true, cmd.area, cmd.x, cmd.z, cmd.entity_id); send_drop_item(s, ses->client_channel, ses->next_drop_item, true, cmd.area, cmd.x, cmd.z, cmd.entity_id);
ses->next_drop_item.clear(); ses->next_drop_item.clear();
return HandlerResult::Type::SUPPRESS; return HandlerResult::Type::SUPPRESS;
@@ -975,8 +977,8 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
} else if ((static_cast<uint8_t>(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && (ses->version() != GameVersion::BB)) { } else if ((static_cast<uint8_t>(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && (ses->version() != GameVersion::BB)) {
const auto& cmd = check_size_t<G_SpecializableItemDropRequest_6xA2>(data); const auto& cmd = check_size_t<G_SpecializableItemDropRequest_6xA2>(data);
ses->next_drop_item.id = ses->next_item_id++; ses->next_drop_item.id = ses->next_item_id++;
send_drop_item(ses->server_channel, ses->next_drop_item, false, cmd.area, cmd.x, cmd.z, cmd.entity_id); send_drop_item(s, ses->server_channel, ses->next_drop_item, false, cmd.area, cmd.x, cmd.z, cmd.entity_id);
send_drop_item(ses->client_channel, ses->next_drop_item, false, cmd.area, cmd.x, cmd.z, cmd.entity_id); send_drop_item(s, ses->client_channel, ses->next_drop_item, false, cmd.area, cmd.x, cmd.z, cmd.entity_id);
ses->next_drop_item.clear(); ses->next_drop_item.clear();
return HandlerResult::Type::SUPPRESS; return HandlerResult::Type::SUPPRESS;
+2 -2
View File
@@ -2725,7 +2725,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
default: default:
throw logic_error("player data command not implemented for version"); throw logic_error("player data command not implemented for version");
} }
player->inventory.decode_mags(c->version()); player->inventory.decode_for_version(c->version());
c->channel.language = player->inventory.language; c->channel.language = player->inventory.language;
string name_str = player->disp.name.decode(c->language()); string name_str = player->disp.name.decode(c->language());
@@ -3752,7 +3752,7 @@ static void on_D0_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
c->game_data.pending_item_trade->other_client_id = cmd.target_client_id; c->game_data.pending_item_trade->other_client_id = cmd.target_client_id;
for (size_t x = 0; x < cmd.item_count; x++) { for (size_t x = 0; x < cmd.item_count; x++) {
auto& item = c->game_data.pending_item_trade->items.emplace_back(cmd.item_datas[x]); auto& item = c->game_data.pending_item_trade->items.emplace_back(cmd.item_datas[x]);
item.decode_if_mag(c->version()); item.decode_for_version(c->version());
} }
// If the other player has a pending trade as well, assume this is the second // If the other player has a pending trade as well, assume this is the second
+16 -12
View File
@@ -222,7 +222,7 @@ static void on_sync_joining_player_item_state(shared_ptr<Client> c, uint8_t comm
// NOTE: If we use this codepath for non-V3 in the future, we'll need to // NOTE: If we use this codepath for non-V3 in the future, we'll need to
// change this hardcoded version. This only works because GC's mag // change this hardcoded version. This only works because GC's mag
// encoding/decoding is symmetric (encode and decode do the same thing). // encoding/decoding is symmetric (encode and decode do the same thing).
floor_items[z].item.decode_if_mag(GameVersion::GC); floor_items[z].item.decode_for_version(GameVersion::GC);
} }
string out_compressed_data = bc0_compress(decompressed); string out_compressed_data = bc0_compress(decompressed);
@@ -289,7 +289,7 @@ static void on_sync_joining_player_disp_and_inventory(
// NOTE: If we use this codepath for non-V3 in the future, we'll need to // NOTE: If we use this codepath for non-V3 in the future, we'll need to
// change this hardcoded version. This only works because GC's mag // change this hardcoded version. This only works because GC's mag
// encoding/decoding is symmetric (encode and decode do the same thing). // encoding/decoding is symmetric (encode and decode do the same thing).
out_cmd.inventory.items[z].data.decode_if_mag(GameVersion::GC); out_cmd.inventory.items[z].data.decode_for_version(GameVersion::GC);
} }
send_command_t(target, command, flag, out_cmd); send_command_t(target, command, flag, out_cmd);
} }
@@ -712,16 +712,19 @@ void forward_subcommand_with_mag_transcode_t(shared_ptr<Client> c, uint8_t comma
} }
auto l = c->require_lobby(); auto l = c->require_lobby();
auto s = c->require_server_state();
for (auto& other_c : l->clients) { for (auto& other_c : l->clients) {
if (!other_c || other_c == c) { if (!other_c || other_c == c) {
continue; continue;
} }
CmdT out_cmd = cmd;
if (c->version() != other_c->version()) { if (c->version() != other_c->version()) {
out_cmd.item_data.decode_if_mag(c->version()); CmdT out_cmd = cmd;
out_cmd.item_data.encode_if_mag(other_c->version()); out_cmd.item_data.decode_for_version(c->version());
out_cmd.item_data.encode_for_version(other_c->version(), s->item_parameter_table_for_version(other_c->version()));
send_command_t(other_c, command, flag, out_cmd);
} else {
send_command_t(other_c, command, flag, cmd);
} }
send_command_t(other_c, command, flag, out_cmd);
} }
} }
@@ -742,7 +745,7 @@ static void on_create_inventory_item_t(shared_ptr<Client> c, uint8_t command, ui
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
auto p = c->game_data.player(); auto p = c->game_data.player();
ItemData item = cmd.item_data; ItemData item = cmd.item_data;
item.decode_if_mag(c->version()); item.decode_for_version(c->version());
l->on_item_id_generated_externally(item.id); l->on_item_id_generated_externally(item.id);
p->add_item(item); p->add_item(item);
@@ -786,7 +789,7 @@ static void on_drop_partial_stack_t(shared_ptr<Client> c, uint8_t command, uint8
// TODO: Should we delete anything from the inventory here? Does the client // TODO: Should we delete anything from the inventory here? Does the client
// send an appropriate 6x29 alongside this? // send an appropriate 6x29 alongside this?
ItemData item = cmd.item_data; ItemData item = cmd.item_data;
item.decode_if_mag(c->version()); item.decode_for_version(c->version());
l->on_item_id_generated_externally(item.id); l->on_item_id_generated_externally(item.id);
l->add_item(item, cmd.area, cmd.x, cmd.z); l->add_item(item, cmd.area, cmd.x, cmd.z);
@@ -879,7 +882,7 @@ static void on_buy_shop_item(shared_ptr<Client> c, uint8_t command, uint8_t flag
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
auto p = c->game_data.player(); auto p = c->game_data.player();
ItemData item = cmd.item_data; ItemData item = cmd.item_data;
item.decode_if_mag(c->version()); item.decode_for_version(c->version());
l->on_item_id_generated_externally(item.id); l->on_item_id_generated_externally(item.id);
p->add_item(item); p->add_item(item);
@@ -908,6 +911,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, uint8_t command, u
const auto& cmd = check_size_t<CmdT>(data, size); const auto& cmd = check_size_t<CmdT>(data, size);
auto s = c->require_server_state();
auto l = c->require_lobby(); auto l = c->require_lobby();
if (!l->is_game() || (c->lobby_client_id != l->leader_id)) { if (!l->is_game() || (c->lobby_client_id != l->leader_id)) {
return; return;
@@ -918,7 +922,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, uint8_t command, u
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
ItemData item = cmd.item.item; ItemData item = cmd.item.item;
item.decode_if_mag(c->version()); item.decode_for_version(c->version());
l->on_item_id_generated_externally(item.id); l->on_item_id_generated_externally(item.id);
l->add_item(item, cmd.item.area, cmd.item.x, cmd.item.z); l->add_item(item, cmd.item.area, cmd.item.x, cmd.item.z);
@@ -938,8 +942,8 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, uint8_t command, u
} }
CmdT out_cmd = cmd; CmdT out_cmd = cmd;
if (c->version() != lc->version()) { if (c->version() != lc->version()) {
out_cmd.item.item.decode_if_mag(c->version()); out_cmd.item.item.decode_for_version(c->version());
out_cmd.item.item.encode_if_mag(lc->version()); out_cmd.item.item.encode_for_version(lc->version(), s->item_parameter_table_for_version(lc->version()));
} }
send_command_t(lc, command, flag, out_cmd); send_command_t(lc, command, flag, out_cmd);
} }
+25 -17
View File
@@ -1475,6 +1475,8 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
throw runtime_error("lobby is not a spectator team"); throw runtime_error("lobby is not a spectator team");
} }
auto s = c->require_server_state();
S_JoinSpectatorTeam_GC_Ep3_E8 cmd; S_JoinSpectatorTeam_GC_Ep3_E8 cmd;
cmd.variations.clear(0); cmd.variations.clear(0);
@@ -1501,7 +1503,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
p.lobby_data.client_id = wc->lobby_client_id; p.lobby_data.client_id = wc->lobby_client_id;
p.lobby_data.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language()); p.lobby_data.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language());
p.inventory = wc_p->inventory; p.inventory = wc_p->inventory;
p.inventory.encode_mags(c->version()); p.inventory.encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
p.disp = wc_p->disp.to_dcpcv3(c->language(), p.inventory.language); p.disp = wc_p->disp.to_dcpcv3(c->language(), p.inventory.language);
p.disp.enforce_lobby_join_limits(c->version()); p.disp.enforce_lobby_join_limits(c->version());
@@ -1539,7 +1541,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
auto& p = cmd.players[client_id]; auto& p = cmd.players[client_id];
p.lobby_data = entry.lobby_data; p.lobby_data = entry.lobby_data;
p.inventory = entry.inventory; p.inventory = entry.inventory;
p.inventory.encode_mags(c->version()); p.inventory.encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
p.disp = entry.disp; p.disp = entry.disp;
p.disp.enforce_lobby_join_limits(c->version()); p.disp.enforce_lobby_join_limits(c->version());
@@ -1671,11 +1673,12 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
S_JoinGame_GC_Ep3_64 cmd; S_JoinGame_GC_Ep3_64 cmd;
size_t player_count = populate_v3_cmd(cmd); size_t player_count = populate_v3_cmd(cmd);
auto s = c->require_server_state();
for (size_t x = 0; x < 4; x++) { for (size_t x = 0; x < 4; x++) {
if (l->clients[x]) { if (l->clients[x]) {
auto other_p = l->clients[x]->game_data.player(); auto other_p = l->clients[x]->game_data.player();
cmd.players_ep3[x].inventory = other_p->inventory; cmd.players_ep3[x].inventory = other_p->inventory;
cmd.players_ep3[x].inventory.encode_mags(c->version()); cmd.players_ep3[x].inventory.encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
cmd.players_ep3[x].disp = convert_player_disp_data<PlayerDispDataDCPCV3>(other_p->disp, c->language(), other_p->inventory.language); cmd.players_ep3[x].disp = convert_player_disp_data<PlayerDispDataDCPCV3>(other_p->disp, c->language(), other_p->inventory.language);
cmd.players_ep3[x].disp.enforce_lobby_join_limits(c->version()); cmd.players_ep3[x].disp.enforce_lobby_join_limits(c->version());
} }
@@ -1712,6 +1715,8 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
template <typename LobbyDataT, typename DispDataT, typename RecordsT, bool UseLanguageMarkerInName> template <typename LobbyDataT, typename DispDataT, typename RecordsT, bool UseLanguageMarkerInName>
void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Client> joining_client = nullptr) { void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Client> joining_client = nullptr) {
auto s = c->require_server_state();
uint8_t command; uint8_t command;
if (l->is_game()) { if (l->is_game()) {
if (joining_client) { if (joining_client) {
@@ -1789,7 +1794,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
e.lobby_data.name.encode(lp->disp.name.decode(lp->inventory.language), c->language()); e.lobby_data.name.encode(lp->disp.name.decode(lp->inventory.language), c->language());
} }
e.inventory = lp->inventory; e.inventory = lp->inventory;
e.inventory.encode_mags(c->version()); e.inventory.encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
e.disp = convert_player_disp_data<DispDataT>(lp->disp, c->language(), lp->inventory.language); e.disp = convert_player_disp_data<DispDataT>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits(c->version()); e.disp.enforce_lobby_join_limits(c->version());
} }
@@ -1826,6 +1831,8 @@ void send_join_lobby_dc_nte(shared_ptr<Client> c, shared_ptr<Lobby> l,
} }
} }
auto s = c->require_server_state();
size_t used_entries = 0; size_t used_entries = 0;
for (const auto& lc : lobby_clients) { for (const auto& lc : lobby_clients) {
auto lp = lc->game_data.player(); auto lp = lc->game_data.player();
@@ -1835,7 +1842,7 @@ void send_join_lobby_dc_nte(shared_ptr<Client> c, shared_ptr<Lobby> l,
e.lobby_data.client_id = lc->lobby_client_id; e.lobby_data.client_id = lc->lobby_client_id;
e.lobby_data.name.encode(lp->disp.name.decode(lp->inventory.language), c->language()); e.lobby_data.name.encode(lp->disp.name.decode(lp->inventory.language), c->language());
e.inventory = lp->inventory; e.inventory = lp->inventory;
e.inventory.encode_mags(c->version()); e.inventory.encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
e.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(lp->disp, c->language(), lp->inventory.language); e.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits(c->version()); e.disp.enforce_lobby_join_limits(c->version());
} }
@@ -1939,6 +1946,8 @@ void send_get_player_info(shared_ptr<Client> c) {
// Trade window // Trade window
void send_execute_item_trade(shared_ptr<Client> c, const vector<ItemData>& items) { void send_execute_item_trade(shared_ptr<Client> c, const vector<ItemData>& items) {
auto s = c->require_server_state();
SC_TradeItems_D0_D3 cmd; SC_TradeItems_D0_D3 cmd;
if (items.size() > cmd.item_datas.size()) { if (items.size() > cmd.item_datas.size()) {
throw logic_error("too many items in execute trade command"); throw logic_error("too many items in execute trade command");
@@ -1947,7 +1956,7 @@ void send_execute_item_trade(shared_ptr<Client> c, const vector<ItemData>& items
cmd.item_count = items.size(); cmd.item_count = items.size();
for (size_t x = 0; x < items.size(); x++) { for (size_t x = 0; x < items.size(); x++) {
cmd.item_datas[x] = items[x]; cmd.item_datas[x] = items[x];
cmd.item_datas[x].encode_if_mag(c->version()); cmd.item_datas[x].encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
} }
send_command_t(c, 0xD3, 0x00, cmd); send_command_t(c, 0xD3, 0x00, cmd);
} }
@@ -2072,39 +2081,38 @@ void send_set_player_visibility(shared_ptr<Lobby> l, shared_ptr<Client> c,
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// BB game commands // BB game commands
void send_drop_item(Channel& ch, const ItemData& item, void send_drop_item(shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t entity_id) { bool from_enemy, uint8_t area, float x, float z, uint16_t entity_id) {
G_DropItem_PC_V3_BB_6x5F cmd = { G_DropItem_PC_V3_BB_6x5F cmd = {
{{0x5F, 0x0B, 0x0000}, {area, from_enemy, entity_id, x, z, 0, 0, item}}, 0}; {{0x5F, 0x0B, 0x0000}, {area, from_enemy, entity_id, x, z, 0, 0, item}}, 0};
cmd.item.item.encode_if_mag(ch.version); cmd.item.item.encode_for_version(ch.version, s->item_parameter_table_for_version(ch.version));
ch.send(0x60, 0x00, &cmd, sizeof(cmd)); ch.send(0x60, 0x00, &cmd, sizeof(cmd));
} }
void send_drop_item(shared_ptr<Lobby> l, const ItemData& item, void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t entity_id) { bool from_enemy, uint8_t area, float x, float z, uint16_t entity_id) {
auto s = l->require_server_state();
for (auto& c : l->clients) { for (auto& c : l->clients) {
if (!c) { if (!c) {
continue; continue;
} }
send_drop_item(c->channel, item, from_enemy, area, x, z, entity_id); send_drop_item(s, c->channel, item, from_enemy, area, x, z, entity_id);
} }
} }
void send_drop_stacked_item(Channel& ch, const ItemData& item, void send_drop_stacked_item(shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t area, float x, float z) {
uint8_t area, float x, float z) { G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{0x5D, 0x0A, 0x0000}, area, 0, x, z, item}, 0};
G_DropStackedItem_PC_V3_BB_6x5D cmd = { cmd.item_data.encode_for_version(ch.version, s->item_parameter_table_for_version(ch.version));
{{0x5D, 0x0A, 0x0000}, area, 0, x, z, item}, 0};
cmd.item_data.encode_if_mag(ch.version);
ch.send(0x60, 0x00, &cmd, sizeof(cmd)); ch.send(0x60, 0x00, &cmd, sizeof(cmd));
} }
void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item, void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item, uint8_t area, float x, float z) {
uint8_t area, float x, float z) { auto s = l->require_server_state();
for (auto& c : l->clients) { for (auto& c : l->clients) {
if (!c) { if (!c) {
continue; continue;
} }
send_drop_stacked_item(c->channel, item, area, x, z); send_drop_stacked_item(s, c->channel, item, area, x, z);
} }
} }
+2 -2
View File
@@ -292,11 +292,11 @@ void send_ep3_change_music(Channel& ch, uint32_t song);
void send_set_player_visibility(std::shared_ptr<Client> c, bool visible); void send_set_player_visibility(std::shared_ptr<Client> c, bool visible);
void send_revive_player(std::shared_ptr<Client> c); void send_revive_player(std::shared_ptr<Client> c);
void send_drop_item(Channel& ch, const ItemData& item, void send_drop_item(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id); bool from_enemy, uint8_t area, float x, float z, uint16_t request_id);
void send_drop_item(std::shared_ptr<Lobby> l, const ItemData& item, void send_drop_item(std::shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id); bool from_enemy, uint8_t area, float x, float z, uint16_t request_id);
void send_drop_stacked_item(Channel& ch, const ItemData& item, void send_drop_stacked_item(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
uint8_t area, float x, float z); uint8_t area, float x, float z);
void send_drop_stacked_item(std::shared_ptr<Lobby> l, const ItemData& item, void send_drop_stacked_item(std::shared_ptr<Lobby> l, const ItemData& item,
uint8_t area, float x, float z); uint8_t area, float x, float z);
+68 -69
View File
@@ -627,17 +627,17 @@ Proxy session commands:\n\
} }
data.resize((data.size() + 3) & (~3)); data.resize((data.size() + 3) & (~3));
shared_ptr<ProxyServer::LinkedSession> proxy_session; shared_ptr<ProxyServer::LinkedSession> ses;
try { try {
proxy_session = this->get_proxy_session(session_name); ses = this->get_proxy_session(session_name);
} catch (const exception&) { } catch (const exception&) {
} }
if (proxy_session.get()) { if (ses.get()) {
if (command_name[1] == 's') { if (command_name[1] == 's') {
proxy_session->server_channel.send(data); ses->server_channel.send(data);
} else { } else {
proxy_session->client_channel.send(data); ses->client_channel.send(data);
} }
} else { } else {
@@ -645,8 +645,7 @@ Proxy session commands:\n\
if (session_name.empty()) { if (session_name.empty()) {
c = this->state->game_server->get_client(); c = this->state->game_server->get_client();
} else { } else {
auto clients = this->state->game_server->get_clients_by_identifier( auto clients = this->state->game_server->get_clients_by_identifier(session_name);
session_name);
if (clients.empty()) { if (clients.empty()) {
throw runtime_error("no such client"); throw runtime_error("no such client");
} }
@@ -668,9 +667,9 @@ Proxy session commands:\n\
} }
} else if (command_name == "show-slots") { } else if (command_name == "show-slots") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
for (size_t z = 0; z < session->lobby_players.size(); z++) { for (size_t z = 0; z < ses->lobby_players.size(); z++) {
const auto& player = session->lobby_players[z]; const auto& player = ses->lobby_players[z];
if (player.guild_card_number) { if (player.guild_card_number) {
auto secid_name = name_for_section_id(player.section_id); auto secid_name = name_for_section_id(player.section_id);
fprintf(stderr, " %zu: %" PRIu32 " => %s (%c, %s, %s)\n", fprintf(stderr, " %zu: %" PRIu32 " => %s (%c, %s, %s)\n",
@@ -683,11 +682,11 @@ Proxy session commands:\n\
} }
} else if ((command_name == "c") || (command_name == "chat") || (command_name == "dchat")) { } else if ((command_name == "c") || (command_name == "chat") || (command_name == "dchat")) {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
bool is_dchat = (command_name == "dchat"); bool is_dchat = (command_name == "dchat");
if (!is_dchat && (session->version() == GameVersion::PC || session->version() == GameVersion::BB)) { if (!is_dchat && (ses->version() == GameVersion::PC || ses->version() == GameVersion::BB)) {
send_chat_message_from_client(session->server_channel, command_args, 0); send_chat_message_from_client(ses->server_channel, command_args, 0);
} else { } else {
string data(8, '\0'); string data(8, '\0');
data.push_back('\x09'); data.push_back('\x09');
@@ -699,13 +698,13 @@ Proxy session commands:\n\
data.push_back('\0'); data.push_back('\0');
} }
data.resize((data.size() + 3) & (~3)); data.resize((data.size() + 3) & (~3));
session->server_channel.send(0x06, 0x00, data); ses->server_channel.send(0x06, 0x00, data);
} }
} else if ((command_name == "wc") || (command_name == "wchat")) { } else if ((command_name == "wc") || (command_name == "wchat")) {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
if ((session->version() != GameVersion::GC) || if ((ses->version() != GameVersion::GC) ||
!session->config.check_flag(Client::Flag::IS_EPISODE_3)) { !ses->config.check_flag(Client::Flag::IS_EPISODE_3)) {
throw runtime_error("wchat can only be used on Episode 3"); throw runtime_error("wchat can only be used on Episode 3");
} }
string data(8, '\0'); string data(8, '\0');
@@ -715,27 +714,27 @@ Proxy session commands:\n\
data += command_args; data += command_args;
data.push_back('\0'); data.push_back('\0');
data.resize((data.size() + 3) & (~3)); data.resize((data.size() + 3) & (~3));
session->server_channel.send(0x06, 0x00, data); ses->server_channel.send(0x06, 0x00, data);
} else if (command_name == "marker") { } else if (command_name == "marker") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
session->server_channel.send(0x89, stoul(command_args)); ses->server_channel.send(0x89, stoul(command_args));
} else if ((command_name == "warp") || (command_name == "warpme")) { } else if ((command_name == "warp") || (command_name == "warpme")) {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
uint8_t area = stoul(command_args); uint8_t area = stoul(command_args);
send_warp(session->client_channel, session->lobby_client_id, area, true); send_warp(ses->client_channel, ses->lobby_client_id, area, true);
} else if (command_name == "warpall") { } else if (command_name == "warpall") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
uint8_t area = stoul(command_args); uint8_t area = stoul(command_args);
send_warp(session->client_channel, session->lobby_client_id, area, false); send_warp(ses->client_channel, ses->lobby_client_id, area, false);
send_warp(session->server_channel, session->lobby_client_id, area, false); send_warp(ses->server_channel, ses->lobby_client_id, area, false);
} else if ((command_name == "info-board") || (command_name == "info-board-data")) { } else if ((command_name == "info-board") || (command_name == "info-board-data")) {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
string data; string data;
if (command_name == "info-board-data") { if (command_name == "info-board-data") {
@@ -746,98 +745,98 @@ Proxy session commands:\n\
data.push_back('\0'); data.push_back('\0');
data.resize((data.size() + 3) & (~3)); data.resize((data.size() + 3) & (~3));
session->server_channel.send(0xD9, 0x00, data); ses->server_channel.send(0xD9, 0x00, data);
} else if (command_name == "set-override-section-id") { } else if (command_name == "set-override-section-id") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
if (command_args.empty()) { if (command_args.empty()) {
session->config.override_section_id = 0xFF; ses->config.override_section_id = 0xFF;
} else { } else {
session->config.override_section_id = section_id_for_name(command_args); ses->config.override_section_id = section_id_for_name(command_args);
} }
} else if (command_name == "set-override-event") { } else if (command_name == "set-override-event") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
if (command_args.empty()) { if (command_args.empty()) {
session->config.override_lobby_event = 0xFF; ses->config.override_lobby_event = 0xFF;
} else { } else {
session->config.override_lobby_event = event_for_name(command_args); ses->config.override_lobby_event = event_for_name(command_args);
if ((session->version() != GameVersion::DC) && if ((ses->version() != GameVersion::DC) &&
(session->version() != GameVersion::PC) && (ses->version() != GameVersion::PC) &&
!((session->version() == GameVersion::GC) && session->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION))) { !((ses->version() == GameVersion::GC) && ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION))) {
session->client_channel.send(0xDA, session->config.override_lobby_event); ses->client_channel.send(0xDA, ses->config.override_lobby_event);
} }
} }
} else if (command_name == "set-override-lobby-number") { } else if (command_name == "set-override-lobby-number") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
if (command_args.empty()) { if (command_args.empty()) {
session->config.override_lobby_number = 0x80; ses->config.override_lobby_number = 0x80;
} else { } else {
session->config.override_lobby_number = lobby_type_for_name(command_args); ses->config.override_lobby_number = lobby_type_for_name(command_args);
} }
} else if (command_name == "set-challenge-rank-title") { } else if (command_name == "set-challenge-rank-title") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
session->challenge_rank_title_override = command_args; ses->challenge_rank_title_override = command_args;
} else if (command_name == "set-challenge-rank-color") { } else if (command_name == "set-challenge-rank-color") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
session->challenge_rank_color_override = stoul(command_args, nullptr, 16); ses->challenge_rank_color_override = stoul(command_args, nullptr, 16);
} else if (command_name == "set-chat-filter") { } else if (command_name == "set-chat-filter") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
set_flag(session->config, Client::Flag::PROXY_CHAT_FILTER_ENABLED, command_args); set_flag(ses->config, Client::Flag::PROXY_CHAT_FILTER_ENABLED, command_args);
} else if (command_name == "set-infinite-hp") { } else if (command_name == "set-infinite-hp") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
set_flag(session->config, Client::Flag::INFINITE_HP_ENABLED, command_args); set_flag(ses->config, Client::Flag::INFINITE_HP_ENABLED, command_args);
} else if (command_name == "set-infinite-tp") { } else if (command_name == "set-infinite-tp") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
set_flag(session->config, Client::Flag::INFINITE_TP_ENABLED, command_args); set_flag(ses->config, Client::Flag::INFINITE_TP_ENABLED, command_args);
} else if (command_name == "set-switch-assist") { } else if (command_name == "set-switch-assist") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
set_flag(session->config, Client::Flag::SWITCH_ASSIST_ENABLED, command_args); set_flag(ses->config, Client::Flag::SWITCH_ASSIST_ENABLED, command_args);
} else if (command_name == "set-save-files" && this->state->proxy_allow_save_files) { } else if (command_name == "set-save-files" && this->state->proxy_allow_save_files) {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
set_flag(session->config, Client::Flag::PROXY_SAVE_FILES, command_args); set_flag(ses->config, Client::Flag::PROXY_SAVE_FILES, command_args);
} else if (command_name == "set-block-function-calls") { } else if (command_name == "set-block-function-calls") {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
set_flag(session->config, Client::Flag::PROXY_BLOCK_FUNCTION_CALLS, command_args); set_flag(ses->config, Client::Flag::PROXY_BLOCK_FUNCTION_CALLS, command_args);
} else if ((command_name == "create-item") || (command_name == "set-next-item")) { } else if ((command_name == "create-item") || (command_name == "set-next-item")) {
auto session = this->get_proxy_session(session_name); auto ses = this->get_proxy_session(session_name);
if (session->version() == GameVersion::BB) { if (ses->version() == GameVersion::BB) {
throw runtime_error("proxy session is BB"); throw runtime_error("proxy session is BB");
} }
if (!session->is_in_game) { if (!ses->is_in_game) {
throw runtime_error("proxy session is not in a game"); throw runtime_error("proxy session is not in a game");
} }
if (session->lobby_client_id != session->leader_client_id) { if (ses->lobby_client_id != ses->leader_client_id) {
throw runtime_error("proxy session is not game leader"); throw runtime_error("proxy session is not game leader");
} }
auto s = session->require_server_state(); auto s = ses->require_server_state();
ItemData item = s->item_name_index->parse_item_description(session->version(), command_args); ItemData item = s->item_name_index->parse_item_description(ses->version(), command_args);
item.id = random_object<uint32_t>(); item.id = random_object<uint32_t>();
if (command_name == "set-next-item") { if (command_name == "set-next-item") {
session->next_drop_item = item; ses->next_drop_item = item;
string name = s->describe_item(session->version(), session->next_drop_item, true); string name = s->describe_item(ses->version(), ses->next_drop_item, true);
send_text_message(session->client_channel, "$C7Next drop:\n" + name); send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
} else { } else {
send_drop_stacked_item(session->client_channel, item, session->area, session->x, session->z); send_drop_stacked_item(s, ses->client_channel, item, ses->area, ses->x, ses->z);
send_drop_stacked_item(session->server_channel, item, session->area, session->x, session->z); send_drop_stacked_item(s, ses->server_channel, item, ses->area, ses->x, ses->z);
string name = s->describe_item(session->version(), session->next_drop_item, true); string name = s->describe_item(ses->version(), ses->next_drop_item, true);
send_text_message(session->client_channel, "$C7Item created:\n" + name); send_text_message(ses->client_channel, "$C7Item created:\n" + name);
} }
} else if (command_name == "close-idle-sessions") { } else if (command_name == "close-idle-sessions") {