#include "proxy.h" #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) { INTERNET_PER_CONN_OPTION_LIST list; INTERNET_PER_CONN_OPTION options[3]; unsigned long listSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); options[0].dwOption = INTERNET_PER_CONN_FLAGS; if (enable) options[0].Value.dwValue = PROXY_TYPE_PROXY | PROXY_TYPE_DIRECT; else options[0].Value.dwValue = PROXY_TYPE_DIRECT; options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER; options[1].Value.pszValue = const_cast(proxyAddr.c_str()); options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; options[2].Value.pszValue = const_cast(""); list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); list.pszConnection = NULL; list.dwOptionCount = 3; list.dwOptionError = 0; list.pOptions = options; if (!InternetSetOptionA(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, listSize)) { Log::error("Failed to set proxy options, Err: {}", GetLastError()); return false; } InternetSetOption(NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0); InternetSetOption(NULL, INTERNET_OPTION_REFRESH, NULL, 0); return true; } Proxy* g_proxy = nullptr; bool running = true; void cleanup() { static std::mutex cleanupMutex; std::lock_guard lock(cleanupMutex); static bool cleaned = false; if (cleaned) return; cleaned = true; if (g_proxy) { Log::info("Shutting down proxy"); g_proxy->Shutdown(); } Log::info("Restoring system proxy settings"); setProxy(false, ""); } BOOL WINAPI consoleHandler(DWORD dwType) { if (dwType == CTRL_C_EVENT || dwType == CTRL_CLOSE_EVENT || dwType == CTRL_LOGOFF_EVENT || dwType == CTRL_SHUTDOWN_EVENT) { running = false; cleanup(); exit(0); 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(); SetConsoleCtrlHandler(consoleHandler, TRUE); atexit(cleanup); Log::info("Init"); loadCatalogOnStartup(); loadAllCustomItems(); /* proxy setup */ Log::info("Starting proxy"); g_proxy = new Proxy(); if (!g_proxy->Init()) { Log::error("Proxy failed to start"); return 1; } setProxy(true, std::format("127.0.0.1:{}", PROXY_PORT)); /* listeners */ 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 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() || !g_stackableItems.empty() || !g_uniqueItems.empty()) { Log::info("Merging catalog and custom items into real inventory response"); std::regex qtyRegex(R"("quantity"\s*:\s*\d+)"); for (const auto& id : g_stackableItems) { size_t pos = data.find("\"objectId\":\"" + id + "\""); if (pos != std::string::npos) { size_t start = data.rfind("{", pos); size_t end = data.find("}", pos); if (start != std::string::npos && end != std::string::npos && start < end) { std::string objStr = data.substr(start, end - start + 1); objStr = std::regex_replace(objStr, qtyRegex, "\"quantity\":100"); data.replace(start, end - start + 1, objStr); } } } 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() + g_uniqueItems.size()) * 60); std::unordered_set dbIds; for (auto& id : g_stackableItems) dbIds.insert(id); for (auto& id : g_uniqueItems) dbIds.insert(id); for (auto& id : g_perks) dbIds.insert(id); std::unordered_set handledIds; auto injectItem = [&](const std::string& id, int qty) { if (id.empty()) return; if (handledIds.count(id)) return; std::string searchPat = "\"objectId\":\"" + id + "\""; if (data.find(searchPat) != std::string::npos) { handledIds.insert(id); return; } injected += ",{\"lastUpdateAt\":" + std::to_string(now) + ",\"quantity\":" + std::to_string(qty) + ",\"objectId\":\"" + id + "\"}"; handledIds.insert(id); }; 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); for (const auto& id : g_allObjectIds) { if (dbIds.find(id) == dbIds.end()) injectItem(id, 1); } if (!injected.empty()) data.insert(closePos, injected); Log::info("Injected {} new items into inventory", injected.empty() ? 0 : std::count(injected.begin(), injected.end(), '{')); } } else Log::warning("No catalog or custom data available to inject into inventory yet!"); } else if (url.find("api/v1/dbd-character-data/get-all") != 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()) { size_t pos = 0; size_t count = 0; std::unordered_set stackableSet(localStackable.begin(), localStackable.end()); std::unordered_set uniqueSet(localUnique.begin(), localUnique.end()); std::unordered_set perkSet(localPerks.begin(), localPerks.end()); while ((pos = data.find("\"characterItems\":[", pos)) != std::string::npos) { pos += 18; size_t endPos = data.find("]", pos); if (endPos != std::string::npos) { std::string currentItems = data.substr(pos, endPos - pos); std::string newItems; newItems.reserve(currentItems.size() * 2); std::unordered_set seenIds; size_t itemPos = 0; while ((itemPos = currentItems.find("\"itemId\":\"", itemPos)) != std::string::npos) { itemPos += 10; size_t quotePos = currentItems.find("\"", itemPos); if (quotePos == std::string::npos) break; std::string id = currentItems.substr(itemPos, quotePos - itemPos); seenIds.insert(id); int qty = 100; if (perkSet.count(id)) qty = 3; else if (uniqueSet.count(id)) qty = 1; newItems += "{\"itemId\":\"" + id + "\",\"quantity\":" + std::to_string(qty) + "},"; } auto injectIfMissing = [&](const std::vector& list, int qty) { for (const auto& id : list) { if (seenIds.find(id) == seenIds.end()) { newItems += "{\"itemId\":\"" + id + "\",\"quantity\":" + std::to_string(qty) + "},"; seenIds.insert(id); } } }; injectIfMissing(localStackable, 100); injectIfMissing(localUnique, 1); injectIfMissing(localPerks, 3); if (!newItems.empty()) newItems.pop_back(); // trailing comma data.replace(pos, endPos - pos, newItems); endPos = pos + newItems.length(); pos = endPos; count++; } } Log::info("Added missing items and targeted perk tiers in {} character inventories", count); } else Log::warning("No custom dumped items available to inject into character inventory!"); } }); /* pause */ Log::info("Proxy running (CTRL+C to stop)"); while (running) Sleep(100); /* cleanup */ cleanup(); return 0; }