Compare commits

..

8 Commits

Author SHA1 Message Date
neru 697bff9752 chore: make more logs dbg only
Build / build (push) Successful in 3m26s
2026-04-09 23:27:08 -03:00
neru 0fa2e0540b feat: add getCatalogItems listener
Build / build (push) Successful in 3m32s
2026-04-09 23:23:10 -03:00
neru a1a123054f feat: move dumping to separate thread 2026-04-09 23:16:12 -03:00
neru 76d581c419 feat: add message spoofing 2026-04-09 23:12:12 -03:00
neru 4f91ab9cff fix: prevent log spam on rel builds 2026-04-09 23:11:55 -03:00
neru e6111f8dbd feat: add message spoofing 2026-04-09 23:11:44 -03:00
neru 7ec6e385a0 feat: add runtime catalog dumping 2026-04-09 23:11:31 -03:00
neru 7427357bc5 fix: dangling else
Build / build (push) Successful in 3m12s
2026-04-09 06:45:58 -03:00
2 changed files with 99 additions and 9 deletions
+97 -9
View File
@@ -6,6 +6,7 @@
#include <random>
#include <vector>
#include <regex>
#include <thread>
#include <time.h>
@@ -109,10 +110,11 @@ bool Spoofer::parseCatalog(std::string data)
auto extractIds = [&](const std::string& key, std::unordered_set<std::string>& targetSet) {
if (catalogData.contains(key) && catalogData[key].contains("items"))
for (const auto& id : catalogData[key]["items"])
if (id.is_string())
targetSet.insert(id.get<std::string>());
else
Log::warning("Catalog missing or invalid category: {}", key);
{
if (id.is_string()) targetSet.insert(id.get<std::string>());
}
else
Log::warning("Catalog missing or invalid category: {}", key);
};
extractIds("item", _catalogItemIds);
@@ -142,10 +144,11 @@ bool Spoofer::parseStackable(std::string data, std::unordered_set<std::string>&
auto populate = [&](const std::string& key, std::unordered_set<std::string>& targetSet) {
if (doc.contains(key) && doc[key].is_array())
for (const auto& item : doc[key])
if (item.is_string())
targetSet.insert(item.get<std::string>());
else
Log::warning("Missing stackables array ({})", key);
{
if (item.is_string()) targetSet.insert(item.get<std::string>());
}
else
Log::warning("Missing stackables array ({})", key);
};
populate("Slashers", slasherSet);
@@ -271,7 +274,7 @@ void Spoofer::modifyCharacterData(json& js)
/*
ghost node hotfix (untested)
*/
/* if (js.contains("bloodWebData") && js["bloodWebData"].contains("ringData"))
/* if (js.contains("bloodWebData") && js["bloodWebData"].contains("ringData"))
{
auto& ringData = js["bloodWebData"]["ringData"];
@@ -345,12 +348,34 @@ void Spoofer::modifyCharacterData(json& js)
}
}
#ifdef _DEBUG
Log::verbose("Spoofed data for character {}", name);
#endif
}
/*
endpoint handlers
*/
void Spoofer::onGetCatalogItems(std::string& body)
{
std::thread t([body]() {
std::string outPath = utils::getExePath() + "catalog.json";
std::ofstream file(outPath);
if (file.is_open())
{
file << body;
file.close();
Log::verbose("Raw catalog saved to {}", outPath);
}
else
Log::error("Unable to write to catalog.json");
});
t.detach();
parseCatalog(body);
}
void Spoofer::onGetAll(std::string& body)
{
json doc = json::parse(body, nullptr, false);
@@ -424,6 +449,62 @@ void Spoofer::onInventoryAll(std::string& body)
Log::verbose("Inventory spoofed");
}
void Spoofer::onMessageList(std::string& body)
{
json doc = json::parse(body, nullptr, false);
if (doc.is_discarded()) return Log::error("JSON parse error for dbd-messages/listV2");
auto now = std::chrono::system_clock::now();
auto now_seconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
auto now_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
json msg;
/*
msg base
*/
msg["allowedPlatforms"] = json::array({"egs", "grdk", "ps4", "ps5", "steam", "xbox", "xsx"});
msg["flag"] = "READ";
msg["gameSpecificData"] = {};
msg["read"] = false;
msg["tag"] = json::array({"inbox"});
msg["expireAt"] = now_seconds + (1337 * 24 * 60 * 60);
msg["received"] = now_milliseconds;
msg["recipientId"] = "system";
/*
msg content
*/
json bodyContent;
bodyContent["sections"] = json::array();
bodyContent["sections"].push_back(
{{"type", "text"},
{"text", "Japan is turning footsteps into electricity!<br><br>Using piezoelectric tiles, every step "
"you take generates a small amount of energy. Millions of steps together can power LED "
"lights and displays in busy places like Shibuya Station.<br><br>A brilliant way to create a "
"sustainable and smart city — turning movement into clean, renewable energy."}});
bodyContent["image"] = {
{"packagedPath", "/Game/UI/UMGAssets/Icons/ItemAddons/iconAddon_powerBulb.iconAddon_powerBulb"},
{"contentVersion", "ccc3f02b0a671fe19a0017d6a69293876a465fd9"},
{"uri", ""}};
bodyContent["sections"].push_back(
{{"type", "itemshowcase"},
{"rewards", json::array({{{"type", "inventory"}, {"id", "ADDON_flashlight_oddbulb"}, {"amount", 1}}})}});
msg["message"] = {};
msg["message"]["title"] = "Japan is turning footsteps into electricity";
msg["message"]["body"] = bodyContent.dump();
doc["messages"].push_back(msg);
body = doc.dump();
#ifdef _DEBUG
Log::verbose("Spoofed message list");
#endif
}
void Spoofer::onBloodweb(std::string& body, std::string& respHeaders)
{
json doc = json::parse(body, nullptr, false);
@@ -455,13 +536,17 @@ void Spoofer::onBloodweb(std::string& body, std::string& respHeaders)
respHeaders = std::regex_replace(respHeaders, statusRegex, "HTTP/1.1 200");
body = mock.dump();
#ifdef _DEBUG
Log::verbose("Spoofed bloodweb request for unowned character.");
#endif
return;
}
modifyCharacterData(doc);
body = doc.dump();
#ifdef _DEBUG
Log::verbose("Spoofed bloodweb items for owned character");
#endif
}
/*
@@ -476,8 +561,11 @@ void Spoofer::serverResponseHandler(const std::string& url, std::string& body, s
Log::verbose("BHVR api res @ {}", url);
#endif
if (url.find("api/v1/extensions/store/getCatalogItems") != std::string::npos) return onGetCatalogItems(body);
if (url.find("api/v1/dbd-character-data/get-all") != std::string::npos) return onGetAll(body);
if (url.find("api/v1/dbd-character-data/get-all") != std::string::npos) return onGetAll(body);
if (url.find("api/v1/dbd-inventories/all") != std::string::npos) return onInventoryAll(body);
if (url.find("/api/v1/messages/listV2") != std::string::npos) return onMessageList(body);
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)
return onBloodweb(body, respHeaders);
+2
View File
@@ -28,8 +28,10 @@ class Spoofer
void generateBloodweb(nlohmann::json& data);
void modifyCharacterData(nlohmann::json& js);
void onGetCatalogItems(std::string& body);
void onGetAll(std::string& body);
void onInventoryAll(std::string& body);
void onMessageList(std::string& body);
void onBloodweb(std::string& body, std::string& respHeaders);
void serverResponseHandler(const std::string& url, std::string& body, std::string& respHeaders);