/* * The Mana Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2024 The Mana Developers * * This file is part of The Mana Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "configuration.h" #include "event.h" #include "log.h" #include "utils/stringutils.h" #include "utils/xml.h" void ConfigurationObject::setValue(const std::string &key, const std::string &value) { mOptions[key] = value; } std::string ConfigurationObject::getValue(const std::string &key, const std::string &deflt) const { auto iter = mOptions.find(key); return iter != mOptions.end() ? iter->second : deflt; } int ConfigurationObject::getValue(const std::string &key, int deflt) const { auto iter = mOptions.find(key); return iter != mOptions.end() ? atoi(iter->second.c_str()) : deflt; } unsigned ConfigurationObject::getValue(const std::string &key, unsigned deflt) const { auto iter = mOptions.find(key); return iter != mOptions.end() ? atol(iter->second.c_str()) : deflt; } double ConfigurationObject::getValue(const std::string &key, double deflt) const { auto iter = mOptions.find(key); return iter != mOptions.end() ? atof(iter->second.c_str()) : deflt; } void ConfigurationObject::clear() { mOptions.clear(); } ConfigurationObject::~ConfigurationObject() { clear(); } void Configuration::cleanDefaults() { if (mDefaultsData) { for (auto &[_, variableData] : *mDefaultsData) { delete variableData; } delete mDefaultsData; mDefaultsData = nullptr; } } Configuration::~Configuration() { cleanDefaults(); } void Configuration::setDefaultValues(DefaultsData *defaultsData) { cleanDefaults(); mDefaultsData = defaultsData; } VariableData *Configuration::getDefault(const std::string &key, VariableData::DataType type) const { if (mDefaultsData) { auto itdef = mDefaultsData->find(key); if (itdef != mDefaultsData->end() && itdef->second && itdef->second->getType() == type) { return itdef->second; } Log::info("%s: No value in registry for key %s", mConfigPath.c_str(), key.c_str()); } return nullptr; } int Configuration::getIntValue(const std::string &key) const { int defaultValue = 0; auto iter = mOptions.find(key); if (iter == mOptions.end()) { VariableData *vd = getDefault(key, VariableData::DATA_INT); if (vd) defaultValue = ((IntData*)vd)->getData(); } else { defaultValue = atoi(iter->second.c_str()); } return defaultValue; } std::string Configuration::getStringValue(const std::string &key) const { std::string defaultValue; auto iter = mOptions.find(key); if (iter == mOptions.end()) { if (VariableData *vd = getDefault(key, VariableData::DATA_STRING)) defaultValue = ((StringData*)vd)->getData(); } else { defaultValue = iter->second; } return defaultValue; } float Configuration::getFloatValue(const std::string &key) const { float defaultValue = 0.0f; auto iter = mOptions.find(key); if (iter == mOptions.end()) { if (VariableData *vd = getDefault(key, VariableData::DATA_FLOAT)) defaultValue = ((FloatData*)vd)->getData(); } else { defaultValue = atof(iter->second.c_str()); } return defaultValue; } bool Configuration::getBoolValue(const std::string &key) const { bool defaultValue = false; auto iter = mOptions.find(key); if (iter == mOptions.end()) { if (VariableData *vd = getDefault(key, VariableData::DATA_BOOL)) defaultValue = ((BoolData*)vd)->getData(); } else { return getBoolFromString(iter->second, defaultValue); } return defaultValue; } void ConfigurationObject::initFromXML(XML::Node parent_node) { clear(); for (auto node : parent_node.children()) { if (node.name() == "option") { std::string name = node.getProperty("name", std::string()); if (!name.empty()) mOptions[name] = node.getProperty("value", std::string()); } } } void Configuration::init(const std::string &filename, bool useResManager) { XML::Document doc(filename, useResManager); if (useResManager) mConfigPath = "PhysFS://" + filename; else mConfigPath = filename; XML::Node rootNode = doc.rootNode(); if (!rootNode) { Log::info("Couldn't open configuration file: %s", filename.c_str()); return; } if (rootNode.name() != "configuration") { Log::warn("No configuration file (%s)", filename.c_str()); return; } initFromXML(rootNode); } template struct Option { Option(const char *name, const T &value, const T &defaultValue) : name(name), value(value), defaultValue(defaultValue) {} const char *name; const T &value; const T &defaultValue; }; template static void serialize(XML::Writer &writer, const Option &option) { if (option.value == option.defaultValue) return; writer.startElement("option"); writer.addAttribute("name", option.name); writer.addAttribute("value", option.value); writer.endElement(); } static void serialize(XML::Writer &writer, const ItemShortcutEntry &itemShortcut) { writer.startElement("itemshortcut"); writer.addAttribute("index", itemShortcut.index); writer.addAttribute("id", itemShortcut.itemId); writer.endElement(); } static void serialize(XML::Writer &writer, const EmoteShortcutEntry &emoteShortcut) { writer.startElement("emoteshortcut"); writer.addAttribute("index", emoteShortcut.index); writer.addAttribute("id", emoteShortcut.emoteId); writer.endElement(); } static void serialize(XML::Writer &writer, const Outfit &outfit) { writer.startElement("outfit"); writer.addAttribute("index", outfit.index); writer.addAttribute("items", outfit.items); writer.addAttribute("unequip", outfit.unequip); writer.endElement(); } static void serialize(XML::Writer &writer, const UserColor &color) { if (!color.color.empty()) writer.addAttribute("color", color.color); writer.addAttribute("gradient", color.gradient); if (color.delay) writer.addAttribute("delay", *color.delay); } static void serialize(XML::Writer &writer, const WindowState &state) { if (state.x) writer.addAttribute("x", *state.x); if (state.y) writer.addAttribute("y", *state.y); if (state.width) writer.addAttribute("width", *state.width); if (state.height) writer.addAttribute("height", *state.height); if (state.visible) writer.addAttribute("visible", *state.visible); if (state.sticky) writer.addAttribute("sticky", *state.sticky); } static const char *serverTypeToString(ServerType type) { switch (type) { case ServerType::TmwAthena: return "tmwathena"; case ServerType::ManaServ: return "manaserv"; default: return ""; } } static void serialize(XML::Writer &writer, const ServerInfo &server) { writer.startElement("server"); writer.addAttribute("name", server.name); writer.addAttribute("type", serverTypeToString(server.type)); writer.startElement("connection"); writer.addAttribute("hostname", server.hostname); writer.addAttribute("port", server.port); writer.endElement(); // connection if (!server.description.empty()) { writer.startElement("description"); writer.writeText(server.description); writer.endElement(); } if (!server.persistentIp) { writer.startElement("persistentIp"); writer.writeText(server.persistentIp ? "1" : "0"); writer.endElement(); } writer.endElement(); // server } template void serdeOptions(T option) { option("OverlayDetail", &Config::overlayDetail); option("speechBubblecolor", &Config::speechBubblecolor); option("speechBubbleAlpha", &Config::speechBubbleAlpha); option("speech", &Config::speech); option("visiblenames", &Config::visibleNames); option("showgender", &Config::showGender); option("showMonstersTakedDamage", &Config::showMonstersTakedDamage); option("showWarps", &Config::showWarps); option("hideCompletedQuests", &Config::hideCompletedQuests); option("particleMaxCount", &Config::particleMaxCount); option("particleFastPhysics", &Config::particleFastPhysics); option("particleEmitterSkip", &Config::particleEmitterSkip); option("particleeffects", &Config::particleEffects); option("logToStandardOut", &Config::logToStandardOut); option("opengl", &Config::opengl); option("vsync", &Config::vsync); option("windowmode", &Config::windowMode); option("screenwidth", &Config::screenWidth); option("screenheight", &Config::screenHeight); option("scale", &Config::scale); option("sound", &Config::sound); option("sfxVolume", &Config::sfxVolume); option("notificationsVolume", &Config::notificationsVolume); option("musicVolume", &Config::musicVolume); option("fpslimit", &Config::fpsLimit); option("remember", &Config::remember); option("username", &Config::username); option("lastCharacter", &Config::lastCharacter); option("updatehost", &Config::updatehost); option("screenshotDirectory", &Config::screenshotDirectory); option("screenshotDirectorySuffix", &Config::screenshotDirectorySuffix); option("useScreenshotDirectorySuffix", &Config::useScreenshotDirectorySuffix); option("EnableSync", &Config::enableSync); option("joystickEnabled", &Config::joystickEnabled); option("upTolerance", &Config::upTolerance); option("downTolerance", &Config::downTolerance); option("leftTolerance", &Config::leftTolerance); option("rightTolerance", &Config::rightTolerance); option("logNpcInGui", &Config::logNpcInGui); option("download-music", &Config::downloadMusic); option("guialpha", &Config::guiAlpha); option("ChatLogLength", &Config::chatLogLength); option("enableChatLog", &Config::enableChatLog); option("whispertab", &Config::whisperTab); option("customcursor", &Config::customCursor); option("showownname", &Config::showOwnName); option("showpickupparticle", &Config::showPickupParticle); option("showpickupchat", &Config::showPickupChat); option("showMinimap", &Config::showMinimap); option("fontSize", &Config::fontSize); option("ReturnToggles", &Config::returnTogglesChat); option("ScrollLaziness", &Config::scrollLaziness); option("ScrollRadius", &Config::scrollRadius); option("ScrollCenterOffsetX", &Config::scrollCenterOffsetX); option("ScrollCenterOffsetY", &Config::scrollCenterOffsetY); option("onlineServerList", &Config::onlineServerList); option("theme", &Config::theme); option("disableTransparency", &Config::disableTransparency); option("persistent-player-list", &Config::persistentPlayerList); option("player-ignore-strategy", &Config::playerIgnoreStrategy); option("default-player-permissions", &Config::defaultPlayerPermissions); } void serialize(XML::Writer &writer, const Config &config) { const Config defaults; auto serializeOption = [&](const char *name, auto member) { serialize(writer, Option { name, config.*member, defaults.*member }); }; writer.startElement("configuration"); serdeOptions(serializeOption); for (const auto &[name, value] : config.unknownOptions) serialize(writer, Option { name.c_str(), value, std::string() }); for (const auto &[action, key] : config.keys) { writer.startElement("key"); writer.addAttribute("action", action); writer.addAttribute("key", key); writer.endElement(); } for (auto &itemShortcut : config.itemShortcuts) serialize(writer, itemShortcut); for (auto &emoteShortcut : config.emoteShortcuts) serialize(writer, emoteShortcut); for (auto &outfit : config.outfits) serialize(writer, outfit); for (auto &[type, color] : config.colors) { writer.startElement("color"); writer.addAttribute("type", type); serialize(writer, color); writer.endElement(); } for (const auto &[name, state] : config.windows) { writer.startElement("window"); writer.addAttribute("name", name); serialize(writer, state); writer.endElement(); // window } for (const auto &server : config.servers) { if (server.save && server.isValid()) serialize(writer, server); } for (const auto &[name, relation] : config.players) { writer.startElement("player"); writer.addAttribute("name", name); writer.addAttribute("relation", static_cast(relation)); writer.endElement(); } writer.endElement(); // configuration } void deserialize(XML::Node node, ItemShortcutEntry &itemShortcut) { node.attribute("index", itemShortcut.index); node.attribute("id", itemShortcut.itemId); } void deserialize(XML::Node node, EmoteShortcutEntry &emoteShortcut) { node.attribute("index", emoteShortcut.index); node.attribute("id", emoteShortcut.emoteId); } void deserialize(XML::Node node, Outfit &outfit) { node.attribute("index", outfit.index); node.attribute("items", outfit.items); node.attribute("unequip", outfit.unequip); } void deserialize(XML::Node node, UserColor &color) { node.attribute("color", color.color); node.attribute("gradient", color.gradient); node.attribute("delay", color.delay); } void deserialize(XML::Node node, WindowState &state) { node.attribute("x", state.x); node.attribute("y", state.y); node.attribute("width", state.width); node.attribute("height", state.height); node.attribute("visible", state.visible); node.attribute("sticky", state.sticky); } void deserialize(XML::Node node, ServerInfo &server) { node.attribute("name", server.name); std::string type; node.attribute("type", type); server.type = ServerInfo::parseType(type); server.save = true; for (auto node : node.children()) { if (node.name() == "connection") { node.attribute("hostname", server.hostname); node.attribute("port", server.port); } else if (node.name() == "description") { server.description = node.textContent(); } else if (node.name() == "persistentIp") { const std::string value { node.textContent() }; server.persistentIp = getBoolFromString(value, server.persistentIp); } } } void deserialize(XML::Node node, Config &config) { std::map options; for (auto node : node.children()) { if (node.name() == "option") { std::string name; if (!node.attribute("name", name)) continue; node.attribute("value", options[name]); } else if (node.name() == "list") { // Backwards compatibility for old configuration files for (auto node : node.children()) { if (node.name() == "player") { std::string playerName; PlayerRelation relation = PlayerRelation::Neutral; for (auto node : node.children()) { if (node.name() == "option") { std::string optionName; if (node.attribute("name", optionName)) { if (optionName == "name") node.attribute("value", playerName); else if (optionName == "relation") node.attribute("value", relation); } } } if (!playerName.empty()) config.players[playerName] = relation; } } } else if (node.name() == "key") { std::string action; node.attribute("action", action); if (!action.empty()) node.attribute("key", config.keys[action]); } else if (node.name() == "itemshortcut") { deserialize(node, config.itemShortcuts.emplace_back()); } else if (node.name() == "emoteshortcut") { deserialize(node, config.emoteShortcuts.emplace_back()); } else if (node.name() == "outfit") { deserialize(node, config.outfits.emplace_back()); } else if (node.name() == "color") { std::string type; node.attribute("type", type); deserialize(node, config.colors[type]); } else if (node.name() == "window") { std::string name; node.attribute("name", name); deserialize(node, config.windows[name]); } else if (node.name() == "player") { std::string name; node.attribute("name", name); if (!name.empty()) node.attribute("relation", config.players[name]); } else if (node.name() == "server") { deserialize(node, config.servers.emplace_back()); } } auto deserializeOption = [&](const char *name, auto member) { auto it = options.find(name); if (it == options.end()) return; fromString(it->second.data(), config.*member); options.erase(it); }; serdeOptions(deserializeOption); config.unknownOptions = std::move(options); }