From 698ad23963391c72f94db4bc36ec82efa2ed62cf Mon Sep 17 00:00:00 2001 From: neru Date: Sat, 21 Mar 2026 23:31:59 -0300 Subject: [PATCH] feat: completely rewrite unlocker logic --- src/unlocker/main.cpp | 381 ++----------------------------- src/unlocker/spoofing.cpp | 458 ++++++++++++++++++++++++++++++++++++++ src/unlocker/spoofing.h | 40 ++++ 3 files changed, 520 insertions(+), 359 deletions(-) create mode 100644 src/unlocker/spoofing.cpp create mode 100644 src/unlocker/spoofing.h diff --git a/src/unlocker/main.cpp b/src/unlocker/main.cpp index 522832a..a6e51a1 100644 --- a/src/unlocker/main.cpp +++ b/src/unlocker/main.cpp @@ -1,30 +1,22 @@ #include "proxy.h" +#include "spoofing.h" #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include -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 ""; -} bool setProxy(bool enable, const std::string& proxyAddr) { @@ -62,8 +54,8 @@ bool setProxy(bool enable, const std::string& proxyAddr) return true; } -Proxy* g_proxy = nullptr; bool running = true; +Proxy* g_Proxy = nullptr; void cleanup() { @@ -73,10 +65,10 @@ void cleanup() if (cleaned) return; cleaned = true; - if (g_proxy) + if (g_Proxy) { Log::info("Shutting down proxy"); - g_proxy->Shutdown(); + g_Proxy->Shutdown(); } Log::info("Restoring system proxy settings"); @@ -91,178 +83,11 @@ BOOL WINAPI consoleHandler(DWORD dwType) running = false; cleanup(); exit(0); - return TRUE; + //return TRUE; } return FALSE; } -std::mutex g_dataMutex; -std::vector g_allObjectIds; -std::vector g_allCharacterIds; -std::vector g_stackableItems; -std::vector g_uniqueItems; -std::vector g_perks; - -void loadDictDump(const std::string& path, std::vector& outStackable, std::vector& outUnique) -{ - simdjson::dom::parser parser; - simdjson::dom::element doc; - auto error = parser.load(path).get(doc); - if (error) - { - Log::warning("Failed to open or parse dict dump {}: {}", path, simdjson::error_message(error)); - return; - } - - simdjson::dom::array itemsArr; - if (doc.at_key("Items").get(itemsArr) == simdjson::SUCCESS) - { - for (auto item : itemsArr) - { - std::string_view id; - if (item.get(id) == simdjson::SUCCESS) outStackable.push_back(std::string(id)); - } - } - - simdjson::dom::array powersArr; - if (doc.at_key("Powers").get(powersArr) == simdjson::SUCCESS) - { - for (auto item : powersArr) - { - std::string_view id; - if (item.get(id) == simdjson::SUCCESS) outUnique.push_back(std::string(id)); - } - } -} - -void loadArrayDump(const std::string& path, std::vector& outList) -{ - simdjson::dom::parser parser; - simdjson::dom::element doc; - auto error = parser.load(path).get(doc); - if (error) - { - Log::warning("Failed to open or parse array dump {}: {}", path, simdjson::error_message(error)); - return; - } - - simdjson::dom::array arr; - if (doc.get(arr) == simdjson::SUCCESS) - { - for (auto item : arr) - { - std::string_view id; - if (item.get(id) == simdjson::SUCCESS) outList.push_back(std::string(id)); - } - } -} - -void loadAllCustomItems() -{ - std::lock_guard lock(g_dataMutex); - g_stackableItems.clear(); - g_uniqueItems.clear(); - g_perks.clear(); - - loadDictDump(getExeDir() + "addons.json", g_stackableItems, g_stackableItems); - loadDictDump(getExeDir() + "items.json", g_stackableItems, g_uniqueItems); - loadArrayDump(getExeDir() + "offerings.json", g_stackableItems); - loadArrayDump(getExeDir() + "perks.json", g_perks); - - Log::info("Loaded {} stackable, {} unique (powers), and {} perk items from dumps", g_stackableItems.size(), - g_uniqueItems.size(), g_perks.size()); -} - -void parseCatalog(const std::string& data) -{ - try - { - simdjson::ondemand::parser parser; - simdjson::padded_string json(data); - auto doc = parser.iterate(json); - - std::vector newObjectIds; - std::vector newCharacterIds; - - auto items_data = doc["data"]; - - for (auto item : items_data["character"]["items"]) - { - std::string_view id; - if (item.get_string().get(id) == simdjson::SUCCESS) - { - newCharacterIds.push_back(std::string(id)); - newObjectIds.push_back(std::string(id)); - } - } - - for (auto item : items_data["shrine"]["items"]) - { - std::string_view id; - if (item.get_string().get(id) == simdjson::SUCCESS) - { - newObjectIds.push_back(std::string(id)); - } - } - - for (auto item : items_data["item"]["items"]) - { - std::string_view id; - if (item.get_string().get(id) == simdjson::SUCCESS) - { - if (id.find("cell") == std::string_view::npos && id.find("Pack") == std::string_view::npos) - { - newObjectIds.push_back(std::string(id)); - } - } - } - - for (auto item : items_data["outfit"]["items"]) - { - std::string_view id; - if (item.get_string().get(id) == simdjson::SUCCESS) - { - newObjectIds.push_back(std::string(id)); - } - } - - std::lock_guard lock(g_dataMutex); - g_allObjectIds = std::move(newObjectIds); - g_allCharacterIds = std::move(newCharacterIds); - - Log::info("Catalog parsed: {} items, {} characters", g_allObjectIds.size(), g_allCharacterIds.size()); - } - catch (const std::exception& e) - { - Log::error("Failed to parse catalog: {}", e.what()); - } -} - -void updateCatalog(const std::string& data) -{ - std::string path = getExeDir() + "catalog_dump.json"; - std::ofstream file(path); - file << data; - file.close(); - Log::info("Raw catalog saved to {}", path); - - parseCatalog(data); -} - -void loadCatalogOnStartup() -{ - std::string path = getExeDir() + "catalog_dump.json"; - std::ifstream file(path); - if (file.is_open()) - { - Log::info("Found catalog_dump.json - re-parsing raw dump with current filters..."); - std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - parseCatalog(content); - } - else - Log::warning("No catalog_dump.json found. Start the game and visit the store once to initialise the unlocker."); -} - int main() { Log::createConsole(); @@ -271,15 +96,12 @@ int main() Log::info("Init"); - loadCatalogOnStartup(); - loadAllCustomItems(); - /* proxy setup */ Log::info("Starting proxy"); - g_proxy = new Proxy(); - if (!g_proxy->Init()) + g_Proxy = new Proxy(); + if (!g_Proxy->Init()) { Log::error("Proxy failed to start"); return 1; @@ -287,171 +109,12 @@ int main() setProxy(true, std::format("127.0.0.1:{}", PROXY_PORT)); /* - listeners + Spoofer setup */ - g_proxy->OnServerResponse.addListener([](const std::string& url, std::string& data) { -#ifdef _DEBUG - if (url.find("bhvrdbd.com") != std::string::npos) Log::verbose("BHVR api res: {}", url); -#endif + Log::info("Spoofer init"); + Spoofer* spoofer = new Spoofer(); - if (url.find("api/v1/extensions/store/getCatalogItems") != std::string::npos) - updateCatalog(data); - else if (url.find("api/v1/dbd-inventories/all") != std::string::npos) - { - std::lock_guard lock(g_dataMutex); - if (!g_allObjectIds.empty()) - { - Log::info("Merging catalog and dumped items into real inventory response"); - - size_t closePos = data.rfind("]}"); - if (closePos != std::string::npos) - { - uint64_t now = time(nullptr); - std::string injected; - injected.reserve((g_allObjectIds.size() + g_stackableItems.size()) * 80); - - std::unordered_set seenIds; - size_t pos = 0; - while ((pos = data.find("\"objectId\":\"", pos)) != std::string::npos) - { - pos += 12; - size_t end = data.find("\"", pos); - if (end != std::string::npos) - { - seenIds.insert(data.substr(pos, end - pos)); - pos = end; - } - } - - auto injectItem = [&](const std::string& id, int qty) { - if (id.empty() || seenIds.count(id)) return; - injected += std::format(",{{\"lastUpdateAt\":{},\"quantity\":{},\"objectId\":\"{}\"}}", now, qty, id); - seenIds.insert(id); - }; - - for (const auto& id : g_allObjectIds) injectItem(id, 1); - for (const auto& id : g_stackableItems) injectItem(id, 100); - for (const auto& id : g_uniqueItems) injectItem(id, 1); - for (const auto& id : g_perks) injectItem(id, 3); - - if (!injected.empty()) data.insert(closePos, injected); - - Log::info("Injected {} items into global inventory", - std::count(injected.begin(), injected.end(), '{')); - } - } - else - Log::warning("No catalog data available to inject into global inventory yet!"); - } - else if (url.find("api/v1/dbd-character-data/") != std::string::npos) - { - std::vector localStackable; - std::vector localUnique; - std::vector localPerks; - { - std::lock_guard lock(g_dataMutex); - localStackable = g_stackableItems; - localUnique = g_uniqueItems; - localPerks = g_perks; - } - - if (!localStackable.empty() || !localUnique.empty() || !localPerks.empty()) - { - std::unordered_set perkSet(localPerks.begin(), localPerks.end()); - std::unordered_set uniqueSet(localUnique.begin(), localUnique.end()); - std::regex qtyRegex(R"("quantity"\s*:\s*\d+)"); - - const char* targetArrays[] = {"\"characterItems\":[", "\"inventory\":[", "\"bloodwebRewards\":[", - "\"prestigeRewards\":["}; - bool isBloodweb = url.find("/bloodweb") != std::string::npos; - size_t arraysModified = 0; - - for (const char* arrayKey : targetArrays) - { - size_t pos = 0; - while ((pos = data.find(arrayKey, pos)) != std::string::npos) - { - pos += strlen(arrayKey); - size_t endPos = data.find("]", pos); - if (endPos == std::string::npos) break; - - std::string currentItems = data.substr(pos, endPos - pos); - std::unordered_set seenIds; - std::unordered_set stackableSet(localStackable.begin(), localStackable.end()); - - size_t itemPos = 0; - while ((itemPos = currentItems.find("\"itemId\":\"", itemPos)) != std::string::npos) - { - size_t idStart = itemPos + 10; - size_t idEnd = currentItems.find("\"", idStart); - if (idEnd == std::string::npos) break; - - std::string id = currentItems.substr(idStart, idEnd - idStart); - seenIds.insert(id); - - int qty = -1; - if (perkSet.count(id)) - qty = 3; - else if (uniqueSet.count(id)) - qty = 1; - else if (stackableSet.count(id)) - qty = 100; - - if (qty != -1) - { - size_t objStart = currentItems.rfind("{", itemPos); - size_t objEnd = currentItems.find("}", itemPos); - if (objStart != std::string::npos && objEnd != std::string::npos && objStart < objEnd) - { - std::string objStr = currentItems.substr(objStart, objEnd - objStart + 1); - if (std::regex_search(objStr, qtyRegex)) - objStr = - std::regex_replace(objStr, qtyRegex, "\"quantity\":" + std::to_string(qty)); - else - objStr.insert(objStr.length() - 1, ",\"quantity\":" + std::to_string(qty)); - - currentItems.replace(objStart, objEnd - objStart + 1, objStr); - itemPos = objStart + objStr.length(); - } - else - itemPos = idEnd; - } - else - itemPos = idEnd; - } - - auto injectIfMissing = [&](const std::vector& list, int qty) { - for (const auto& id : list) - { - if (seenIds.find(id) == seenIds.end()) - { - if (!currentItems.empty() && currentItems.back() != ',') currentItems += ","; - currentItems += - "{\"itemId\":\"" + id + "\",\"quantity\":" + std::to_string(qty) + "}"; - seenIds.insert(id); - } - } - }; - - if (!isBloodweb && (std::string_view(arrayKey) == "\"characterItems\":[" || - std::string_view(arrayKey) == "\"inventory\":[")) - { - injectIfMissing(localStackable, 100); - injectIfMissing(localUnique, 1); - injectIfMissing(localPerks, 3); - } - - data.replace(pos, endPos - pos, currentItems); - pos += currentItems.length(); - arraysModified++; - } - } - Log::info("Spoofed items in {} character data arrays across response", arraysModified); - } - else - Log::warning("No custom dumped items available to inject into character data!"); - } - }); + spoofer->init(g_Proxy); /* pause diff --git a/src/unlocker/spoofing.cpp b/src/unlocker/spoofing.cpp new file mode 100644 index 0000000..afc3553 --- /dev/null +++ b/src/unlocker/spoofing.cpp @@ -0,0 +1,458 @@ +#include "spoofing.h" + +#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); + }); +} + +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); +} + +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 (name == "Bear" || name == "Bob" || name == "Chuckles" || (name.length() >= 2 && name[0] == 'K')) + isSlasher = true; + } + + 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()); + + if (js.contains("characterItems") && js["characterItems"].is_array()) + { + 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*/) +{ + if (url.find("bhvrdbd.com") != std::string::npos) Log::verbose("BHVR api res @ {}", url); + + 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 objectIds; + //objectIds.insert(_camperItemIds.begin(), _camperItemIds.end()); + //objectIds.insert(_slasherPowerIds.begin(), _slasherPowerIds.end()); + + //std::unordered_set addonIds; + //addonIds.insert(_camperAddonIds.begin(), _camperAddonIds.end()); + //addonIds.insert(_slasherAddonIds.begin(), _slasherAddonIds.end()); + + 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 (objectIds.find(objectId) != objectIds.end()) + { + foundObjects.insert(objectId); + item["quantity"] = 100; + continue; + } + + if (addonIds.find(objectId) != addonIds.end()) + { + foundAddons.insert(objectId); + item["quantity"] = 100; + continue; + }*/ + + 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 : objectIds) + { + if (foundObjects.find(id) == foundObjects.end()) + { + itemsArr.push_back({ + {"objectId", id}, + {"quantity", 100}, + {"lastUpdateAt", std::time(nullptr)}, + }); + } + } + + for (const std::string& id : addonIds) + { + if (foundAddons.find(id) == foundAddons.end()) + { + itemsArr.push_back({ + {"objectId", id}, + {"quantity", 100}, + {"lastUpdateAt", std::time(nullptr)}, + }); + } + }*/ + + 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) + { + charInfo["isEntitled"] = true; + if (charInfo.contains("bloodWebLevel") && charInfo["bloodWebLevel"] <= 1) + { + charInfo["bloodWebLevel"] = 15; + if (charInfo["bloodWebData"].contains("level")) charInfo["bloodWebData"]["level"] = 1; + } + + 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); + modifyCharacterData(doc); + body = doc.dump(); + } + catch (const json::parse_error& e) + { + Log::error("JSON parse error in {}: {}", url, e.what()); + } + return; + } +} \ No newline at end of file diff --git a/src/unlocker/spoofing.h b/src/unlocker/spoofing.h new file mode 100644 index 0000000..b2117e9 --- /dev/null +++ b/src/unlocker/spoofing.h @@ -0,0 +1,40 @@ +#pragma once + +#include "proxy.h" + +#include +#include + +#include + +class Spoofer +{ + public: + void init(Proxy* proxy); + + private: + void registerListeners(Proxy* proxy); + void loadData(); + + void parseCatalog(std::string data); + + void parseAndDumpCatalog(std::string& data); + void modifyCharacterData(nlohmann::json& js); + + void serverResponseHandler(const std::string& url, std::string& body, std::string& respHeaders); + + std::unordered_set _camperItemIds; + std::unordered_set _slasherPowerIds; + + std::unordered_set _camperOfferingIds; + std::unordered_set _slasherOfferingIds; + + std::unordered_set _camperAddonIds; + std::unordered_set _slasherAddonIds; + + std::unordered_set _slasherPerkIds; + std::unordered_set _camperPerkIds; + + std::unordered_set _catalogOutfitIds; + std::unordered_set _catalogItemIds; +};