This commit is contained in:
+22
-359
@@ -1,30 +1,22 @@
|
||||
#include "proxy.h"
|
||||
#include "spoofing.h"
|
||||
|
||||
#include <nerutils/log.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <wininet.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <format>
|
||||
#include <mutex>
|
||||
#include <ctime>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
#include <simdjson.h>
|
||||
//#include <fstream>
|
||||
//#include <iostream>
|
||||
//#include <string>
|
||||
//#include <string_view>
|
||||
//#include <vector>
|
||||
//#include <format>
|
||||
//#include <mutex>
|
||||
//#include <ctime>
|
||||
//#include <regex>
|
||||
//#include <unordered_set>
|
||||
//#include <simdjson.h>
|
||||
|
||||
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<std::string> g_allObjectIds;
|
||||
std::vector<std::string> g_allCharacterIds;
|
||||
std::vector<std::string> g_stackableItems;
|
||||
std::vector<std::string> g_uniqueItems;
|
||||
std::vector<std::string> g_perks;
|
||||
|
||||
void loadDictDump(const std::string& path, std::vector<std::string>& outStackable, std::vector<std::string>& 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<std::string>& 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<std::mutex> 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<std::string> newObjectIds;
|
||||
std::vector<std::string> 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<std::mutex> 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<char>(file)), std::istreambuf_iterator<char>());
|
||||
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<std::mutex> 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<std::string> 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<std::string> localStackable;
|
||||
std::vector<std::string> localUnique;
|
||||
std::vector<std::string> localPerks;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_dataMutex);
|
||||
localStackable = g_stackableItems;
|
||||
localUnique = g_uniqueItems;
|
||||
localPerks = g_perks;
|
||||
}
|
||||
|
||||
if (!localStackable.empty() || !localUnique.empty() || !localPerks.empty())
|
||||
{
|
||||
std::unordered_set<std::string> perkSet(localPerks.begin(), localPerks.end());
|
||||
std::unordered_set<std::string> 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<std::string> seenIds;
|
||||
std::unordered_set<std::string> 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<std::string>& 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
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
#include "spoofing.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <minwindef.h>
|
||||
|
||||
#include <nerutils/log.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
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<char>(itemFile)), std::istreambuf_iterator<char>());
|
||||
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<std::string>());
|
||||
|
||||
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<std::string>());
|
||||
}
|
||||
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<char>(offeringsFile)), std::istreambuf_iterator<char>());
|
||||
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<std::string>());
|
||||
|
||||
if (doc.contains("Campers") && doc["Campers"].is_array())
|
||||
for (const auto& offering : doc["Campers"])
|
||||
if (offering.is_string()) _camperOfferingIds.insert(offering.get<std::string>());
|
||||
}
|
||||
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<char>(addonFile)), std::istreambuf_iterator<char>());
|
||||
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<std::string>());
|
||||
|
||||
if (doc.contains("Campers") && doc["Campers"].is_array())
|
||||
for (const auto& power : doc["Campers"])
|
||||
if (power.is_string()) _camperAddonIds.insert(power.get<std::string>());
|
||||
}
|
||||
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<char>(perkFile)), std::istreambuf_iterator<char>());
|
||||
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<std::string>());
|
||||
|
||||
if (doc.contains("Campers") && doc["Campers"].is_array())
|
||||
for (const auto& power : doc["Campers"])
|
||||
if (power.is_string()) _camperPerkIds.insert(power.get<std::string>());
|
||||
}
|
||||
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<std::string>());
|
||||
|
||||
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<std::string>());
|
||||
|
||||
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<std::string> 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<std::string> 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<std::string> foundObjects;
|
||||
std::unordered_set<std::string> foundAddons;
|
||||
std::unordered_set<std::string> foundPerks;
|
||||
std::unordered_set<std::string> foundOfferings;
|
||||
std::unordered_set<std::string> foundCatalogItems;
|
||||
|
||||
//std::unordered_set<std::string> objectIds;
|
||||
//objectIds.insert(_camperItemIds.begin(), _camperItemIds.end());
|
||||
//objectIds.insert(_slasherPowerIds.begin(), _slasherPowerIds.end());
|
||||
|
||||
//std::unordered_set<std::string> addonIds;
|
||||
//addonIds.insert(_camperAddonIds.begin(), _camperAddonIds.end());
|
||||
//addonIds.insert(_slasherAddonIds.begin(), _slasherAddonIds.end());
|
||||
|
||||
std::unordered_set<std::string> offeringIds;
|
||||
offeringIds.insert(_camperOfferingIds.begin(), _camperOfferingIds.end());
|
||||
offeringIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end());
|
||||
|
||||
std::unordered_set<std::string> catalogIds;
|
||||
catalogIds.insert(_catalogOutfitIds.begin(), _catalogOutfitIds.end());
|
||||
catalogIds.insert(_catalogItemIds.begin(), _catalogItemIds.end());
|
||||
|
||||
std::unordered_set<std::string> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "proxy.h"
|
||||
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
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<std::string> _camperItemIds;
|
||||
std::unordered_set<std::string> _slasherPowerIds;
|
||||
|
||||
std::unordered_set<std::string> _camperOfferingIds;
|
||||
std::unordered_set<std::string> _slasherOfferingIds;
|
||||
|
||||
std::unordered_set<std::string> _camperAddonIds;
|
||||
std::unordered_set<std::string> _slasherAddonIds;
|
||||
|
||||
std::unordered_set<std::string> _slasherPerkIds;
|
||||
std::unordered_set<std::string> _camperPerkIds;
|
||||
|
||||
std::unordered_set<std::string> _catalogOutfitIds;
|
||||
std::unordered_set<std::string> _catalogItemIds;
|
||||
};
|
||||
Reference in New Issue
Block a user