feat: add main with unlocker / dumper
This commit is contained in:
@@ -80,6 +80,15 @@ set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT d
|
||||
|
||||
group_files("${UNLOCKER_SOURCES}")
|
||||
|
||||
# copy resources
|
||||
file(GLOB JSON_RES_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/res/*.json")
|
||||
add_custom_command(TARGET dbd-unlocker POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${JSON_RES_FILES}
|
||||
"$<TARGET_FILE_DIR:dbd-unlocker>/"
|
||||
COMMENT "Copying JSON resources to executable directory"
|
||||
)
|
||||
|
||||
# ------------------------------
|
||||
# dumper
|
||||
# ------------------------------
|
||||
|
||||
+280
-1
@@ -4,6 +4,26 @@
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wininet.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <format>
|
||||
#include <mutex>
|
||||
#include <ctime>
|
||||
#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)
|
||||
{
|
||||
@@ -52,6 +72,144 @@ BOOL WINAPI consoleHandler(DWORD dwType)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::mutex g_dataMutex;
|
||||
std::vector<std::string> g_allObjectIds;
|
||||
std::vector<std::string> g_allCharacterIds;
|
||||
std::vector<std::string> g_dumpedItems;
|
||||
|
||||
void loadJsonDump(const std::string& path)
|
||||
{
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open())
|
||||
{
|
||||
Log::warning("Failed to open dump file: {}", path);
|
||||
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())
|
||||
{
|
||||
for (auto item : field.value().get_array())
|
||||
{
|
||||
std::string_view id;
|
||||
if (item.get_string().get(id) == simdjson::SUCCESS)
|
||||
{
|
||||
g_dumpedItems.push_back(std::string(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log::warning("Failed to parse dump file {}: {}", path, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void loadAllCustomItems()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_dataMutex);
|
||||
g_dumpedItems.clear();
|
||||
loadJsonDump(getExeDir() + "itemdb.json");
|
||||
loadJsonDump(getExeDir() + "itemaddonsdb.json");
|
||||
loadJsonDump(getExeDir() + "offeringdb.json");
|
||||
Log::info("Loaded {} custom items from dumps", g_dumpedItems.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();
|
||||
@@ -59,7 +217,12 @@ int main()
|
||||
|
||||
Log::info("Unlocker init");
|
||||
|
||||
loadCatalogOnStartup();
|
||||
loadAllCustomItems();
|
||||
|
||||
/*
|
||||
proxy setup
|
||||
*/
|
||||
Log::verbose("Starting proxy");
|
||||
Proxy* proxy = new Proxy();
|
||||
if (!proxy->Init())
|
||||
@@ -67,13 +230,129 @@ int main()
|
||||
Log::error("Proxy failed to start");
|
||||
return 1;
|
||||
}
|
||||
|
||||
setProxy(true, std::format("127.0.0.1:{}", PROXY_PORT));
|
||||
|
||||
/*
|
||||
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) {
|
||||
bool dumpOnly = true;
|
||||
|
||||
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)
|
||||
updateCatalog(data);
|
||||
//else if (dumpOnly == true)
|
||||
// return;
|
||||
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);
|
||||
localObjectIds = g_allObjectIds;
|
||||
}
|
||||
|
||||
if (!localObjectIds.empty())
|
||||
{
|
||||
Log::info("Merging {} catalog items into real inventory response", localObjectIds.size());
|
||||
|
||||
size_t closePos = data.rfind("]}");
|
||||
if (closePos != std::string::npos)
|
||||
{
|
||||
uint64_t now = time(nullptr);
|
||||
std::string injected;
|
||||
injected.reserve(localObjectIds.size() * 60);
|
||||
|
||||
for (const auto& id : localObjectIds)
|
||||
{
|
||||
if (data.find("\"objectId\":\"" + id + "\"") != std::string::npos) continue;
|
||||
|
||||
injected += ",{\"lastUpdateAt\":" + std::to_string(now) + ",\"quantity\":1,\"objectId\":\"" +
|
||||
id + "\"}";
|
||||
}
|
||||
|
||||
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 data available to inject into inventory yet!");
|
||||
}
|
||||
else if (url.find("api/v1/dbd-character-data/get-all") != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> localItems;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_dataMutex);
|
||||
localItems = g_dumpedItems;
|
||||
}
|
||||
|
||||
if (!localItems.empty())
|
||||
{
|
||||
size_t pos = 0;
|
||||
size_t count = 0;
|
||||
while ((pos = data.find("\"characterItems\":[", pos)) != std::string::npos)
|
||||
{
|
||||
pos += 18;
|
||||
|
||||
size_t endPos = data.find("]", pos);
|
||||
std::string currentItems;
|
||||
if (endPos != std::string::npos) currentItems = data.substr(pos, endPos - pos);
|
||||
|
||||
std::string injected;
|
||||
for (const auto& item : localItems)
|
||||
{
|
||||
if (endPos == std::string::npos ||
|
||||
currentItems.find("\"itemId\":\"" + item + "\"") == std::string::npos)
|
||||
{
|
||||
injected += "{\"itemId\":\"" + item + "\",\"quantity\":100},";
|
||||
}
|
||||
}
|
||||
|
||||
if (!injected.empty())
|
||||
{
|
||||
if (data[pos] == ']') injected.pop_back();
|
||||
|
||||
data.insert(pos, injected);
|
||||
pos += injected.length();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
Log::info("Injected missing items into {} character inventories", count);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::warning("No custom dumped items available to inject into character inventory!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
pause
|
||||
*/
|
||||
Log::verbose("Proxy running (CTRL+C to stop)");
|
||||
while (running)
|
||||
Sleep(100);
|
||||
|
||||
/*
|
||||
cleanup
|
||||
*/
|
||||
Log::verbose("Shutting down proxy");
|
||||
proxy->Shutdown();
|
||||
delete proxy;
|
||||
|
||||
Reference in New Issue
Block a user