From 0300ced79ca43db980730a036c619a68069475e3 Mon Sep 17 00:00:00 2001 From: neru Date: Sat, 11 Apr 2026 10:41:45 -0300 Subject: [PATCH] feat: add config --- src/unlocker/spoofing.cpp | 228 ++++++++++++++++++++++++-------------- src/unlocker/spoofing.h | 10 ++ 2 files changed, 156 insertions(+), 82 deletions(-) diff --git a/src/unlocker/spoofing.cpp b/src/unlocker/spoofing.cpp index 9bda1e2..58fd450 100644 --- a/src/unlocker/spoofing.cpp +++ b/src/unlocker/spoofing.cpp @@ -39,6 +39,7 @@ void Spoofer::init(Proxy* proxy) { registerListeners(proxy); loadData(); + loadConfig(); } void Spoofer::registerListeners(Proxy* proxy) @@ -90,6 +91,37 @@ void Spoofer::loadData() LOADDATA("perks.json", Perks, _camperPerkIds, _slasherPerkIds, "Perks won't be added"); } +void Spoofer::loadConfig() +{ + std::string configPath = utils::getExePath() + "config.json"; + std::ifstream configFile(configPath); + + if (configFile.is_open()) + { + try + { + json configJson = json::parse(configFile); + _config.spoofCharacterOwnership = configJson.value("spoofCharacterOwnership", false); + _config.spoofInventory = configJson.value("spoofInventory", true); + _config.spoofCustomization = configJson.value("spoofCustomization", true); + Log::info("Loaded config: Ownership={}, Inventory={}, Customization={}", _config.spoofCharacterOwnership, + _config.spoofInventory, _config.spoofCustomization); + } + catch (...) + { + Log::error("Failed to parse config.json, using defaults"); + } + } + else + { + Log::info("config.json not found, using default settings"); + json defaultConfig = { + {"spoofCharacterOwnership", true}, {"spoofInventory", true}, {"spoofCustomization", true}}; + std::ofstream out(configPath); + out << defaultConfig.dump(4); + } +} + /* data parsing */ @@ -241,81 +273,94 @@ void Spoofer::modifyCharacterData(json& js) } std::string name = js["characterName"]; - bool slasher = isSlasher(js["characterName"]); - bool needsSpoofing = false; - if (js.value("isEntitled", true) == false) + if (_config.spoofCharacterOwnership) { - _unownedCharacters.insert(name); - js["isEntitled"] = true; - js["purchaseInfo"] = {{"quantity", 1}, - {"origin", "PlayerInventory"}, - {"reason", "Item(s) added via Purchase"}, - {"lastUpdateAt", std::time(nullptr)}, - {"objectId", name}}; - needsSpoofing = true; + bool needsSpoofing = false; + + if (js.value("isEntitled", false) == false) + { + _unownedCharacters.insert(name); + js["isEntitled"] = true; + js["purchaseInfo"] = {{"quantity", 1}, + {"origin", "PlayerInventory"}, + {"reason", "Item(s) added via Purchase"}, + {"lastUpdateAt", std::time(nullptr)}, + {"objectId", name}}; + needsSpoofing = true; + } + else if (_unownedCharacters.contains(name)) + needsSpoofing = true; + + /* + modifications for unowned characters (spoof level and fake bloodweb) + */ + if (needsSpoofing) + { + if (js.contains("bloodWebLevel") && js["bloodWebLevel"].is_number() && js["bloodWebLevel"] <= 15) + if (!js.contains("prestigeLevel") || (js["prestigeLevel"].is_number() && js["prestigeLevel"] <= 0)) + js["bloodWebLevel"] = 16; + + if (js.contains("bloodWebData")) generateBloodweb(js["bloodWebData"]); + } } - else if (_unownedCharacters.contains(name)) - needsSpoofing = true; + else - /* - modifications for unowned characters (spoof level and fake bloodweb) - */ - if (needsSpoofing) { - if (js.contains("bloodWebLevel") && js["bloodWebLevel"].is_number() && js["bloodWebLevel"] <= 15) - if (!js.contains("prestigeLevel") || (js["prestigeLevel"].is_number() && js["prestigeLevel"] <= 0)) - js["bloodWebLevel"] = 16; - - if (js.contains("bloodWebData")) generateBloodweb(js["bloodWebData"]); + if (js.value("isEntitled", false) == false) return; } /* item spoofing */ - if (js.contains("characterItems") && js["characterItems"].is_array()) + if (_config.spoofInventory) { - std::unordered_set existingItemIds; + bool slasher = isSlasher(js["characterName"]); - std::unordered_set 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()); - stackableIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end()); - - for (auto& item : js["characterItems"]) + if (js.contains("characterItems") && js["characterItems"].is_array()) { - /* + std::unordered_set existingItemIds; + + std::unordered_set 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()); + stackableIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end()); + + for (auto& item : js["characterItems"]) + { + /* set existing items to rnd number */ - if (item.contains("itemId") && item["itemId"].is_string()) - { - std::string itemId = item["itemId"]; - existingItemIds.insert(itemId); - if (stackableIds.contains(itemId)) item["quantity"] = getRandomQuantity(); + if (item.contains("itemId") && item["itemId"].is_string()) + { + std::string itemId = item["itemId"]; + existingItemIds.insert(itemId); + if (stackableIds.contains(itemId)) item["quantity"] = getRandomQuantity(); + } } - } - auto appendItems = [&](const std::unordered_set& idList, bool isPerk) { - for (const auto& itemId : idList) - if (existingItemIds.find(itemId) == existingItemIds.end()) - js["characterItems"].push_back( - {{"itemId", itemId}, {"quantity", isPerk ? 3 : getRandomQuantity()}}); - }; + auto appendItems = [&](const std::unordered_set& idList, bool isPerk) { + for (const auto& itemId : idList) + if (existingItemIds.find(itemId) == existingItemIds.end()) + js["characterItems"].push_back( + {{"itemId", itemId}, {"quantity", isPerk ? 3 : getRandomQuantity()}}); + }; - if (!slasher) - { - appendItems(_camperItemIds, false); - appendItems(_camperAddonIds, false); - appendItems(_camperOfferingIds, false); - appendItems(_camperPerkIds, true); - } - else - { - appendItems(_slasherAddonIds, false); - appendItems(_slasherOfferingIds, false); - appendItems(_slasherPerkIds, true); + if (!slasher) + { + appendItems(_camperItemIds, false); + appendItems(_camperAddonIds, false); + appendItems(_camperOfferingIds, false); + appendItems(_camperPerkIds, true); + } + else + { + appendItems(_slasherAddonIds, false); + appendItems(_slasherOfferingIds, false); + appendItems(_slasherPerkIds, true); + } } } @@ -380,8 +425,24 @@ void Spoofer::onInventoryAll(std::string& body) int quantity; }; - std::vector categories = {{_camperPerkIds, 3}, {_slasherPerkIds, 3}, {_camperOfferingIds, -1}, - {_slasherOfferingIds, -1}, {_catalogOutfitIds, 1}, {_catalogItemIds, 1}}; + std::vector categories; + if (_config.spoofInventory) + { + categories.push_back({_camperPerkIds, 3}); + categories.push_back({_slasherPerkIds, 3}); + categories.push_back({_camperOfferingIds, -1}); + categories.push_back({_slasherOfferingIds, -1}); + + categories.push_back({_camperItemIds, -1}); + categories.push_back({_camperAddonIds, -1}); + categories.push_back({_slasherAddonIds, -1}); + } + + if (_config.spoofCustomization) + { + categories.push_back({_catalogOutfitIds, 1}); + categories.push_back({_catalogItemIds, 1}); + } for (auto& item : itemsArr) { @@ -481,36 +542,39 @@ void Spoofer::onBloodweb(std::string& body, std::string& respHeaders) json doc = json::parse(body, nullptr, false); if (doc.is_discarded()) return Log::error("JSON parse error for bloodweb response"); - if (body.find("NotAllowedException") != std::string::npos && body.find("not owned") != std::string::npos) + if (_config.spoofCharacterOwnership) { - Log::info("Spoofing bloodweb error for unowned character"); - json mock; - mock["bloodWebLevelChanged"] = false; - mock["updatedWallets"] = json::array(); - mock["bloodWebLevel"] = 16; - mock["prestigeLevel"] = 0; - mock["bloodWebData"] = json::object(); - mock["characterItems"] = json::array(); - mock["characterName"] = this->_lastBloodWebChar; - mock["isEntitled"] = true; - mock["purchaseInfo"] = {{"quantity", 1}, - {"origin", "PlayerInventory"}, - {"reason", "Item(s) added via Purchase"}, - {"lastUpdateAt", std::time(nullptr)}, - {"objectId", this->_lastBloodWebChar}}; + if (body.find("NotAllowedException") != std::string::npos && body.find("not owned") != std::string::npos) + { + Log::info("Spoofing bloodweb error for unowned character"); + json mock; + mock["bloodWebLevelChanged"] = false; + mock["updatedWallets"] = json::array(); + mock["bloodWebLevel"] = 16; + mock["prestigeLevel"] = 0; + mock["bloodWebData"] = json::object(); + mock["characterItems"] = json::array(); + mock["characterName"] = this->_lastBloodWebChar; + mock["isEntitled"] = true; + mock["purchaseInfo"] = {{"quantity", 1}, + {"origin", "PlayerInventory"}, + {"reason", "Item(s) added via Purchase"}, + {"lastUpdateAt", std::time(nullptr)}, + {"objectId", this->_lastBloodWebChar}}; - _unownedCharacters.insert(this->_lastBloodWebChar); // probably not needed but just in case + _unownedCharacters.insert(this->_lastBloodWebChar); // probably not needed but just in case - modifyCharacterData(mock); + modifyCharacterData(mock); - std::regex statusRegex(R"(HTTP\/\d\.\d\s+403)"); - respHeaders = std::regex_replace(respHeaders, statusRegex, "HTTP/1.1 200"); + std::regex statusRegex(R"(HTTP\/\d\.\d\s+403)"); + respHeaders = std::regex_replace(respHeaders, statusRegex, "HTTP/1.1 200"); - body = mock.dump(); + body = mock.dump(); #ifdef _DEBUG - Log::verbose("Spoofed bloodweb request for unowned character."); + Log::verbose("Spoofed bloodweb request for unowned character."); #endif - return; + return; + } } modifyCharacterData(doc); diff --git a/src/unlocker/spoofing.h b/src/unlocker/spoofing.h index ddba0dd..d1087b4 100644 --- a/src/unlocker/spoofing.h +++ b/src/unlocker/spoofing.h @@ -9,6 +9,13 @@ #include +struct SpooferConfig +{ + bool spoofCharacterOwnership = false; + bool spoofInventory = false; + bool spoofCustomization = false; +}; + class Spoofer { public: @@ -17,6 +24,7 @@ class Spoofer private: void registerListeners(Proxy* proxy); void loadData(); + void loadConfig(); bool parseCatalog(std::string data); bool parseStackable(std::string data, std::unordered_set& camperSet, @@ -37,6 +45,8 @@ class Spoofer void serverResponseHandler(const std::string& url, std::string& body, std::string& respHeaders); void clientRequestHandler(std::string& url, const std::string& body, std::string& reqHeaders); + SpooferConfig _config; + std::unordered_set _camperItemIds; std::unordered_set _slasherPowerIds;