Core bpay fixes
This commit is contained in:
@@ -41,6 +41,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -720,7 +721,7 @@ bool BattlePayMgr::AssembleCatalog(std::vector<uint8>& outBlob, std::unordered_m
|
|||||||
outRouting.clear();
|
outRouting.clear();
|
||||||
|
|
||||||
if (_templateBlob.empty())
|
if (_templateBlob.empty())
|
||||||
return false;
|
return BuildCatalogFromDB(outBlob, outRouting);
|
||||||
|
|
||||||
// The writer now decodes ALL FOUR arrays (94 products, 116 deliverables, 21 groups, 97 shop
|
// The writer now decodes ALL FOUR arrays (94 products, 116 deliverables, 21 groups, 97 shop
|
||||||
// entries) with a byte-exact round trip, instead of only the first 9 "simple shape" records.
|
// entries) with a byte-exact round trip, instead of only the first 9 "simple shape" records.
|
||||||
@@ -810,33 +811,46 @@ bool BattlePayMgr::AssembleCatalog(std::vector<uint8>& outBlob, std::unordered_m
|
|||||||
rec.DisplayInfo->Flags = displayInfoFlags;
|
rec.DisplayInfo->Flags = displayInfoFlags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Build a name->shop_product index for automatic matching of retail catalog products to our
|
||||||
|
// gold-priced shop_product rows. This lets the template's display art (FileDataID, ModelSceneID,
|
||||||
|
// DisplayCards) survive while we patch prices and deliverables.
|
||||||
|
std::unordered_map<std::string, ShopProduct const*> nameIndex;
|
||||||
|
for (auto const& [id, product] : _products)
|
||||||
|
if (product.Enabled && !product.Name.empty())
|
||||||
|
nameIndex[product.Name] = &product;
|
||||||
|
|
||||||
|
// Track which shop_products have been assigned so we can emit unpinned ones as extras.
|
||||||
|
std::unordered_set<uint32> assignedProducts;
|
||||||
|
|
||||||
size_t candIdx = 0;
|
size_t candIdx = 0;
|
||||||
for (size_t slot = 0; slot < catalog.Products.size(); ++slot)
|
for (size_t slot = 0; slot < catalog.Products.size(); ++slot)
|
||||||
{
|
{
|
||||||
// The id the client actually purchases by. The previous code read record+81, which is really
|
|
||||||
// DisplayInfo.fileDataID - the card ARTWORK id - so the routing map was built from FileDataIDs
|
|
||||||
// and never resolved. Purchases only worked because GetProductByAdvertisedId falls through to
|
|
||||||
// GetProduct(id).
|
|
||||||
uint32 const slotProductId = catalog.Products[slot].ProductID;
|
uint32 const slotProductId = catalog.Products[slot].ProductID;
|
||||||
ShopProduct const* assigned = nullptr;
|
ShopProduct const* assigned = nullptr;
|
||||||
|
|
||||||
|
// Priority 1: explicit slot override
|
||||||
auto ovr = _slotOverrides.find(uint8(slot));
|
auto ovr = _slotOverrides.find(uint8(slot));
|
||||||
if (ovr != _slotOverrides.end())
|
if (ovr != _slotOverrides.end())
|
||||||
{
|
{
|
||||||
if (ovr->second != 0) // pinned; 0 = forced placeholder
|
if (ovr->second != 0) // pinned; 0 = forced placeholder
|
||||||
assigned = GetProduct(ovr->second);
|
assigned = GetProduct(ovr->second);
|
||||||
}
|
}
|
||||||
// NOTE: no automatic slot filling any more. The catalog we ship already describes 94 real
|
|
||||||
// products with our own gold prices patched in, and shop_product is keyed on those same
|
// Priority 2: match by DisplayInfo name against shop_product names.
|
||||||
// wire productIDs, so every card resolves on its own. Auto-assigning candidates to slots
|
// This picks up the remaining ~57 shop_products that have no slot override but whose
|
||||||
// here would stamp one product's name and price onto a different product's card - harmless
|
// names appear in the retail catalog (e.g. "Anima Wyrmling", "Lil' Ursoc", etc.).
|
||||||
// when only 9 rows existed, actively wrong now there are 66. Explicit shop_slot_override
|
if (!assigned && catalog.Products[slot].DisplayInfo)
|
||||||
// pins still work above for anyone who wants to re-badge a specific slot.
|
{
|
||||||
|
auto it = nameIndex.find(catalog.Products[slot].DisplayInfo->Name1);
|
||||||
|
if (it != nameIndex.end())
|
||||||
|
assigned = it->second;
|
||||||
|
}
|
||||||
|
|
||||||
if (assigned)
|
if (assigned)
|
||||||
{
|
{
|
||||||
reskin(catalog.Products[slot], *assigned);
|
reskin(catalog.Products[slot], *assigned);
|
||||||
outRouting[slotProductId] = assigned->ProductID;
|
outRouting[slotProductId] = assigned->ProductID;
|
||||||
|
assignedProducts.insert(assigned->ProductID);
|
||||||
if (report)
|
if (report)
|
||||||
report->append(Trinity::StringFormat(" slot {}: [{}] '{}' -> product {} (price {}, {}{})\n",
|
report->append(Trinity::StringFormat(" slot {}: [{}] '{}' -> product {} (price {}, {}{})\n",
|
||||||
slot, slotProductId, assigned->Name, assigned->ProductID, assigned->Price,
|
slot, slotProductId, assigned->Name, assigned->ProductID, assigned->Price,
|
||||||
@@ -844,22 +858,126 @@ bool BattlePayMgr::AssembleCatalog(std::vector<uint8>& outBlob, std::unordered_m
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Not pinned. Leave the shipped record's price alone - the blob already describes a real
|
// Not pinned and no name match. Hide this retail-only card by clearing its
|
||||||
// product with our gold price patched in, and shop_product is keyed on the same wire
|
// DisplayInfo so the client treats it as an empty slot. This prevents the user
|
||||||
// productID. Only give it a DisplayInfo when it has none, so the confirmation dialog can
|
// from clicking a $-priced product that cannot be delivered.
|
||||||
// render a name instead of erroring on nil.
|
|
||||||
if (!catalog.Products[slot].DisplayInfo)
|
|
||||||
{
|
|
||||||
if (ShopProduct const* known = GetProduct(slotProductId))
|
|
||||||
catalog.Products[slot].DisplayInfo = BattlePayCatalogWriter::MakeDisplayInfo(known->Name, known->Description);
|
|
||||||
else
|
|
||||||
catalog.Products[slot].DisplayInfo = BattlePayCatalogWriter::MakeDisplayInfo(placeholderName, std::string());
|
catalog.Products[slot].DisplayInfo = BattlePayCatalogWriter::MakeDisplayInfo(placeholderName, std::string());
|
||||||
}
|
catalog.Products[slot].Flags = 0; // clear buyableHere bits so Buy button is greyed out
|
||||||
if (report)
|
if (report)
|
||||||
report->append(Trinity::StringFormat(" slot {}: [{}] <placeholder - not purchasable>\n", slot, slotProductId));
|
report->append(Trinity::StringFormat(" slot {}: [{}] <hidden - not routable>\n", slot, slotProductId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append shop_products that did NOT match any catalog slot as new entries at the end.
|
||||||
|
// These are products in the DB that have no retail template counterpart.
|
||||||
|
for (auto const& [id, product] : _products)
|
||||||
|
{
|
||||||
|
if (!product.Enabled || assignedProducts.count(product.ProductID))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BattlePayCatalogProduct catalogProd;
|
||||||
|
catalogProd.ProductID = product.ProductID;
|
||||||
|
catalogProd.Flags = 10;
|
||||||
|
catalogProd.Type = 0;
|
||||||
|
catalogProd.Eligibility = 0;
|
||||||
|
catalogProd.PmtProductID = product.ProductID;
|
||||||
|
catalogProd.RequiredDeliverableID = 0;
|
||||||
|
catalogProd.PadBits = 0;
|
||||||
|
catalogProd.BundledProductIDs = {};
|
||||||
|
|
||||||
|
uint64 displayPrice;
|
||||||
|
if (product.HasDisplayPrice)
|
||||||
|
displayPrice = product.DisplayPrice;
|
||||||
|
else if (product.Currency == 1)
|
||||||
|
displayPrice = (product.Price / 10000) * 100000;
|
||||||
|
else
|
||||||
|
displayPrice = 0;
|
||||||
|
|
||||||
|
catalogProd.NormalPriceFixedPoint = displayPrice;
|
||||||
|
catalogProd.CurrentPriceFixedPoint = displayPrice;
|
||||||
|
catalogProd.DisplayInfo = BattlePayCatalogWriter::MakeDisplayInfo(product.Name, product.Description);
|
||||||
|
|
||||||
|
// Build deliverables for this product
|
||||||
|
std::vector<uint32> delivIds;
|
||||||
|
for (size_t i = 0; i < product.Deliverables.size(); ++i)
|
||||||
|
{
|
||||||
|
uint32 delivId = uint32(catalog.Deliverables.size()) + 1;
|
||||||
|
ShopDeliverable const& dv = product.Deliverables[i];
|
||||||
|
BattlePayDeliverable catalogDv;
|
||||||
|
catalogDv.DeliverableID = delivId;
|
||||||
|
catalogDv.Name = product.Name;
|
||||||
|
catalogDv.AlreadyOwns = 0;
|
||||||
|
catalogDv.HasPetResult = 0;
|
||||||
|
catalogDv.PetResult = 0;
|
||||||
|
catalogDv.PadBits = 0;
|
||||||
|
|
||||||
|
switch (dv.Type)
|
||||||
|
{
|
||||||
|
case 1: catalogDv.Type = 14; catalogDv.ItemID = dv.Id; catalogDv.Quantity = dv.Count; break;
|
||||||
|
case 2: catalogDv.Type = 3; catalogDv.MountSpellID = dv.Id; break;
|
||||||
|
case 3: catalogDv.Type = 4; catalogDv.Quantity = dv.Count; break;
|
||||||
|
case 5: catalogDv.Type = dv.Id;
|
||||||
|
if (dv.Id == 1) catalogDv.BoostID = 11;
|
||||||
|
break;
|
||||||
|
default: catalogDv.Type = dv.Type; catalogDv.ItemID = dv.Id; catalogDv.Quantity = dv.Count; break;
|
||||||
|
}
|
||||||
|
catalog.Deliverables.push_back(catalogDv);
|
||||||
|
delivIds.push_back(delivId);
|
||||||
|
}
|
||||||
|
if (delivIds.empty())
|
||||||
|
{
|
||||||
|
uint32 delivId = uint32(catalog.Deliverables.size()) + 1;
|
||||||
|
BattlePayDeliverable catalogDv;
|
||||||
|
catalogDv.DeliverableID = delivId;
|
||||||
|
catalogDv.Type = 0;
|
||||||
|
catalogDv.ItemID = 0;
|
||||||
|
catalogDv.Quantity = 0;
|
||||||
|
catalogDv.Name = product.Name;
|
||||||
|
catalog.Deliverables.push_back(catalogDv);
|
||||||
|
delivIds.push_back(delivId);
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogProd.DeliverableIDs = std::move(delivIds);
|
||||||
|
|
||||||
|
catalog.Products.push_back(catalogProd);
|
||||||
|
|
||||||
|
// Add a group if needed
|
||||||
|
bool groupExists = false;
|
||||||
|
for (auto const& g : catalog.Groups)
|
||||||
|
if (g.GroupID == product.GroupId) { groupExists = true; break; }
|
||||||
|
if (!groupExists)
|
||||||
|
{
|
||||||
|
BattlePayProductGroup group;
|
||||||
|
group.GroupID = product.GroupId;
|
||||||
|
group.Ordering = product.GroupId;
|
||||||
|
group.Flags = 0;
|
||||||
|
group.ParentGroupID = 0;
|
||||||
|
group.DisplayType = 0;
|
||||||
|
group.IconFileDataID = 0;
|
||||||
|
group.Name = "Category " + std::to_string(product.GroupId);
|
||||||
|
group.DisabledDescription = "";
|
||||||
|
catalog.Groups.push_back(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a shop entry
|
||||||
|
BattlePayShopEntry entry;
|
||||||
|
entry.EntryID = uint32(catalog.Entries.size()) + 1;
|
||||||
|
entry.GroupID = product.GroupId;
|
||||||
|
entry.ProductID = product.ProductID;
|
||||||
|
entry.Ordering = product.Ordering;
|
||||||
|
entry.Flags = 0;
|
||||||
|
entry.BannerType = 0;
|
||||||
|
entry.DisplayInfo = BattlePayCatalogWriter::MakeDisplayInfo(product.Name, product.Description);
|
||||||
|
entry.PadBits = 0;
|
||||||
|
catalog.Entries.push_back(entry);
|
||||||
|
|
||||||
|
outRouting[product.ProductID] = product.ProductID;
|
||||||
|
|
||||||
|
if (report)
|
||||||
|
report->append(Trinity::StringFormat(" slot {}: [NEW] '{}' -> product {} (price {})\n",
|
||||||
|
catalog.Products.size() - 1, product.Name, product.ProductID, displayPrice));
|
||||||
|
}
|
||||||
|
|
||||||
if (report && candIdx < candidates.size())
|
if (report && candIdx < candidates.size())
|
||||||
report->append(Trinity::StringFormat(" OVERFLOW: {} enabled product(s) could not be shown (only {} slots).\n",
|
report->append(Trinity::StringFormat(" OVERFLOW: {} enabled product(s) could not be shown (only {} slots).\n",
|
||||||
candidates.size() - candIdx, catalog.Products.size()));
|
candidates.size() - candIdx, catalog.Products.size()));
|
||||||
@@ -1014,3 +1132,225 @@ std::string BattlePayMgr::BuildStatusReport() const
|
|||||||
report.append(Trinity::StringFormat("Assembled blob: {} bytes, generation {}.", blob.size(), _catalogGeneration));
|
report.append(Trinity::StringFormat("Assembled blob: {} bytes, generation {}.", blob.size(), _catalogGeneration));
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BattlePayMgr::BuildCatalogFromDB(std::vector<uint8>& outBlob, std::unordered_map<uint32, uint32>& outRouting) const
|
||||||
|
{
|
||||||
|
BattlePayCatalog catalog;
|
||||||
|
catalog.Result = 0;
|
||||||
|
catalog.CurrencyID = 0;
|
||||||
|
|
||||||
|
std::string const placeholderName = std::string(sConfigMgr->GetStringDefault("Shop.PlaceholderName", "Currently unavailable"));
|
||||||
|
|
||||||
|
// Collect all unique groupIds
|
||||||
|
std::set<uint32> groupIds;
|
||||||
|
for (auto const& [id, product] : _products)
|
||||||
|
if (product.Enabled)
|
||||||
|
groupIds.insert(product.GroupId);
|
||||||
|
|
||||||
|
// Build deliverable map: productId -> vector of deliverable IDs in the catalog
|
||||||
|
std::unordered_map<uint32, std::vector<uint32>> productDeliverableIds;
|
||||||
|
|
||||||
|
// Build deliverables ? mirror the type mapping in BuildDeliverable (BattlePayHandler.cpp:717).
|
||||||
|
// DB types: 1=item, 2=mount/spell, 3=WoW Token, 5=service.
|
||||||
|
// Catalog wire types: 14=Item/Toy, 3=Mount, 4=WowToken, service-id passthrough for type 5.
|
||||||
|
uint32 deliverableId = 1;
|
||||||
|
for (auto const& [id, product] : _products)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < product.Deliverables.size(); ++i)
|
||||||
|
{
|
||||||
|
ShopDeliverable const& dv = product.Deliverables[i];
|
||||||
|
BattlePayDeliverable catalogDv;
|
||||||
|
catalogDv.DeliverableID = deliverableId;
|
||||||
|
catalogDv.Name = product.Name;
|
||||||
|
catalogDv.AlreadyOwns = 0;
|
||||||
|
catalogDv.HasPetResult = 0;
|
||||||
|
catalogDv.PetResult = 0;
|
||||||
|
catalogDv.PadBits = 0;
|
||||||
|
|
||||||
|
switch (dv.Type)
|
||||||
|
{
|
||||||
|
case 1: // item / toy
|
||||||
|
catalogDv.Type = 14;
|
||||||
|
catalogDv.ItemID = dv.Id;
|
||||||
|
catalogDv.Quantity = dv.Count;
|
||||||
|
break;
|
||||||
|
case 2: // mount / toy spell
|
||||||
|
catalogDv.Type = 3;
|
||||||
|
catalogDv.MountSpellID = dv.Id;
|
||||||
|
break;
|
||||||
|
case 3: // WoW Token
|
||||||
|
catalogDv.Type = 4;
|
||||||
|
catalogDv.Quantity = dv.Count;
|
||||||
|
break;
|
||||||
|
case 5: // service ? id is the catalog service type (1=Boost, 5=NameChange, etc.)
|
||||||
|
catalogDv.Type = dv.Id;
|
||||||
|
if (dv.Id == 1 /* SHOP_SERVICE_CHARACTER_BOOST */)
|
||||||
|
catalogDv.BoostID = 11;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
catalogDv.Type = dv.Type;
|
||||||
|
catalogDv.ItemID = dv.Id;
|
||||||
|
catalogDv.Quantity = dv.Count;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog.Deliverables.push_back(catalogDv);
|
||||||
|
productDeliverableIds[id].push_back(deliverableId);
|
||||||
|
++deliverableId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no deliverables, add an empty one so the product is structurally valid
|
||||||
|
if (product.Deliverables.empty())
|
||||||
|
{
|
||||||
|
BattlePayDeliverable catalogDv;
|
||||||
|
catalogDv.DeliverableID = deliverableId;
|
||||||
|
catalogDv.Type = 0;
|
||||||
|
catalogDv.ItemID = 0;
|
||||||
|
catalogDv.Quantity = 0;
|
||||||
|
catalogDv.Name = product.Name;
|
||||||
|
catalog.Deliverables.push_back(catalogDv);
|
||||||
|
productDeliverableIds[id].push_back(deliverableId);
|
||||||
|
++deliverableId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build groups
|
||||||
|
for (uint32 gid : groupIds)
|
||||||
|
{
|
||||||
|
BattlePayProductGroup group;
|
||||||
|
group.GroupID = gid;
|
||||||
|
group.Ordering = gid;
|
||||||
|
group.Flags = 0;
|
||||||
|
group.ParentGroupID = 0;
|
||||||
|
group.DisplayType = 0;
|
||||||
|
group.IconFileDataID = 0;
|
||||||
|
|
||||||
|
// Name the groups based on typical WoW store categories
|
||||||
|
switch (gid)
|
||||||
|
{
|
||||||
|
case 0: group.Name = "Services"; break;
|
||||||
|
case 13: group.Name = "Pets"; break;
|
||||||
|
case 15: group.Name = "Mounts"; break;
|
||||||
|
case 30: group.Name = "WoW Token"; break;
|
||||||
|
case 138: group.Name = "Toys"; break;
|
||||||
|
case 139: group.Name = "Transmog"; break;
|
||||||
|
case 158: group.Name = "Subscriptions"; break;
|
||||||
|
case 195: group.Name = "Hunter Pets"; break;
|
||||||
|
case 197: group.Name = "Vendor Mounts"; break;
|
||||||
|
case 209: group.Name = "Expansions"; break;
|
||||||
|
case 225: group.Name = "Bundles"; break;
|
||||||
|
default: group.Name = "Category " + std::to_string(gid); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
group.DisabledDescription = "";
|
||||||
|
catalog.Groups.push_back(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build products + shop entries
|
||||||
|
uint32 entryId = 1;
|
||||||
|
for (auto const& [id, product] : _products)
|
||||||
|
{
|
||||||
|
if (!product.Enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BattlePayCatalogProduct catalogProd;
|
||||||
|
catalogProd.ProductID = product.ProductID;
|
||||||
|
catalogProd.Flags = 10; // bits 1+3 drive buyableHere
|
||||||
|
catalogProd.Type = 0;
|
||||||
|
catalogProd.Eligibility = 0;
|
||||||
|
catalogProd.PmtProductID = product.ProductID;
|
||||||
|
catalogProd.RequiredDeliverableID = 0;
|
||||||
|
catalogProd.DeliverableIDs = productDeliverableIds[id];
|
||||||
|
catalogProd.BundledProductIDs = {};
|
||||||
|
catalogProd.PadBits = 0;
|
||||||
|
|
||||||
|
// Price
|
||||||
|
uint64 displayPrice;
|
||||||
|
if (product.HasDisplayPrice)
|
||||||
|
displayPrice = product.DisplayPrice;
|
||||||
|
else if (product.Currency == 1)
|
||||||
|
displayPrice = (product.Price / 10000) * 100000;
|
||||||
|
else
|
||||||
|
displayPrice = 0;
|
||||||
|
|
||||||
|
catalogProd.NormalPriceFixedPoint = displayPrice;
|
||||||
|
catalogProd.CurrentPriceFixedPoint = displayPrice;
|
||||||
|
|
||||||
|
// DisplayInfo
|
||||||
|
catalogProd.DisplayInfo = BattlePayCatalogWriter::MakeDisplayInfo(product.Name, product.Description);
|
||||||
|
|
||||||
|
catalog.Products.push_back(catalogProd);
|
||||||
|
|
||||||
|
// ShopEntry: one tile per product
|
||||||
|
BattlePayShopEntry entry;
|
||||||
|
entry.EntryID = entryId++;
|
||||||
|
entry.GroupID = product.GroupId;
|
||||||
|
entry.ProductID = product.ProductID;
|
||||||
|
entry.Ordering = product.Ordering;
|
||||||
|
entry.Flags = 0;
|
||||||
|
entry.BannerType = 0;
|
||||||
|
entry.DisplayInfo = BattlePayCatalogWriter::MakeDisplayInfo(product.Name, product.Description);
|
||||||
|
entry.PadBits = 0;
|
||||||
|
|
||||||
|
catalog.Entries.push_back(entry);
|
||||||
|
|
||||||
|
// Every product routes to itself
|
||||||
|
outRouting[product.ProductID] = product.ProductID;
|
||||||
|
}
|
||||||
|
|
||||||
|
outBlob = BattlePayCatalogWriter::Serialize(catalog);
|
||||||
|
TC_LOG_INFO("server.loading", "BattlePay: built catalog from DB ({} bytes, {} products, {} deliverables, {} groups, {} entries).",
|
||||||
|
outBlob.size(), catalog.Products.size(), catalog.Deliverables.size(),
|
||||||
|
catalog.Groups.size(), catalog.Entries.size());
|
||||||
|
|
||||||
|
// Hex dump first 64 bytes for format verification
|
||||||
|
if (outBlob.size() >= 64)
|
||||||
|
{
|
||||||
|
std::string hex;
|
||||||
|
hex.reserve(192);
|
||||||
|
for (size_t i = 0; i < 64; ++i)
|
||||||
|
{
|
||||||
|
char buf[4];
|
||||||
|
snprintf(buf, sizeof(buf), "%02x ", outBlob[i]);
|
||||||
|
hex += buf;
|
||||||
|
if (i == 15 || i == 31 || i == 47)
|
||||||
|
hex += "| ";
|
||||||
|
}
|
||||||
|
TC_LOG_INFO("server.loading", "BattlePay: blob hex (first 64): {}", hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-check: Parse the blob back and re-serialize to verify round-trip integrity
|
||||||
|
{
|
||||||
|
BattlePayCatalog reparsed;
|
||||||
|
std::string parseError;
|
||||||
|
if (!BattlePayCatalogWriter::Parse(outBlob, reparsed, &parseError))
|
||||||
|
{
|
||||||
|
TC_LOG_ERROR("server.loading", "BattlePay: SelfCheck FAILED - parse error: {}", parseError);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::vector<uint8> reSerialized = BattlePayCatalogWriter::Serialize(reparsed);
|
||||||
|
if (reSerialized != outBlob)
|
||||||
|
{
|
||||||
|
TC_LOG_ERROR("server.loading", "BattlePay: SelfCheck FAILED - round-trip mismatch! Original {} bytes, re-serialized {} bytes.",
|
||||||
|
outBlob.size(), reSerialized.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TC_LOG_INFO("server.loading", "BattlePay: SelfCheck OK - round-trip matches ({} bytes, {} products, {} deliverables, {} groups, {} entries).",
|
||||||
|
reSerialized.size(), reparsed.Products.size(), reparsed.Deliverables.size(),
|
||||||
|
reparsed.Groups.size(), reparsed.Entries.size());
|
||||||
|
|
||||||
|
// Log first product details
|
||||||
|
if (!reparsed.Products.empty())
|
||||||
|
{
|
||||||
|
auto const& p = reparsed.Products.front();
|
||||||
|
TC_LOG_INFO("server.loading", "BattlePay: First product: ID={}, Type={}, Flags={}, Eligibility={}, Price={}, CurrencyID={}, DeliverableIDs={}",
|
||||||
|
p.ProductID, p.Type, p.Flags, p.Eligibility, p.NormalPriceFixedPoint,
|
||||||
|
reparsed.CurrencyID, p.DeliverableIDs.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -273,6 +273,9 @@ private:
|
|||||||
bool AssembleCatalog(std::vector<uint8>& outBlob, std::unordered_map<uint32, uint32>& outRouting,
|
bool AssembleCatalog(std::vector<uint8>& outBlob, std::unordered_map<uint32, uint32>& outRouting,
|
||||||
std::string* report) const;
|
std::string* report) const;
|
||||||
|
|
||||||
|
// Builds a complete catalog blob from DB data alone, without the retail template blob.
|
||||||
|
bool BuildCatalogFromDB(std::vector<uint8>& outBlob, std::unordered_map<uint32, uint32>& outRouting) const;
|
||||||
|
|
||||||
std::vector<uint8> _templateBlob;
|
std::vector<uint8> _templateBlob;
|
||||||
std::vector<uint8> _productListBlob;
|
std::vector<uint8> _productListBlob;
|
||||||
std::vector<uint8> _distributionListBlob;
|
std::vector<uint8> _distributionListBlob;
|
||||||
|
|||||||
@@ -783,7 +783,14 @@ void WorldSession::SendBattlePayDistributionList()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sBattlePayMgr->HasDistributionList())
|
if (!sBattlePayMgr->HasDistributionList())
|
||||||
|
{
|
||||||
|
// Build an empty distribution list to unblock the client's shop panel loading state.
|
||||||
|
WorldPackets::BattlePay::GetDistributionListResponse response;
|
||||||
|
response.BuildFromObjects = true;
|
||||||
|
response.Result = RESULT_OK;
|
||||||
|
SendPacket(response.Write());
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WorldPackets::BattlePay::GetDistributionListResponse response;
|
WorldPackets::BattlePay::GetDistributionListResponse response;
|
||||||
response.RawData = &sBattlePayMgr->GetDistributionListBlob();
|
response.RawData = &sBattlePayMgr->GetDistributionListBlob();
|
||||||
|
|||||||
Reference in New Issue
Block a user