#include "spoofing.h" #include #include #include #include #include #include #include using json = nlohmann::json; std::string getExeDir() { char buffer[MAX_PATH]; GetModuleFileNameA(NULL, buffer, MAX_PATH); std::string path(buffer); size_t pos = path.find_last_of("\\/"); if (pos != std::string::npos) return path.substr(0, pos + 1); return ""; } void Spoofer::init(Proxy* proxy) { loadData(); registerListeners(proxy); } void Spoofer::registerListeners(Proxy* proxy) { proxy->OnServerResponse.addListener([this](const std::string& url, std::string& body, std::string& respHeaders) { this->serverResponseHandler(url, body, respHeaders); }); proxy->OnClientRequest.addListener([this](std::string& url, const std::string& body, std::string& reqHeaders) { this->clientRequestHandler(url, body, reqHeaders); }); } void Spoofer::loadData() { Log::info("Loading dump data"); std::string catalogDumpPath = getExeDir() + "catalog.json"; std::ifstream catalogFile(catalogDumpPath); if (catalogFile.is_open()) { std::stringstream buffer; buffer << catalogFile.rdbuf(); parseCatalog(buffer.str()); } else Log::warning("Missing catalog.json"); std::string itemDumpPath = getExeDir() + "items.json"; std::ifstream itemFile(itemDumpPath); if (itemFile.is_open()) { try { std::string content((std::istreambuf_iterator(itemFile)), std::istreambuf_iterator()); json doc = json::parse(content); if (doc.contains("Camper") && doc["Camper"].is_object()) if (doc["Camper"].contains("Items") && doc["Camper"]["Items"].is_array()) for (const auto& item : doc["Camper"]["Items"]) if (item.is_string()) _camperItemIds.insert(item.get()); if (doc.contains("Slasher") && doc["Slasher"].is_object()) if (doc["Slasher"].contains("Powers") && doc["Slasher"]["Powers"].is_array()) for (const auto& item : doc["Slasher"]["Powers"]) if (item.is_string()) _slasherPowerIds.insert(item.get()); } catch (const json::parse_error& e) { Log::error("JSON parse error in {}: {}", "items.json", e.what()); } } else Log::warning("Missing items.json"); std::string offeringDumpPath = getExeDir() + "offerings.json"; std::ifstream offeringsFile(offeringDumpPath); if (offeringsFile.is_open()) { try { std::string content((std::istreambuf_iterator(offeringsFile)), std::istreambuf_iterator()); json doc = json::parse(content); if (doc.contains("Slashers") && doc["Slashers"].is_array()) for (const auto& offering : doc["Slashers"]) if (offering.is_string()) _slasherOfferingIds.insert(offering.get()); if (doc.contains("Campers") && doc["Campers"].is_array()) for (const auto& offering : doc["Campers"]) if (offering.is_string()) _camperOfferingIds.insert(offering.get()); } catch (const json::parse_error& e) { Log::error("JSON parse error in {}: {}", "offerings.json", e.what()); } } else Log::warning("Missing offerings.json"); std::string addonDumpPath = getExeDir() + "addons.json"; std::ifstream addonFile(addonDumpPath); if (addonFile.is_open()) { try { std::string content((std::istreambuf_iterator(addonFile)), std::istreambuf_iterator()); json doc = json::parse(content); if (doc.contains("Slashers") && doc["Slashers"].is_array()) for (const auto& item : doc["Slashers"]) if (item.is_string()) _slasherAddonIds.insert(item.get()); if (doc.contains("Campers") && doc["Campers"].is_array()) for (const auto& power : doc["Campers"]) if (power.is_string()) _camperAddonIds.insert(power.get()); } catch (const json::parse_error& e) { Log::error("JSON parse error in {}: {}", "addons.json", e.what()); } } else Log::warning("Missing addons.json"); try { std::string perkDumpPaths = getExeDir() + "perks.json"; std::ifstream perkFile(perkDumpPaths); if (perkFile.is_open()) { std::string content((std::istreambuf_iterator(perkFile)), std::istreambuf_iterator()); json doc = json::parse(content); if (doc.contains("Slashers") && doc["Slashers"].is_array()) for (const auto& item : doc["Slashers"]) if (item.is_string()) _slasherPerkIds.insert(item.get()); if (doc.contains("Campers") && doc["Campers"].is_array()) for (const auto& power : doc["Campers"]) if (power.is_string()) _camperPerkIds.insert(power.get()); } else Log::warning("Missing perks.json"); } catch (const json::parse_error& e) { Log::error("JSON parse error in {}: {}", "perks.json", e.what()); return; } Log::info("Loaded: Camper items={}, Slasher powers={}, " "Slasher addons={}, Camper addons={}, Camper perks={}, Slasher perks={}" "Slasher offerings={}, Camper offerings={}", _camperItemIds.size(), _slasherPowerIds.size(), _slasherAddonIds.size(), _camperAddonIds.size(), _camperPerkIds.size(), _slasherPerkIds.size(), _slasherOfferingIds.size(), _camperOfferingIds.size()); } void Spoofer::parseCatalog(std::string data) { try { json doc = json::parse(data); if (doc.contains("data")) doc = doc["data"]; if (doc.contains("item") && doc["item"].contains("items") && doc["item"]["items"].is_array()) for (const auto& item : doc["item"]["items"]) if (item.is_string()) _catalogItemIds.insert(item.get()); if (doc.contains("outfit") && doc["outfit"].contains("items") && doc["outfit"]["items"].is_array()) for (const auto& item : doc["outfit"]["items"]) if (item.is_string()) _catalogOutfitIds.insert(item.get()); Log::info("Parsed {} items and {} outfits from catalog", _catalogItemIds.size(), _catalogOutfitIds.size()); } catch (const json::parse_error& e) { Log::error("JSON parse error in {}: {}", "parseCatalog", e.what()); } } void Spoofer::parseAndDumpCatalog(std::string& data) { std::string path = getExeDir() + "catalog.json"; std::ofstream file(path); file << data; file.close(); Log::info("Raw catalog saved to {}", path); parseCatalog(data); } std::unordered_set killerNames = { "Chuckles", "Bob", "HillBilly", "Nurse", "Shape", "Witch", "Killer07", "Cannibal", "Bear", "Nightmare", "Pig", "Clown", "Spirit", "Plague", "Ghostface", "Demogorgon", "Oni", "Gunslinger"}; void Spoofer::modifyCharacterData(json& js) { std::unordered_set existingItemIds; bool isSlasher = false; if (js.contains("characterName") && js["characterName"].is_string()) { std::string name = js["characterName"]; if (killerNames.contains(name) || (name.length() == 3 && name[0] == 'K')) isSlasher = true; } if (js.contains("prestigeLevel") && js["prestigeLevel"].get() <= 1) if (js.contains("bloodWebLevel") && js["bloodWebLevel"].get() <= 15) js["bloodWebLevel"] = 16; if (js.contains("bloodWebData")) { static std::random_device rd; static std::mt19937 rng(rd()); std::unordered_set stackableIds; stackableIds.insert(_camperItemIds.begin(), _camperItemIds.end()); stackableIds.insert(_camperOfferingIds.begin(), _camperOfferingIds.end()); stackableIds.insert(_camperAddonIds.begin(), _camperAddonIds.end()); stackableIds.insert(_slasherAddonIds.begin(), _slasherAddonIds.end()); stackableIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end()); std::string selectedContentId = "Spring2024Offering"; auto it = stackableIds.begin(); std::advance(it, std::uniform_int_distribution<>(0, static_cast(_camperOfferingIds.size()) - 1)(rng)); selectedContentId = *it; std::vector paths; json ringDataArray = json::array(); ringDataArray.push_back({{"nodeData", json::array({{{"nodeId", "0"}, {"state", "Collected"}}})}}); int nodesPerRing[] = {6, 12, 12}; std::vector prevRingNodes = {"0"}; for (int ring = 1; ring <= 3; ++ring) { json nodeDataArray = json::array(); std::vector currentRingNodes; int numNodes = nodesPerRing[ring - 1]; for (int i = 1; i <= numNodes; ++i) { std::string childId = std::to_string((ring * 100) + i); currentRingNodes.push_back(childId); int parentIndex = (i - 1) / (numNodes / static_cast(prevRingNodes.size())); if (parentIndex >= prevRingNodes.size()) parentIndex = static_cast(prevRingNodes.size()) - 1; std::string parentId = prevRingNodes[parentIndex]; paths.push_back(parentId + "_" + childId); nodeDataArray.push_back( {{"nodeId", childId}, {"state", "Collected"}, {"contentId", selectedContentId}}); } ringDataArray.push_back({{"nodeData", nodeDataArray}}); prevRingNodes = std::move(currentRingNodes); } js["bloodWebData"]["paths"] = paths; js["bloodWebData"]["ringData"] = ringDataArray; js["bloodWebData"]["paths"] = paths; js["bloodWebData"]["ringData"] = ringDataArray; } if (js.contains("characterItems") && js["characterItems"].is_array()) { std::unordered_set stackableIds; stackableIds.insert(_camperItemIds.begin(), _camperItemIds.end()); stackableIds.insert(_camperOfferingIds.begin(), _camperOfferingIds.end()); stackableIds.insert(_camperAddonIds.begin(), _camperAddonIds.end()); stackableIds.insert(_slasherAddonIds.begin(), _slasherAddonIds.end()); stackableIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end()); for (auto& item : js["characterItems"]) { if (item.contains("itemId") && item["itemId"].is_string()) { std::string itemId = item["itemId"]; existingItemIds.insert(itemId); if (_slasherPowerIds.find(itemId) != _slasherPowerIds.end()) isSlasher = true; else if (stackableIds.contains(itemId)) item["quantity"] = 100; } } if (!isSlasher) { for (const std::string& itemId : _camperItemIds) if (existingItemIds.find(itemId) == existingItemIds.end()) js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}}); for (const std::string& itemId : _camperAddonIds) if (existingItemIds.find(itemId) == existingItemIds.end()) js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}}); for (const std::string& itemId : _camperOfferingIds) if (existingItemIds.find(itemId) == existingItemIds.end()) js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}}); for (const std::string& itemId : _camperPerkIds) if (existingItemIds.find(itemId) == existingItemIds.end()) js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 3}}); } else { for (const std::string& itemId : _slasherAddonIds) if (existingItemIds.find(itemId) == existingItemIds.end()) js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}}); for (const std::string& itemId : _slasherOfferingIds) if (existingItemIds.find(itemId) == existingItemIds.end()) js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}}); for (const std::string& itemId : _slasherPerkIds) if (existingItemIds.find(itemId) == existingItemIds.end()) js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 3}}); } } } void Spoofer::serverResponseHandler(const std::string& url, std::string& body, std::string& respHeaders) { #ifdef _DEBUG if (url.find("bhvrdbd.com") != std::string::npos) Log::verbose("BHVR api res @ {}", url); #endif if (url.find("api/v1/extensions/store/getCatalogItems") != std::string::npos) return parseAndDumpCatalog(body); if (url.find("api/v1/dbd-inventories/all") != std::string::npos) { try { json doc = json::parse(body); auto& itemsArr = doc["inventoryItems"]; std::unordered_set foundObjects; std::unordered_set foundAddons; std::unordered_set foundPerks; std::unordered_set foundOfferings; std::unordered_set foundCatalogItems; std::unordered_set offeringIds; offeringIds.insert(_camperOfferingIds.begin(), _camperOfferingIds.end()); offeringIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end()); std::unordered_set catalogIds; catalogIds.insert(_catalogOutfitIds.begin(), _catalogOutfitIds.end()); catalogIds.insert(_catalogItemIds.begin(), _catalogItemIds.end()); std::unordered_set perkIds; perkIds.insert(_slasherPerkIds.begin(), _slasherPerkIds.end()); perkIds.insert(_camperPerkIds.begin(), _camperPerkIds.end()); for (auto& item : itemsArr) { std::string objectId = item["objectId"]; if (perkIds.find(objectId) != perkIds.end()) { foundPerks.insert(objectId); item["quantity"] = 3; continue; } if (offeringIds.find(objectId) != offeringIds.end()) { foundOfferings.insert(objectId); item["quantity"] = 100; continue; } if (catalogIds.find(objectId) != catalogIds.end()) { foundCatalogItems.insert(objectId); continue; } } for (const std::string& id : perkIds) { if (foundPerks.find(id) == foundPerks.end()) { itemsArr.push_back({ {"objectId", id}, {"quantity", 3}, {"lastUpdateAt", std::time(nullptr)}, }); } } for (const std::string& id : offeringIds) { if (foundOfferings.find(id) == foundOfferings.end()) { itemsArr.push_back({ {"objectId", id}, {"quantity", 100}, {"lastUpdateAt", std::time(nullptr)}, }); } } for (const std::string& id : catalogIds) { if (foundCatalogItems.find(id) == foundCatalogItems.end()) { itemsArr.push_back({ {"objectId", id}, {"quantity", 1}, {"lastUpdateAt", std::time(nullptr)}, }); } } std::string updatedJson = doc.dump(); body = updatedJson; Log::verbose("Inventory updated: Items={}, Addons={}, Perks={}, Offerings={}", foundObjects.size(), foundAddons.size(), foundPerks.size(), foundOfferings.size()); return; } catch (const json::parse_error& e) { Log::error("JSON parse error in {}: {}", url, e.what()); return; } return; } if (url.find("api/v1/dbd-character-data/get-all") != std::string::npos) { try { json doc = json::parse(body); auto& charList = doc["list"]; for (auto& charInfo : charList) { if (charInfo.contains("bloodWebLevel") && charInfo.at("bloodWebLevel").get() <= 15) if (!charInfo.contains("prestigeLevel") || charInfo.at("prestigeLevel").get() <= 0) charInfo["bloodWebLevel"] = 16; if (charInfo.contains("bloodWebData") && charInfo.at("bloodWebData").contains("level")) charInfo["bloodWebData"]["level"] = 1; if (charInfo["isEntitled"] == false) { charInfo["isEntitled"] = true; if (charInfo.contains("origin")) { charInfo["purchaseInfo"] = {{"quantity", 1}, {"origin", "PlayerInventory"}, {"reason", "Item(s) added via Purchase"}, {"lastUpdateAt", 1770702482}, {"objectId", charInfo["characterName"]}}; } } modifyCharacterData(charInfo); } body = doc.dump(); } catch (const json::parse_error& e) { Log::error("JSON parse error in {}: {}", url, e.what()); } return; } if (url.find("api/v1/dbd-character-data/") != std::string::npos) { try { json doc = json::parse(body); if (body.find("NotAllowedException") != std::string::npos && body.find("not owned") != std::string::npos) { Log::info("Spoofing bloodweb error for unowned character"); json mock; mock["bloodWebLevelChanged"] = false; mock["updatedWallets"] = json::array(); mock["bloodWebLevel"] = 16; mock["prestigeLevel"] = 0; mock["bloodWebData"] = {{"ringData", json::array()}, {"paths", json::array()}}; mock["characterItems"] = json::array(); mock["characterName"] = this->_lastBloodWebChar; modifyCharacterData(mock); size_t firstSpace = respHeaders.find(' '); if (firstSpace != std::string::npos) respHeaders.replace(firstSpace + 1, 3, "200"); body = mock.dump(); return; } else { modifyCharacterData(doc); body = doc.dump(); } return; } catch (const json::parse_error& e) { Log::error("JSON parse error in {}: {}", url, e.what()); } return; } } void Spoofer::clientRequestHandler(std::string& url, const std::string& body, std::string& /*reqHeaders*/) { if (url.find("api/v1/dbd-character-data/bloodweb") != std::string::npos || url.find("api/v1/dbd-character-data/bulk-spending-bloodweb") != std::string::npos) { try { json req = json::parse(body); if (req.contains("characterName")) { this->_lastBloodWebChar = req["characterName"]; Log::info("Detected bloodweb request for character: {}", this->_lastBloodWebChar); } } catch (...) { } } }