Implement bazaar item identity and offline trading rework

This commit is contained in:
Vayle
2026-03-19 22:18:32 -04:00
90 changed files with 5379 additions and 2165 deletions
+101 -84
View File
@@ -437,7 +437,6 @@ namespace RoF2
break;
}
default: {
LogTradingDetail("Unhandled action <red>[{}]", sub_action);
dest->FastQueuePacket(&in);
}
}
@@ -478,7 +477,7 @@ namespace RoF2
for (auto i: results) {
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.trader_id); //trader ID
VARSTRUCT_ENCODE_STRING(bufptr, i.serial_number_RoF.c_str()); //serial
VARSTRUCT_ENCODE_STRING(bufptr, i.item_unique_id.c_str()); //serial
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.cost); //cost
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.stackable ? i.charges : i.count); //quantity
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.item_id); //ID
@@ -531,7 +530,6 @@ namespace RoF2
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
dest->FastQueuePacket(&in, ack_req);
}
}
@@ -620,10 +618,6 @@ namespace RoF2
break;
}
default: {
LogTrading(
"(RoF2) Unhandled action <red>[{}]",
in->action
);
dest->QueuePacket(inapp);
}
}
@@ -755,7 +749,7 @@ namespace RoF2
ar(bl);
//packet size
auto packet_size = bl.item_name.length() + 1 + 34;
uint32 packet_size = bl.item_name.length() + 1 + 34;
for (auto const &b: bl.trade_items) {
packet_size += b.item_name.length() + 1;
packet_size += 12;
@@ -1843,7 +1837,7 @@ namespace RoF2
e->zoneinstance = 0;
e->zone_id = htons(emu_e->zone_id);
e->unknown_one2 = htonl(1);
e->unknown04 = 0;
e->offline_mode = htonl(emu_e->offline_mode);
#undef SlideStructString
#undef PutFieldN
@@ -1860,14 +1854,12 @@ namespace RoF2
{
SETUP_DIRECT_ENCODE(GuildMemberUpdate_Struct, structs::GuildMemberUpdate_Struct);
OUT(GuildID);
memcpy(eq->MemberName, emu->MemberName, sizeof(eq->MemberName));
//OUT(ZoneID);
//OUT(InstanceID);
eq->InstanceID = emu->InstanceID;
eq->ZoneID = emu->ZoneID;
OUT(LastSeen);
eq->Unknown76 = 0;
eq->guild_id = emu->GuildID;
eq->last_seen = emu->LastSeen;
eq->instance_id = emu->InstanceID;
eq->zone_id = emu->ZoneID;
eq->offline_mode = emu->offline_mode;
memcpy(eq->member_name, emu->MemberName, sizeof(eq->member_name));
FINISH_ENCODE();
}
@@ -4113,26 +4105,41 @@ namespace RoF2
break;
}
case ListTraderItems: {
ENCODE_LENGTH_EXACT(Trader_Struct);
SETUP_DIRECT_ENCODE(Trader_Struct, structs::ClickTrader_Struct);
LogTrading("(RoF2) action <green>[{}]", action);
eq->action = structs::RoF2BazaarTraderBuyerActions::ListTraderItems;
std::transform(
std::begin(emu->items),
std::end(emu->items),
std::begin(eq->items),
[&](const uint32 x) {
return x;
}
);
std::copy_n(
std::begin(emu->item_cost),
EQ::invtype::BAZAAR_SIZE,
std::begin(eq->item_cost)
);
EQApplicationPacket *in = *p;
*p = nullptr;
FINISH_ENCODE();
TraderClientMessaging_Struct tcm{};
EQ::Util::MemoryStreamReader ss(reinterpret_cast<char *>(in->pBuffer), in->size);
cereal::BinaryInputArchive ar(ss);
{
ar(tcm);
}
auto buffer = new char[4404]{}; // 4404 is the fixed size of the packet for 200 item limit of RoF2
auto pos = buffer;
auto pos_unique_id = buffer + 4;
auto pos_cost = buffer + 3604;
VARSTRUCT_ENCODE_TYPE(uint32, pos, structs::RoF2BazaarTraderBuyerActions::ListTraderItems);
for (auto const &t: tcm.items) {
strn0cpy(pos_unique_id, t.item_unique_id.data(), t.item_unique_id.length() + 1);
*(uint32 *) pos_cost = t.item_cost;
pos_unique_id += 18;
pos_cost += 4;
}
for (int i = tcm.items.size(); i < EQ::invtype::BAZAAR_SIZE; i++) {
strn0cpy(pos_unique_id, "0000000000000000", 18);
pos_unique_id += 18;
}
safe_delete_array(in->pBuffer);
in->pBuffer = reinterpret_cast<unsigned char *>(buffer);
in->size = 4404;
dest->FastQueuePacket(&in);
break;
}
case TraderAck2: {
@@ -4145,7 +4152,7 @@ namespace RoF2
}
case PriceUpdate: {
SETUP_DIRECT_ENCODE(TraderPriceUpdate_Struct, structs::TraderPriceUpdate_Struct);
switch (emu->SubAction) {
switch (emu->sub_action) {
case BazaarPriceChange_AddItem: {
auto outapp = std::make_unique<EQApplicationPacket>(
OP_Trader,
@@ -4153,7 +4160,7 @@ namespace RoF2
);
auto data = (structs::TraderStatus_Struct *) outapp->pBuffer;
data->action = emu->Action;
data->action = emu->action;
data->sub_action = BazaarPriceChange_AddItem;
LogTrading(
"(RoF2) PriceUpdate action <green>[{}] AddItem subaction <yellow>[{}]",
@@ -4171,7 +4178,7 @@ namespace RoF2
);
auto data = (structs::TraderStatus_Struct *) outapp->pBuffer;
data->action = emu->Action;
data->action = emu->action;
data->sub_action = BazaarPriceChange_RemoveItem;
LogTrading(
"(RoF2) PriceUpdate action <green>[{}] RemoveItem subaction <yellow>[{}]",
@@ -4189,7 +4196,7 @@ namespace RoF2
);
auto data = (structs::TraderStatus_Struct *) outapp->pBuffer;
data->action = emu->Action;
data->action = emu->action;
data->sub_action = BazaarPriceChange_UpdatePrice;
LogTrading(
"(RoF2) PriceUpdate action <green>[{}] UpdatePrice subaction <yellow>[{}]",
@@ -4214,7 +4221,7 @@ namespace RoF2
"(RoF2) BuyTraderItem action <green>[{}] item_id <green>[{}] item_sn <green>[{}] buyer <green>[{}]",
action,
eq->item_id,
eq->serial_number,
eq->item_unique_id,
eq->buyer_name
);
dest->FastQueuePacket(&in);
@@ -4253,7 +4260,7 @@ namespace RoF2
OUT_str(buyer_name);
OUT_str(seller_name);
OUT_str(item_name);
OUT_str(serial_number);
OUT_str(item_unique_id);
FINISH_ENCODE();
}
@@ -4263,15 +4270,13 @@ namespace RoF2
ENCODE_LENGTH_EXACT(TraderDelItem_Struct);
SETUP_DIRECT_ENCODE(TraderDelItem_Struct, structs::TraderDelItem_Struct);
LogTrading(
"(RoF2) trader_id <green>[{}] item_id <green>[{}]",
"(RoF2) trader_id <green>[{}] item_unique_id <green>[{}]",
emu->trader_id,
emu->item_id
emu->item_unique_id
);
eq->TraderID = emu->trader_id;
auto serial = fmt::format("{:016}\n", emu->item_id);
strn0cpy(eq->SerialNumber, serial.c_str(), sizeof(eq->SerialNumber));
LogTrading("(RoF2) TraderID <green>[{}], SerialNumber: <green>[{}]", emu->trader_id, emu->item_id);
eq->trader_id = emu->trader_id;
strn0cpy(eq->item_unique_id, emu->item_unique_id, sizeof(eq->item_unique_id));
FINISH_ENCODE();
}
@@ -4318,13 +4323,12 @@ namespace RoF2
OUT_str(buyer_name);
OUT_str(seller_name);
OUT_str(item_name);
OUT_str(serial_number);
OUT_str(item_unique_id);
FINISH_ENCODE();
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
EQApplicationPacket *in = *p;
*p = nullptr;
@@ -4460,6 +4464,7 @@ namespace RoF2
*p = nullptr;
char *InBuffer = (char *)in->pBuffer;
std::vector<uint32> p_ids { 0x430, 0x420 };
WhoAllReturnStruct *wars = (WhoAllReturnStruct*)InBuffer;
@@ -4485,8 +4490,9 @@ namespace RoF2
x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x);
InBuffer += 4;
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0);
x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, std::ranges::find(p_ids.begin(), p_ids.end(), x) == p_ids.end() ? 0 : x);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0xffffffff);
char Name[64];
@@ -4722,6 +4728,10 @@ namespace RoF2
OtherData = OtherData | 0x01;
}
if (emu->offline) {
OtherData = OtherData | 0x02;
}
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, OtherData);
// float EmitterScalingRadius
@@ -5092,7 +5102,6 @@ namespace RoF2
}
default: {
auto emu = (BuyerGeneric_Struct *) __packet->pBuffer;
LogTradingDetail("(RoF2) Pass thru OP_Barter packet action <red>[{}]", emu->action);
}
}
}
@@ -6150,21 +6159,35 @@ namespace RoF2
switch (action) {
case structs::RoF2BazaarTraderBuyerActions::BeginTraderMode: {
DECODE_LENGTH_EXACT(structs::BeginTrader_Struct);
SETUP_DIRECT_DECODE(ClickTrader_Struct, structs::BeginTrader_Struct);
LogTrading("(RoF2) BeginTraderMode action <green>[{}]", action);
emu->action = TraderOn;
std::copy_n(eq->item_cost, RoF2::invtype::BAZAAR_SIZE, emu->item_cost);
std::transform(
std::begin(eq->items),
std::end(eq->items),
std::begin(emu->serial_number),
[&](const structs::TraderItemSerial_Struct x) {
return Strings::ToUnsignedBigInt(x.serial_number,0);
unsigned char *eq_buffer = __packet->pBuffer;
auto eq = (RoF2::structs::BeginTrader_Struct *) eq_buffer;
ClickTraderNew_Struct out{};
out.action = TraderOn;
for (auto i = 0; i < RoF2::invtype::BAZAAR_SIZE; i++) {
if (eq->item_cost[i] == 0) {
continue;
}
);
FINISH_DIRECT_DECODE();
BazaarTraderDetails btd{};
btd.unique_id = eq->item_unique_ids[i].item_unique_id;
btd.cost = eq->item_cost[i];
out.items.push_back(btd);
}
std::stringstream ss{};
cereal::BinaryOutputArchive ar(ss);
{
ar(out);
}
__packet->size = static_cast<uint32>(ss.str().length());
__packet->pBuffer = new unsigned char[__packet->size]{};
memcpy(__packet->pBuffer, ss.str().data(), __packet->size);
safe_delete_array(eq_buffer);
LogTrading("(RoF2) BeginTraderMode action <green>[{}]", action);
break;
}
case structs::RoF2BazaarTraderBuyerActions::EndTraderMode: {
@@ -6187,18 +6210,14 @@ namespace RoF2
SETUP_DIRECT_DECODE(TraderPriceUpdate_Struct, structs::TraderPriceUpdate_Struct);
LogTrading("(RoF2) PriceUpdate action <green>[{}]", action);
emu->Action = PriceUpdate;
emu->SerialNumber = Strings::ToUnsignedBigInt(eq->serial_number, 0);
if (emu->SerialNumber == 0) {
LogTrading("(RoF2) Price change with invalid serial number <red>[{}]", eq->serial_number);
}
emu->NewPrice = eq->new_price;
emu->action = PriceUpdate;
strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id));
emu->new_price = eq->new_price;
FINISH_DIRECT_DECODE();
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
}
}
}
@@ -6282,21 +6301,12 @@ namespace RoF2
IN(item_id);
IN(trader_id);
emu->action = BazaarInspect;
emu->serial_number = Strings::ToUnsignedInt(eq->serial_number, 0);
if (emu->serial_number == 0) {
LogTrading(
"(RoF2) trader_id = <green>[{}] requested a BazaarInspect with an invalid serial number of <red>[{}]",
eq->trader_id,
eq->serial_number
);
FINISH_DIRECT_DECODE();
return;
}
strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id));
LogTrading("(RoF2) BazaarInspect action <green>[{}] item_id <green>[{}] serial_number <green>[{}]",
action,
eq->item_id,
eq->serial_number
eq->item_unique_id
);
FINISH_DIRECT_DECODE();
break;
@@ -6333,13 +6343,12 @@ namespace RoF2
IN_str(buyer_name);
IN_str(seller_name);
IN_str(item_name);
IN_str(serial_number);
strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id));
FINISH_DIRECT_DECODE();
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
}
return;
}
@@ -6444,7 +6453,15 @@ namespace RoF2
RoF2::structs::ItemSerializationHeader hdr;
//sprintf(hdr.unknown000, "06e0002Y1W00");
strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000));
if (inst->GetUniqueID().empty()) {
strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000));
}
else {
strn0cpy(hdr.unknown000, inst->GetUniqueID().c_str(),sizeof(hdr.unknown000));
hdr.unknown000[16] = '\0';
}
hdr.stacksize = 1;
+1
View File
@@ -306,6 +306,7 @@ namespace RoF2
const size_t SAY_LINK_BODY_SIZE = 56;
const uint32 MAX_GUILD_ID = 50000;
const uint32 MAX_BAZAAR_TRADERS = 600;
const uint64 MAX_BAZAAR_TRANSACTION = 3276700000000; //3276700000000
} /*constants*/
+56 -55
View File
@@ -3317,7 +3317,7 @@ struct BazaarInspect_Struct {
uint32 action;
uint32 unknown_004;
uint32 trader_id;
char serial_number[17];
char item_unique_id[17];
char unknown_029[3];
uint32 item_id;
uint32 unknown_036;
@@ -3560,19 +3560,21 @@ struct WhoAllPlayerPart4 {
};
struct TraderItemSerial_Struct {
char serial_number[17];
char item_unique_id[17];
uint8 unknown_018;
void operator=(uint32 a) {
auto _tmp = fmt::format("{:016}", a);
strn0cpy(this->serial_number, _tmp.c_str(), sizeof(this->serial_number));
TraderItemSerial_Struct& operator=(const char* a) {
strn0cpy(this->item_unique_id, a, sizeof(this->item_unique_id));
unknown_018 = 0;
return *this;
}
};
struct BeginTrader_Struct {
/*0000*/ uint32 action;
/*0004*/ TraderItemSerial_Struct items[200];
/*3604*/ uint32 item_cost[200];
/*0004*/ TraderItemSerial_Struct item_unique_ids[RoF2::invtype::BAZAAR_SIZE];
/*3604*/ uint32 item_cost[RoF2::invtype::BAZAAR_SIZE];
/*4404*/
};
@@ -3598,11 +3600,11 @@ struct BazaarWindowRemoveTrader_Struct {
};
struct TraderPriceUpdate_Struct {
uint32 action;
char serial_number[17];
char unknown_021[3];
uint32 unknown_024;
uint32 new_price;
/*000*/ uint32 action;
/*004*/ char item_unique_id[17];
/*021*/ char unknown_021[3];
/*024*/ uint32 unknown_024;
/*028*/ uint32 new_price;
};
struct Trader_ShowItems_Struct {
@@ -3620,22 +3622,22 @@ struct TraderStatus_Struct {
};
struct TraderBuy_Struct {
/*000*/ uint32 action;
/*004*/ uint32 method;
/*008*/ uint32 sub_action;
/*012*/ uint32 unknown_012;
/*016*/ uint32 trader_id;
/*020*/ char buyer_name[64];
/*084*/ char seller_name[64];
/*148*/ char unknown_148[32];
/*180*/ char item_name[64];
/*244*/ char serial_number[17];
/*261*/ char unknown_261[3];
/*264*/ uint32 item_id;
/*268*/ uint32 price;
/*272*/ uint32 already_sold;
/*276*/ uint32 unknown_276;
/*280*/ uint32 quantity;
/*000*/ uint32 action;
/*004*/ uint32 method;
/*008*/ uint32 sub_action;
/*012*/ uint32 unknown_012;
/*016*/ uint32 trader_id;
/*020*/ char buyer_name[64];
/*084*/ char seller_name[64];
/*148*/ char unknown_148[32];
/*180*/ char item_name[64];
/*244*/ char item_unique_id[17];
/*261*/ char unknown_261[3];
/*264*/ uint32 item_id;
/*268*/ uint32 price;
/*272*/ uint32 already_sold;
/*276*/ uint32 unknown_276;
/*280*/ uint32 quantity;
/*284*/
};
@@ -3655,10 +3657,10 @@ struct MoneyUpdate_Struct{
};
struct TraderDelItem_Struct{
/*000*/ uint32 Unknown000;
/*004*/ uint32 TraderID;
/*008*/ char SerialNumber[17];
/*024*/ uint32 Unknown012;
/*000*/ uint32 unknown_000;
/*004*/ uint32 trader_id;
/*008*/ char item_unique_id[17];
/*025*/ uint32 unknown_025;
/*028*/
};
@@ -3685,22 +3687,22 @@ struct SimpleMessage_Struct{
// Size: 52 + strings
// Other than the strings, all of this packet is network byte order (reverse from normal)
struct GuildMemberEntry_Struct {
char name[1]; // variable length
uint32 level;
uint32 banker; // 1=yes, 0=no
uint32 class_;
uint32 rank;
uint32 time_last_on;
uint32 tribute_enable;
uint32 unknown01; // Seen 0
uint32 total_tribute; // total guild tribute donated, network byte order
uint32 last_tribute; // unix timestamp
uint32 unknown_one; // unknown, set to 1
char public_note[1]; // variable length.
uint16 zoneinstance; // Seen 0s or -1 in RoF2
uint16 zone_id; // Seen 0s or -1 in RoF2
uint32 unknown_one2; // unknown, set to 1
uint32 unknown04; // Seen 0
char name[1]; // variable length
uint32 level;
uint32 banker; // 1=yes, 0=no
uint32 class_;
uint32 rank;
uint32 time_last_on;
uint32 tribute_enable;
uint32 unknown01; // Seen 0
uint32 total_tribute; // total guild tribute donated, network byte order
uint32 last_tribute; // unix timestamp
uint32 unknown_one; // unknown, set to 1
char public_note[1]; // variable length.
uint16 zoneinstance; // Seen 0s or -1 in RoF2
uint16 zone_id; // Seen 0s or -1 in RoF2
uint32 unknown_one2; // unknown, set to 1
uint32 offline_mode; // Displays OFFLINE MODE instead of Zone Name
};
//just for display purposes, this is not actually used in the message encoding other than for size.
@@ -3735,13 +3737,12 @@ struct GuildStatus_Struct
};
struct GuildMemberUpdate_Struct {
/*00*/ uint32 GuildID;
/*04*/ char MemberName[64];
/*68*/ uint16 ZoneID;
/*70*/ uint16 InstanceID; //speculated
/*72*/ uint32 LastSeen; //unix timestamp
/*76*/ uint32 Unknown76;
/*80*/
/*00*/ uint32 guild_id;
/*04*/ char member_name[64];
/*68*/ uint16 zone_id;
/*70*/ uint16 instance_id; //speculated
/*72*/ uint32 last_seen; //unix timestamp
/*76*/ uint32 offline_mode;
};
struct GuildMemberLevelUpdate_Struct {
-2
View File
@@ -226,7 +226,6 @@ namespace Titanium
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, row->trader_entity_id);
bufptr += 4;
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id);
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number);
bufptr += 4;
if (row->stackable) {
strn0cpy(
@@ -2527,7 +2526,6 @@ namespace Titanium
IN(action);
memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name));
IN(serial_number);
FINISH_DIRECT_DECODE();
break;
-2
View File
@@ -335,7 +335,6 @@ namespace UF
bufptr += 64;
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, 1);
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id);
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number);
bufptr += 4;
if (row->stackable) {
strn0cpy(
@@ -3615,7 +3614,6 @@ namespace UF
IN(action);
memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name));
IN(serial_number);
FINISH_DIRECT_DECODE();
break;