feat: add config

This commit is contained in:
2026-04-11 10:41:45 -03:00
parent 83ac8615ba
commit 0300ced79c
2 changed files with 156 additions and 82 deletions
+146 -82
View File
@@ -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<std::string> existingItemIds;
bool slasher = isSlasher(js["characterName"]);
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());
stackableIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end());
for (auto& item : js["characterItems"])
if (js.contains("characterItems") && js["characterItems"].is_array())
{
/*
std::unordered_set<std::string> existingItemIds;
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());
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<std::string>& 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<std::string>& 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<Category> categories = {{_camperPerkIds, 3}, {_slasherPerkIds, 3}, {_camperOfferingIds, -1},
{_slasherOfferingIds, -1}, {_catalogOutfitIds, 1}, {_catalogItemIds, 1}};
std::vector<Category> 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);
+10
View File
@@ -9,6 +9,13 @@
#include <nlohmann/json_fwd.hpp>
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<std::string>& 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<std::string> _camperItemIds;
std::unordered_set<std::string> _slasherPowerIds;