feat: parse new dumps, rewrite injection

This commit is contained in:
2026-03-20 17:30:39 -03:00
parent e8c914e58a
commit 9e6a4318c8
+171 -83
View File
@@ -12,6 +12,8 @@
#include <format> #include <format>
#include <mutex> #include <mutex>
#include <ctime> #include <ctime>
#include <regex>
#include <unordered_set>
#include <simdjson.h> #include <simdjson.h>
std::string getExeDir() std::string getExeDir()
@@ -20,8 +22,7 @@ std::string getExeDir()
GetModuleFileNameA(NULL, buffer, MAX_PATH); GetModuleFileNameA(NULL, buffer, MAX_PATH);
std::string path(buffer); std::string path(buffer);
size_t pos = path.find_last_of("\\/"); size_t pos = path.find_last_of("\\/");
if (pos != std::string::npos) if (pos != std::string::npos) return path.substr(0, pos + 1);
return path.substr(0, pos + 1);
return ""; return "";
} }
@@ -75,49 +76,78 @@ BOOL WINAPI consoleHandler(DWORD dwType)
std::mutex g_dataMutex; std::mutex g_dataMutex;
std::vector<std::string> g_allObjectIds; std::vector<std::string> g_allObjectIds;
std::vector<std::string> g_allCharacterIds; std::vector<std::string> g_allCharacterIds;
std::vector<std::string> g_dumpedItems; std::vector<std::string> g_stackableItems;
std::vector<std::string> g_uniqueItems;
std::vector<std::string> g_perks;
void loadJsonDump(const std::string& path) void loadDictDump(const std::string& path, std::vector<std::string>& outStackable, std::vector<std::string>& outUnique)
{ {
std::ifstream file(path); simdjson::dom::parser parser;
if (!file.is_open()) simdjson::dom::element doc;
auto error = parser.load(path).get(doc);
if (error)
{ {
Log::warning("Failed to open dump file: {}", path); Log::warning("Failed to open or parse dict dump {}: {}", path, simdjson::error_message(error));
return; return;
} }
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
try
{
simdjson::ondemand::parser parser;
simdjson::padded_string json(content);
auto doc = parser.iterate(json);
for (auto field : doc.get_object()) simdjson::dom::array itemsArr;
if (doc.at_key("Items").get(itemsArr) == simdjson::SUCCESS)
{
for (auto item : itemsArr)
{ {
for (auto item : field.value().get_array()) std::string_view id;
{ if (item.get(id) == simdjson::SUCCESS) outStackable.push_back(std::string(id));
std::string_view id;
if (item.get_string().get(id) == simdjson::SUCCESS)
{
g_dumpedItems.push_back(std::string(id));
}
}
} }
} }
catch (const std::exception& e)
simdjson::dom::array powersArr;
if (doc.at_key("Powers").get(powersArr) == simdjson::SUCCESS)
{ {
Log::warning("Failed to parse dump file {}: {}", path, e.what()); 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() void loadAllCustomItems()
{ {
std::lock_guard<std::mutex> lock(g_dataMutex); std::lock_guard<std::mutex> lock(g_dataMutex);
g_dumpedItems.clear(); g_stackableItems.clear();
loadJsonDump(getExeDir() + "itemdb.json"); g_uniqueItems.clear();
loadJsonDump(getExeDir() + "itemaddonsdb.json"); g_perks.clear();
loadJsonDump(getExeDir() + "offeringdb.json");
Log::info("Loaded {} custom items from dumps", g_dumpedItems.size()); 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) void parseCatalog(const std::string& data)
@@ -235,55 +265,77 @@ int main()
/* /*
listeners listeners
*/ */
proxy->OnClientRequest.addListener([](const std::string& url, std::string& data) {
if (url.find("/v1/party") != std::string::npos)
{
data;
Log::verbose("party req");
}
});
proxy->OnServerResponse.addListener([](const std::string& url, std::string& data) { proxy->OnServerResponse.addListener([](const std::string& url, std::string& data) {
bool dumpOnly = true; if (url.find("bhvrdbd.com") != std::string::npos) Log::verbose("BHVR api res: {}", url);
if (url.find("/v1/party") != std::string::npos)
{
data;
Log::verbose("party res");
}
Log::verbose("res: {}", url);
if (url.find("api/v1/extensions/store/getCatalogItems") != std::string::npos) if (url.find("api/v1/extensions/store/getCatalogItems") != std::string::npos)
updateCatalog(data); updateCatalog(data);
//else if (dumpOnly == true)
// return;
else if (url.find("api/v1/dbd-inventories/all") != std::string::npos) else if (url.find("api/v1/dbd-inventories/all") != std::string::npos)
{ {
std::vector<std::string> localObjectIds; std::lock_guard<std::mutex> lock(g_dataMutex);
if (!g_allObjectIds.empty() || !g_stackableItems.empty() || !g_uniqueItems.empty())
{ {
std::lock_guard<std::mutex> lock(g_dataMutex); Log::info("Merging catalog and custom items into real inventory response");
localObjectIds = g_allObjectIds;
}
if (!localObjectIds.empty()) std::regex qtyRegex(R"("quantity"\s*:\s*\d+)");
{ for (const auto& id : g_stackableItems)
Log::info("Merging {} catalog items into real inventory response", localObjectIds.size()); {
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("]}"); size_t closePos = data.rfind("]}");
if (closePos != std::string::npos) if (closePos != std::string::npos)
{ {
uint64_t now = time(nullptr); uint64_t now = time(nullptr);
std::string injected; std::string injected;
injected.reserve(localObjectIds.size() * 60); injected.reserve((g_allObjectIds.size() + g_stackableItems.size() + g_uniqueItems.size()) * 60);
for (const auto& id : localObjectIds) std::unordered_set<std::string> 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<std::string> 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 (data.find("\"objectId\":\"" + id + "\"") != std::string::npos) continue; if (dbIds.find(id) == dbIds.end()) injectItem(id, 1);
injected += ",{\"lastUpdateAt\":" + std::to_string(now) + ",\"quantity\":1,\"objectId\":\"" +
id + "\"}";
} }
if (!injected.empty()) data.insert(closePos, injected); if (!injected.empty()) data.insert(closePos, injected);
@@ -293,53 +345,89 @@ int main()
} }
} }
else else
Log::warning("No catalog data available to inject into inventory yet!"); 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) else if (url.find("api/v1/dbd-character-data/get-all") != std::string::npos)
{ {
std::vector<std::string> localItems;
std::vector<std::string> localStackable;
std::vector<std::string> localUnique;
std::vector<std::string> localPerks;
{ {
std::lock_guard<std::mutex> lock(g_dataMutex); std::lock_guard<std::mutex> lock(g_dataMutex);
localItems = g_dumpedItems; localStackable = g_stackableItems;
localUnique = g_uniqueItems;
localPerks = g_perks;
} }
if (!localItems.empty()) if (!localStackable.empty() || !localUnique.empty() || !localPerks.empty())
{ {
size_t pos = 0; size_t pos = 0;
size_t count = 0; size_t count = 0;
std::unordered_set<std::string> stackableSet(localStackable.begin(), localStackable.end());
std::unordered_set<std::string> uniqueSet(localUnique.begin(), localUnique.end());
std::unordered_set<std::string> perkSet(localPerks.begin(), localPerks.end());
while ((pos = data.find("\"characterItems\":[", pos)) != std::string::npos) while ((pos = data.find("\"characterItems\":[", pos)) != std::string::npos)
{ {
pos += 18; pos += 18;
size_t endPos = data.find("]", pos); size_t endPos = data.find("]", pos);
std::string currentItems; if (endPos != std::string::npos)
if (endPos != std::string::npos) currentItems = data.substr(pos, endPos - pos);
std::string injected;
for (const auto& item : localItems)
{ {
if (endPos == std::string::npos || std::string currentItems = data.substr(pos, endPos - pos);
currentItems.find("\"itemId\":\"" + item + "\"") == std::string::npos) std::string newItems;
newItems.reserve(currentItems.size() * 2);
std::unordered_set<std::string> seenIds;
size_t itemPos = 0;
while ((itemPos = currentItems.find("\"itemId\":\"", itemPos)) != std::string::npos)
{ {
injected += "{\"itemId\":\"" + item + "\",\"quantity\":100},"; 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) + "},";
} }
}
if (!injected.empty()) auto injectIfMissing = [&](const std::vector<std::string>& list, int qty) {
{ for (const auto& id : list)
if (data[pos] == ']') injected.pop_back(); {
if (seenIds.find(id) == seenIds.end())
{
newItems += "{\"itemId\":\"" + id + "\",\"quantity\":" + std::to_string(qty) + "},";
seenIds.insert(id);
}
}
};
data.insert(pos, injected); injectIfMissing(localStackable, 100);
pos += injected.length(); 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++; count++;
} }
} }
Log::info("Injected missing items into {} character inventories", count); Log::info("Injected missing items and targeted perk tiers in {} character inventories", count);
} }
else else
{
Log::warning("No custom dumped items available to inject into character inventory!"); Log::warning("No custom dumped items available to inject into character inventory!");
}
} }
}); });