#include "spoofer.h" #include "utils.h" #include "log-sink-cout.h" #include #include #include #include #include #include #include #include /* misc helper functions */ std::unordered_set slasherNames = { "Chuckles", "Bob", "HillBilly", "Nurse", "Shape", "Witch", "Killer07", "Cannibal", "Bear", "Nightmare", "Pig", "Clown", "Spirit", "Plague", "Ghostface", "Demogorgon", "Oni", "Gunslinger"}; bool isSlasher(std::string name) { if (slasherNames.contains(name) || (name.length() == 3 && name[0] == 'K')) return true; return false; } /* spoofer impl */ Spoofer::Spoofer() { _log = new seallib::Logger("Spoofer"); _log->addSink(std::make_shared()); _log->info("Spoofer init"); loadConfig(); _log->info("Starting WebSocket server"); initServer(); } Spoofer::~Spoofer() { _log->info("Stopping WebSocket server"); stopServer(); delete _log; } void Spoofer::registerListeners(TinyMITMProxy* proxy) { proxy->onClientRequest.addListener( [this](const std::string& url, std::string& body, std::string& headers, bool& blockOutgoing) { this->clientRequestHandler(url, body, headers, blockOutgoing); }); proxy->onServerResponse.addListener( [this](const std::string& url, std::string& body, std::string& headers, bool wasBlocked) { this->serverResponseHandler(url, body, headers, wasBlocked); }); } /* config */ struct FileConfig { SpooferConfig profile; WSMessages::Toggles toggles; }; void Spoofer::loadConfig() { FileConfig conf; std::string buffer; std::string configPath = utils::getExePath() + "config.json"; auto errCtx = glz::read_file_json(conf, configPath, buffer); if (errCtx.ec == glz::error_code::none) { _camperItems = conf.profile.camperItems; _camperAddons = conf.profile.camperAddons; _slasherAddons = conf.profile.slasherAddons; _camperOfferings = conf.profile.camperOfferings; _slasherOfferings = conf.profile.slasherOfferings; _globalOfferings = conf.profile.globalOfferings; _camperPerks = conf.profile.camperPerks; _slasherPerks = conf.profile.slasherPerks; _catalogItemIds = conf.profile.catalogItemIds; _dlcListGRDK = conf.profile.dlcListGRDK; _dlcListEGS = conf.profile.dlcListEGS; _dlcListSteam = conf.profile.dlcListSteam; _unlockedCharacters = conf.profile.unlockedCharacters; _spoofItems = conf.toggles.spoofItems; _spoofPerks = conf.toggles.spoofPerks; _spoofCatalog = conf.toggles.spoofCatalog; _spoofDLCs = conf.toggles.spoofDLCs; _log->info("Config loaded successfully from {}", configPath); } else { _log->info("Config not found or invalid at {}, creating default.", configPath); saveConfig(); } } void Spoofer::saveConfig() { std::string configPath = utils::getExePath() + "config.json"; FileConfig conf; conf.profile.camperItems = _camperItems; conf.profile.camperAddons = _camperAddons; conf.profile.slasherAddons = _slasherAddons; conf.profile.camperOfferings = _camperOfferings; conf.profile.slasherOfferings = _slasherOfferings; conf.profile.globalOfferings = _globalOfferings; conf.profile.camperPerks = _camperPerks; conf.profile.slasherPerks = _slasherPerks; conf.profile.catalogItemIds = _catalogItemIds; conf.profile.dlcListGRDK = _dlcListGRDK; conf.profile.dlcListEGS = _dlcListEGS; conf.profile.dlcListSteam = _dlcListSteam; conf.profile.unlockedCharacters = _unlockedCharacters; conf.toggles.spoofItems = _spoofItems; conf.toggles.spoofPerks = _spoofPerks; conf.toggles.spoofCatalog = _spoofCatalog; conf.toggles.spoofDLCs = _spoofDLCs; std::string buffer; auto errCtx = glz::write_file_json(conf, configPath, buffer); if (errCtx.ec != glz::error_code::none) _log->error("Failed to save config to {}", configPath); } /* websocket */ void Spoofer::initServer() { if (_wsServer) PANIC("Attempted to init websocket server from invalid state"); _wsServer = new ix::WebSocketServer(WS_PORT, WS_ADDR); _wsServer->setOnClientMessageCallback([this](std::shared_ptr connectionState, ix::WebSocket& webSocket, const std::unique_ptr& msg) { this->wsMessageCallback(connectionState, webSocket, msg); }); std::pair res = _wsServer->listen(); if (!res.first) PANIC("Websocket server failed to start"); _log->verbose("WebSocket server running @ {}:{}", WS_ADDR, WS_PORT); _wsServer->start(); } void Spoofer::stopServer() { if (_wsServer) { _wsServer->stop(); delete _wsServer; _wsServer = nullptr; } } void Spoofer::wsMessageCallback(std::shared_ptr /*connectionState*/, ix::WebSocket& webSocket, const std::unique_ptr& msg) { std::lock_guard lock(_mutex); switch (msg->type) { case ix::WebSocketMessageType::Open: { _log->verbose("Websocket connection open, URI: {}", msg->openInfo.uri); WSMessages::Init initMsg; initMsg.profile.camperItems = _camperItems; initMsg.profile.camperAddons = _camperAddons; initMsg.profile.slasherAddons = _slasherAddons; initMsg.profile.camperOfferings = _camperOfferings; initMsg.profile.slasherOfferings = _slasherOfferings; initMsg.profile.globalOfferings = _globalOfferings; initMsg.profile.camperPerks = _camperPerks; initMsg.profile.slasherPerks = _slasherPerks; initMsg.profile.catalogItemIds = _catalogItemIds; initMsg.profile.dlcListGRDK = _dlcListGRDK; initMsg.profile.dlcListEGS = _dlcListEGS; initMsg.profile.dlcListSteam = _dlcListSteam; initMsg.profile.unlockedCharacters = _unlockedCharacters; initMsg.toggles.spoofItems = _spoofItems; initMsg.toggles.spoofPerks = _spoofPerks; initMsg.toggles.spoofCatalog = _spoofCatalog; initMsg.toggles.spoofDLCs = _spoofDLCs; std::string out; auto errCtx = glz::write_json(initMsg, out); if (errCtx && errCtx.ec != glz::error_code::none) _log->error("Error occurred while writing init msg"); else webSocket.send(out); break; } case ix::WebSocketMessageType::Close: _log->verbose("Websocket connection close"); break; case ix::WebSocketMessageType::Message: { WSMessages::Request req; auto err = glz::read_json(req, msg->str); if (err.ec != glz::error_code::none) { _log->error("Failed to parse websocket message"); break; } switch (req.action) { case WSMessages::SYNC_STATE: { _camperItems = std::move(req.profile.camperItems); _camperAddons = std::move(req.profile.camperAddons); _slasherAddons = std::move(req.profile.slasherAddons); _camperOfferings = std::move(req.profile.camperOfferings); _slasherOfferings = std::move(req.profile.slasherOfferings); _globalOfferings = std::move(req.profile.globalOfferings); _camperPerks = std::move(req.profile.camperPerks); _slasherPerks = std::move(req.profile.slasherPerks); _catalogItemIds = std::move(req.profile.catalogItemIds); _dlcListGRDK = std::move(req.profile.dlcListGRDK); _dlcListEGS = std::move(req.profile.dlcListEGS); _dlcListSteam = std::move(req.profile.dlcListSteam); _unlockedCharacters = std::move(req.profile.unlockedCharacters); saveConfig(); _log->verbose("Profile state synchronized."); break; } case WSMessages::SYNC_TOGGLES: { _spoofItems = req.toggles.spoofItems; _spoofPerks = req.toggles.spoofPerks; _spoofCatalog = req.toggles.spoofCatalog; _spoofDLCs = req.toggles.spoofDLCs; saveConfig(); _log->verbose("Toggles synchronized."); break; } default: _log->warning("Unknown action: {}", static_cast(req.action)); break; } break; } default: break; } } /* endpoint related helper functions */ void Spoofer::modifyCharacterInventory(glz::generic& js) { bool slasher = isSlasher(js["characterName"].get()); bool hasItems = js.is_object() && js.get_object().contains("characterItems") && js["characterItems"].is_array(); if (!hasItems) return; std::unordered_set existingItemIds; std::unordered_map stackableQty; stackableQty.insert(_camperItems.begin(), _camperItems.end()); stackableQty.insert(_camperOfferings.begin(), _camperOfferings.end()); stackableQty.insert(_globalOfferings.begin(), _globalOfferings.end()); stackableQty.insert(_camperAddons.begin(), _camperAddons.end()); stackableQty.insert(_slasherAddons.begin(), _slasherAddons.end()); stackableQty.insert(_slasherOfferings.begin(), _slasherOfferings.end()); auto& itemsArr = js["characterItems"].get_array(); /* existing items */ for (auto& item : itemsArr) { if (item.is_object() && item.get_object().contains("itemId") && item["itemId"].is_string()) { std::string itemId = item["itemId"].get(); existingItemIds.insert(itemId); auto it = stackableQty.find(itemId); if (it != stackableQty.end()) item["quantity"] = it->second; } } /* item injection */ auto appendItems = [&](const std::unordered_map& idMap) { for (const auto& [itemId, qty] : idMap) if (existingItemIds.find(itemId) == existingItemIds.end()) { glz::json_t::object_t newItem; newItem["itemId"] = itemId; newItem["quantity"] = qty; itemsArr.push_back(newItem); } }; auto appendPerks = [&](const std::unordered_set& idSet) { for (const auto& itemId : idSet) if (existingItemIds.find(itemId) == existingItemIds.end()) { glz::json_t::object_t newItem; newItem["itemId"] = itemId; newItem["quantity"] = 3; itemsArr.push_back(newItem); } }; if (!slasher) { appendItems(_camperItems); appendItems(_camperAddons); appendItems(_camperOfferings); appendPerks(_camperPerks); } else { appendItems(_slasherAddons); appendItems(_slasherOfferings); appendPerks(_slasherPerks); } appendItems(_globalOfferings); } void Spoofer::modifyCharacterData(glz::generic& js) { if (!js.contains("characterName") || !js["characterName"].is_string()) { _log->verbose("attempted to modify invalid char"); return; } std::string name = js["characterName"].get_string(); if (_spoofCharacters) { bool needsLvlSpoofing = false; if (js.contains("isEntitled") && !js["isEntitled"].get_boolean() && js.contains("characterName") && _unlockedCharacters.contains(js["characterName"].get_string())) { js["isEntitled"] = true; js["purchaseInfo"] = glz::json_t::object_t{{"quantity", 1}, {"origin", "PlayerInventory"}, {"reason", "Item(s) added via Purchase"}, {"lastUpdateAt", static_cast(std::time(nullptr))}, {"objectId", name}}; needsLvlSpoofing = true; } if (needsLvlSpoofing) { if (js.contains("bloodWebLevel") && js["bloodWebLevel"].is_number() && js["bloodWebLevel"].get_number() <= 15) if (!js.contains("prestigeLevel") || (js["prestigeLevel"].is_number() && js["prestigeLevel"].get_number() <= 0)) js["bloodWebLevel"] = 16; if (js.contains("bloodWebData")) generateBloodweb(js["bloodWebData"]); } } if (_spoofItems || _spoofPerks) modifyCharacterInventory(js); } void Spoofer::generateBloodweb(glz::generic& js) { if (!js.is_object()) js = glz::json_t::object_t{}; std::vector paths; glz::json_t::array_t ringDataArray; glz::json_t::object_t rootRing; glz::json_t::array_t rootNodeData; glz::json_t::object_t rootNode; rootNode["nodeId"] = "0"; rootNode["state"] = "Collected"; rootNodeData.push_back(rootNode); rootRing["nodeData"] = rootNodeData; ringDataArray.push_back(rootRing); int nodesPerRing[] = {6, 12, 12}; std::vector prevRingNodes = {"0"}; for (int ring = 1; ring <= 3; ++ring) { glz::json_t::array_t nodeDataArray; std::vector currentRingNodes; int numNodes = nodesPerRing[ring - 1]; for (int i = 1; i <= numNodes; ++i) { std::string childId = std::to_string((ring * 100) + i); currentRingNodes.push_back(childId); int parentIndex = (i - 1) / (numNodes / static_cast(prevRingNodes.size())); std::string parentId = prevRingNodes[(std::min)(parentIndex, (int)prevRingNodes.size() - 1)]; paths.push_back(parentId + "_" + childId); std::string item = PLACEHOLDER_ITEM_ID; glz::json_t::object_t node; node["nodeId"] = childId; node["state"] = "Collected"; node["contentId"] = item; nodeDataArray.push_back(node); } glz::json_t::object_t ringEntry; ringEntry["nodeData"] = nodeDataArray; ringDataArray.push_back(ringEntry); prevRingNodes = std::move(currentRingNodes); } glz::json_t::array_t pathsArr; for (auto& p : paths) pathsArr.push_back(p); js["paths"] = pathsArr; js["ringData"] = ringDataArray; } /* api handlers */ void Spoofer::onServerGetAll(std::string& body) { glz::generic doc{}; auto ec = glz::read_json(doc, body); if (ec) return _log->error("JSON parse error for dbd-character-data/get-all"); if (!doc.is_object() || !doc.get_object().contains("list") || !doc["list"].is_array()) return _log->error("Invalid json for dbd-character-data/get-all"); auto& list = doc["list"].get_array(); for (auto& charData : list) modifyCharacterData(charData); auto written = glz::write_json(doc); if (written) body = written.value(); else _log->error("JSON dump error for dbd-character-data/get-all"); } void Spoofer::onServerInventoryAll(std::string& body) { glz::generic doc{}; auto ec = glz::read_json(doc, body); if (ec) return _log->error("JSON parse error for dbd-inventories/all"); if (!doc.is_object() || !doc.get_object().contains("inventoryItems") || !doc["inventoryItems"].is_array()) return _log->error("Invalid json for JSON parse error for dbd-inventories/all"); auto& itemsArr = doc["inventoryItems"].get_array(); std::unordered_set existingIds; int64_t now = std::time(nullptr); std::unordered_map spoofMap; if (_spoofPerks) { for (const auto& id : _camperPerks) spoofMap[id] = 3; for (const auto& id : _slasherPerks) spoofMap[id] = 3; } if (_spoofItems) { for (const auto& [id, qty] : _camperOfferings) spoofMap[id] = qty; for (const auto& [id, qty] : _slasherOfferings) spoofMap[id] = qty; for (const auto& [id, qty] : _globalOfferings) spoofMap[id] = qty; for (const auto& [id, qty] : _camperItems) spoofMap[id] = qty; for (const auto& [id, qty] : _camperAddons) spoofMap[id] = qty; for (const auto& [id, qty] : _slasherAddons) spoofMap[id] = qty; } if (_spoofCatalog) { //for (const auto& id : _catalogOutfitIds) //spoofMap[id] = 1; for (const auto& id : _catalogItemIds) spoofMap[id] = 1; } /* item updates */ for (auto& item : itemsArr) { std::string id; if (item.is_object() && item.get_object().contains("objectId") && item["objectId"].is_string()) id = item["objectId"].get(); if (id.empty()) continue; existingIds.insert(id); auto it = spoofMap.find(id); if (it != spoofMap.end()) item["quantity"] = it->second; } /* item inserts */ for (const auto& [id, qty] : spoofMap) { if (!existingIds.contains(id)) { glz::json_t::object_t newItem; newItem["objectId"] = id; newItem["quantity"] = qty; newItem["lastUpdateAt"] = now; itemsArr.push_back(newItem); } } auto written = glz::write_json(doc); if (written) body = written.value(); else _log->error("JSON dump error for dbd-inventories/all"); _log->verbose("Inventory spoofed"); } void Spoofer::onServerMessageList(std::string& body) { glz::generic doc; auto ec = glz::read_json(doc, body); if (ec) { _log->error("JSON parse error for onServerMessageList"); return; } auto now = std::chrono::system_clock::now(); auto now_seconds = std::chrono::duration_cast(now.time_since_epoch()).count(); auto now_milliseconds = std::chrono::duration_cast(now.time_since_epoch()).count(); /* msg construction */ glz::generic bodyContent = glz::generic::object_t{ {"sections", glz::generic::array_t{ glz::generic::object_t{ {"type", "text"}, {"text", "Japan is turning footsteps into electricity!

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.

A brilliant way to create a " "sustainable and smart city - turning movement into clean, renewable energy."}}, glz::generic::object_t{ {"type", "itemshowcase"}, {"rewards", glz::generic::array_t{glz::generic::object_t{ {"type", "inventory"}, {"id", "ADDON_flashlight_oddbulb"}, {"amount", 1.0}}}}}}}, {"image", glz::generic::object_t{ {"packagedPath", "/Game/UI/UMGAssets/Icons/ItemAddons/iconAddon_powerBulb.iconAddon_powerBulb"}, {"contentVersion", "ccc3f02b0a671fe19a0017d6a69293876a465fd9"}, {"uri", ""}}}}; std::string bodyContentStr; ec = glz::write_json(bodyContent, bodyContentStr); if (ec) { _log->error("JSON write error for onServerMessageList"); return; } glz::generic msg = glz::generic::object_t{ {"allowedPlatforms", glz::generic::array_t{"egs", "grdk", "ps4", "ps5", "steam", "xbox", "xsx"}}, {"flag", "READ"}, {"gameSpecificData", glz::generic::object_t{}}, {"read", false}, {"tag", glz::generic::array_t{"inbox"}}, {"expireAt", static_cast(now_seconds + (1337 * 24 * 60 * 60))}, {"received", static_cast(now_milliseconds)}, {"recipientId", "system"}, {"message", glz::generic::object_t{{"title", "Japan is turning footsteps into electricity"}, {"body", bodyContentStr}}}}; doc["messages"].get_array().push_back(msg); ec = glz::write_json(doc, body); if (ec) { _log->error("JSON write error for onServerMessageList"); return; } _log->verbose("Spoofed message list"); } void Spoofer::onServerBloodweb(std::string& body, std::string& respHeaders) { glz::generic doc; auto ec = glz::read_json(doc, body); if (ec) { _log->error("JSON parse error for onServerBloodweb"); return; } /* false char response */ if (body.find("NotAllowedException") != std::string::npos && body.find("not owned") != std::string::npos) { glz::generic mock = glz::generic::object_t{}; mock["bloodWebLevelChanged"] = false; mock["updatedWallets"] = glz::generic::array_t{}; mock["bloodWebLevel"] = 16; mock["prestigeLevel"] = 0; mock["bloodWebData"] = glz::generic::object_t{}; mock["characterItems"] = glz::generic::array_t{}; mock["characterName"] = this->_lastBwCharacter; mock["isEntitled"] = true; glz::generic::object_t purchaseInfo; purchaseInfo["quantity"] = 1; purchaseInfo["origin"] = "PlayerInventory"; purchaseInfo["reason"] = "Item(s) added via Purchase"; purchaseInfo["lastUpdateAt"] = static_cast(std::time(nullptr)); purchaseInfo["objectId"] = this->_lastBwCharacter; mock["purchaseInfo"] = purchaseInfo; if (_spoofCharacters) generateBloodweb(mock["bloodWebData"]); if (_spoofItems || _spoofPerks) modifyCharacterInventory(mock); std::string new_body; ec = glz::write_json(mock, new_body); if (ec) { _log->error("JSON write error for onServerBloodweb"); return; } body = new_body; std::regex statusRegex(R"(HTTP\/\d\.\d\s+403)"); respHeaders = std::regex_replace(respHeaders, statusRegex, "HTTP/1.1 200"); _log->verbose("Spoofed bloodweb request for unowned character."); } /* bloodweb fixup for already owned perks */ if (_spoofPerks) { if (doc.contains("bloodWebData") && doc["bloodWebData"].is_object()) { auto& bloodWebData = doc["bloodWebData"]; if (!bloodWebData.contains("paths") || !bloodWebData.contains("ringData")) return; for (auto& ring : bloodWebData["ringData"].get_array()) { if (!ring.contains("nodeData") || !ring["nodeData"].is_array()) continue; for (auto& node : ring["nodeData"].get_array()) { if (!node.contains("contentId") || !node["contentId"].is_string()) continue; std::string contentId = node["contentId"].get_string(); if (_camperPerks.contains(contentId) || _slasherPerks.contains(contentId)) node["contentId"] = PLACEHOLDER_ITEM_ID; } } _log->verbose("Fixed bloodweb request"); } } modifyCharacterData(doc); auto written = glz::write_json(doc); if (written) body = written.value(); else _log->error("JSON write error for onServerBloodweb"); _log->verbose("Spoofed bloodweb items for owned character"); } void Spoofer::onServerUpdateEntitlements(const std::string& url, std::string& body) { if (!_spoofDLCs) return; glz::generic doc; auto ec = glz::read_json(doc, body); if (ec) { _log->error("JSON parse error for onClientUpdateEntitlements"); return; } if (doc.contains("entitlements") && doc["entitlements"].is_array()) { auto& entitlements = doc["entitlements"].get_array(); std::unordered_set* dlcList = nullptr; if (url.starts_with("https://grdk.live.bhvrdbd.com/")) dlcList = &_dlcListGRDK; else if (url.starts_with("https://egs.live.bhvrdbd.com/")) dlcList = &_dlcListEGS; else return _log->error("Entitlement spoofing only works with Epic / Xbox"); for (const std::string& dlcId : *dlcList) { auto it = std::ranges::find_if( entitlements, [&](const auto& item) { return item.is_string() && item.get_string() == dlcId; }); if (it == entitlements.end()) entitlements.emplace_back(dlcId); } } ec = glz::write_json(doc, body); if (ec) { _log->error("JSON write error for onClientUpdateEntitlements"); return; } _log->verbose("Spoofed entitlements (response)"); } void Spoofer::onClientBloodweb(std::string& body) { glz::generic doc; auto ec = glz::read_json(doc, body); if (ec) { _log->error("JSON parse error for onClientBloodweb"); return; } if (doc.contains("characterName")) _lastBwCharacter = doc["characterName"].get_string(); } void Spoofer::onClientUpdateEntitlements(const std::string& url, std::string& body) { if (!_spoofDLCs) return; glz::generic doc; auto ec = glz::read_json(doc, body); if (ec) { _log->error("JSON parse error for onClientUpdateEntitlements"); return; } if (doc.contains("clientEntitlementIds") && doc["clientEntitlementIds"].is_array()) { auto& entitlements = doc["clientEntitlementIds"].get_array(); std::unordered_set* dlcList = nullptr; if (url.starts_with("https://grdk.live.bhvrdbd.com/")) dlcList = &_dlcListGRDK; else if (url.starts_with("https://egs.live.bhvrdbd.com/")) dlcList = &_dlcListEGS; else return _log->error("Entitlement spoofing only works with Epic / Xbox"); for (const std::string& dlcId : *dlcList) { auto it = std::ranges::find_if( entitlements, [&](const auto& item) { return item.is_string() && item.get_string() == dlcId; }); if (it == entitlements.end()) entitlements.emplace_back(dlcId); } } ec = glz::write_json(doc, body); if (ec) { _log->error("JSON write error for onClientUpdateEntitlements"); return; } _log->verbose("Spoofed entitlements (client)"); } /* proxy handlers */ void Spoofer::serverResponseHandler(const std::string& url, std::string& body, std::string& headers, bool& /*blockOutgoing*/) { if (url.find("bhvrdbd.com") == std::string::npos) return; std::lock_guard lock(_mutex); if (url.find("/api/v1/messages/listV2") != std::string::npos) return onServerMessageList(body); if (url.find("/api/v1/dbd-inventories/all") != std::string::npos) return onServerInventoryAll(body); if (url.find("/api/v1/dbd-character-data/get-all") != std::string::npos) return onServerGetAll(body); if (url.find("/api/v1/owned-products/get-update-entitlements") != std::string::npos) return onServerUpdateEntitlements(url, body); if (url.find("/api/v1/dbd-character-data/bloodweb") != std::string::npos || url.find("/api/v1/dbd-character-data/bloodweb/v2") != std::string::npos || url.find("/api/v1/dbd-character-data/bulk-spending-bloodweb") != std::string::npos) return onServerBloodweb(body, headers); } void Spoofer::clientRequestHandler(const std::string& url, std::string& body, std::string& /*headers*/, bool /*wasBlocked*/) { if (url.find("bhvrdbd.com") == std::string::npos) return; std::lock_guard lock(_mutex); if (url.find("/api/v1/owned-products/get-update-entitlements") != std::string::npos) return onClientUpdateEntitlements(url, body); if (url.find("/api/v1/dbd-character-data/bloodweb") != std::string::npos || url.find("/api/v1/dbd-character-data/bloodweb/v2") != std::string::npos || url.find("/api/v1/dbd-character-data/bulk-spending-bloodweb") != std::string::npos) return onClientBloodweb(body); }