diff options
Diffstat (limited to 'src/server/apps')
24 files changed, 7886 insertions, 0 deletions
diff --git a/src/server/apps/CMakeLists.txt b/src/server/apps/CMakeLists.txt new file mode 100644 index 0000000000..807c89efc3 --- /dev/null +++ b/src/server/apps/CMakeLists.txt @@ -0,0 +1,198 @@ +# +# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# Make the script module list available in the current scope +GetApplicationsList(APPLICATIONS_BUILD_LIST) + +if (APPS_BUILD STREQUAL "none") + set(APPS_DEFAULT_BUILD "disabled") +else() + set(APPS_DEFAULT_BUILD "enabled") +endif() + +# Sets BUILD_APPS_USE_WHITELIST +# Sets BUILD_APPS_WHITELIST +if (APPS_BUILD MATCHES "-only") + set(BUILD_APPS_USE_WHITELIST ON) + + if (APPS_BUILD STREQUAL "auth-only") + list(APPEND BUILD_APPS_WHITELIST authserver) + endif() + + if (APPS_BUILD STREQUAL "world-only") + list(APPEND BUILD_APPS_WHITELIST worldserver) + endif() +endif() + +# Set the SCRIPTS_${BUILD_APP} variables from the +# variables set above +foreach(BUILD_APP ${APPLICATIONS_BUILD_LIST}) + ApplicationNameToVariable(${BUILD_APP} BUILD_APP_VARIABLE) + + if(${BUILD_APP_VARIABLE} STREQUAL "default") + if(BUILD_APPS_USE_WHITELIST) + list(FIND BUILD_APPS_WHITELIST "${BUILD_APP}" INDEX) + if(${INDEX} GREATER -1) + set(${BUILD_APP_VARIABLE} ${APPS_DEFAULT_BUILD}) + else() + set(${BUILD_APP_VARIABLE} "disabled") + endif() + else() + set(${BUILD_APP_VARIABLE} ${APPS_DEFAULT_BUILD}) + endif() + endif() + + # Build the Graph values + if(${BUILD_APP_VARIABLE} MATCHES "enabled") + list(APPEND BUILD_APP_GRAPH_KEYS apps) + set(BUILD_APP_VALUE_DISPLAY_apps apps) + list(APPEND BUILD_APP_VALUE_CONTAINS_apps ${BUILD_APP}) + + if (${BUILD_APP} MATCHES "authserver") + set (BUILD_APPLICATION_AUTHSERVER 1) + elseif(${BUILD_APP} MATCHES "worldserver") + set (BUILD_APPLICATION_WORLDSERVER 1) + endif() + else() + list(APPEND BUILD_APP_GRAPH_KEYS disabled) + set(BUILD_APP_VALUE_DISPLAY_disabled disabled) + list(APPEND BUILD_APP_VALUE_CONTAINS_disabled ${BUILD_APP}) + endif() +endforeach() + +list(SORT BUILD_APP_GRAPH_KEYS) +list(REMOVE_DUPLICATES BUILD_APP_GRAPH_KEYS) + +# Display the graphs +message("") +message("* Apps build list (${APPS_BUILD}):") +message(" |") + +foreach(BUILD_APP_GRAPH_KEY ${BUILD_APP_GRAPH_KEYS}) + if(NOT BUILD_APP_GRAPH_KEY STREQUAL "disabled") + message(" +- ${BUILD_APP_VALUE_DISPLAY_${BUILD_APP_GRAPH_KEY}}") + else() + message(" | ${BUILD_APP_VALUE_DISPLAY_${BUILD_APP_GRAPH_KEY}}") + endif() + foreach(BUILD_APP_GRAPH_ENTRY ${BUILD_APP_VALUE_CONTAINS_${BUILD_APP_GRAPH_KEY}}) + message(" | +- ${BUILD_APP_GRAPH_ENTRY}") + endforeach() + message(" |") +endforeach() + +message("") + +GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) + +# Generates the actual apps projects +foreach(APPLICATION_NAME ${APPLICATIONS_BUILD_LIST}) + GetPathToApplication(${APPLICATION_NAME} SOURCE_APP_PATH) + ApplicationNameToVariable(${APPLICATION_NAME} BUILD_APP_VARIABLE) + + if (${BUILD_APP_VARIABLE} STREQUAL "disabled") + continue() + endif() + + unset(APP_PRIVATE_SOURCES) + CollectSourceFiles( + ${SOURCE_APP_PATH} + APP_PRIVATE_SOURCES + # Exclude + ${SOURCE_APP_PATH}/PrecompiledHeaders) + + if (WIN32) + list(APPEND APP_PRIVATE_SOURCES ${winDebugging}) + + if (${APPLICATION_NAME} MATCHES "worldserver") + list(APPEND APP_PRIVATE_SOURCES ${winService}) + endif() + + if (MSVC) + list(APPEND APP_PRIVATE_SOURCES ${SOURCE_APP_PATH}/${APPLICATION_NAME}.rc) + endif() + endif() + + GetProjectNameOfApplicationName(${APPLICATION_NAME} APP_PROJECT_NAME) + + # Create the application project + add_executable(${APP_PROJECT_NAME} + ${APP_PRIVATE_SOURCES}) + + add_dependencies(${APP_PROJECT_NAME} revision.h) + + target_link_libraries(${APP_PROJECT_NAME} + PRIVATE + acore-core-interface) + + if (${APP_PROJECT_NAME} MATCHES "authserver") + target_link_libraries(${APP_PROJECT_NAME} + PUBLIC + shared) + elseif(${APP_PROJECT_NAME} MATCHES "worldserver") + target_link_libraries(${APP_PROJECT_NAME} + PUBLIC + modules + scripts + game + gsoap + readline + gperftools) + + if (UNIX AND NOT NOJEM) + set(${APP_PROJECT_NAME}_LINK_FLAGS "-pthread -lncurses ${${APP_PROJECT_NAME}_LINK_FLAGS}") + endif() + + set_target_properties(${APP_PROJECT_NAME} PROPERTIES LINK_FLAGS "${${APP_PROJECT_NAME}_LINK_FLAGS}") + + # Add all dynamic projects as dependency to the worldserver + if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES) + add_dependencies(${APP_PROJECT_NAME} ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES}) + endif() + endif() + + unset(APP_PUBLIC_INCLUDES) + CollectIncludeDirectories( + ${SOURCE_APP_PATH} + APP_PUBLIC_INCLUDES + # Exclude + ${SOURCE_APP_PATH}/PrecompiledHeaders) + + target_include_directories(${APP_PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) + + target_include_directories(${APP_PROJECT_NAME} + PUBLIC + ${APP_PUBLIC_INCLUDES} + PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_NAME}) + + set_target_properties(${APP_PROJECT_NAME} + PROPERTIES + FOLDER + "server") + + # Install config + CopyApplicationConfig(${APP_PROJECT_NAME} ${APPLICATION_NAME}) + + if (UNIX) + install(TARGETS ${APP_PROJECT_NAME} DESTINATION bin) + elseif (WIN32) + install(TARGETS ${APP_PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_PREFIX}") + endif() + + set(PATH_TO_PCH ${SOURCE_APP_PATH}/PrecompiledHeaders/${APPLICATION_NAME}PCH.h) + + # Generate precompiled header + if (USE_COREPCH AND EXISTS ${PATH_TO_PCH}) + add_cxx_pch(${APP_PROJECT_NAME} ${PATH_TO_PCH}) + endif() +endforeach()
\ No newline at end of file diff --git a/src/server/apps/authserver/Authentication/AuthCodes.cpp b/src/server/apps/authserver/Authentication/AuthCodes.cpp new file mode 100644 index 0000000000..0f7e5d0e09 --- /dev/null +++ b/src/server/apps/authserver/Authentication/AuthCodes.cpp @@ -0,0 +1,39 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#include "AuthCodes.h" +#include "RealmList.h" + +namespace AuthHelper +{ + constexpr static uint32 MAX_PRE_BC_CLIENT_BUILD = 6141; + + bool IsPreBCAcceptedClientBuild(uint32 build) + { + return build <= MAX_PRE_BC_CLIENT_BUILD && sRealmList->GetBuildInfo(build); + } + + bool IsPostBCAcceptedClientBuild(uint32 build) + { + return build > MAX_PRE_BC_CLIENT_BUILD && sRealmList->GetBuildInfo(build); + } + + bool IsAcceptedClientBuild(uint32 build) + { + return sRealmList->GetBuildInfo(build) != nullptr; + } +}; diff --git a/src/server/apps/authserver/Authentication/AuthCodes.h b/src/server/apps/authserver/Authentication/AuthCodes.h new file mode 100644 index 0000000000..35037e0a3b --- /dev/null +++ b/src/server/apps/authserver/Authentication/AuthCodes.h @@ -0,0 +1,91 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _AUTHCODES_H +#define _AUTHCODES_H + +#include "Define.h" +#include <array> + +enum AuthResult +{ + WOW_SUCCESS = 0x00, + WOW_FAIL_BANNED = 0x03, + WOW_FAIL_UNKNOWN_ACCOUNT = 0x04, + WOW_FAIL_INCORRECT_PASSWORD = 0x05, + WOW_FAIL_ALREADY_ONLINE = 0x06, + WOW_FAIL_NO_TIME = 0x07, + WOW_FAIL_DB_BUSY = 0x08, + WOW_FAIL_VERSION_INVALID = 0x09, + WOW_FAIL_VERSION_UPDATE = 0x0A, + WOW_FAIL_INVALID_SERVER = 0x0B, + WOW_FAIL_SUSPENDED = 0x0C, + WOW_FAIL_FAIL_NOACCESS = 0x0D, + WOW_SUCCESS_SURVEY = 0x0E, + WOW_FAIL_PARENTCONTROL = 0x0F, + WOW_FAIL_LOCKED_ENFORCED = 0x10, + WOW_FAIL_TRIAL_ENDED = 0x11, + WOW_FAIL_USE_BATTLENET = 0x12, + WOW_FAIL_ANTI_INDULGENCE = 0x13, + WOW_FAIL_EXPIRED = 0x14, + WOW_FAIL_NO_GAME_ACCOUNT = 0x15, + WOW_FAIL_CHARGEBACK = 0x16, + WOW_FAIL_INTERNET_GAME_ROOM_WITHOUT_BNET = 0x17, + WOW_FAIL_GAME_ACCOUNT_LOCKED = 0x18, + WOW_FAIL_UNLOCKABLE_LOCK = 0x19, + WOW_FAIL_CONVERSION_REQUIRED = 0x20, + WOW_FAIL_DISCONNECTED = 0xFF +}; + +enum LoginResult +{ + LOGIN_OK = 0x00, + LOGIN_FAILED = 0x01, + LOGIN_FAILED2 = 0x02, + LOGIN_BANNED = 0x03, + LOGIN_UNKNOWN_ACCOUNT = 0x04, + LOGIN_UNKNOWN_ACCOUNT3 = 0x05, + LOGIN_ALREADYONLINE = 0x06, + LOGIN_NOTIME = 0x07, + LOGIN_DBBUSY = 0x08, + LOGIN_BADVERSION = 0x09, + LOGIN_DOWNLOAD_FILE = 0x0A, + LOGIN_FAILED3 = 0x0B, + LOGIN_SUSPENDED = 0x0C, + LOGIN_FAILED4 = 0x0D, + LOGIN_CONNECTED = 0x0E, + LOGIN_PARENTALCONTROL = 0x0F, + LOGIN_LOCKED_ENFORCED = 0x10 +}; + +enum ExpansionFlags +{ + POST_BC_EXP_FLAG = 0x2, + PRE_BC_EXP_FLAG = 0x1, + NO_VALID_EXP_FLAG = 0x0 +}; + +struct RealmBuildInfo; + +namespace AuthHelper +{ + bool IsAcceptedClientBuild(uint32 build); + bool IsPostBCAcceptedClientBuild(uint32 build); + bool IsPreBCAcceptedClientBuild(uint32 build); +}; + +#endif diff --git a/src/server/apps/authserver/Main.cpp b/src/server/apps/authserver/Main.cpp new file mode 100644 index 0000000000..b0105d658b --- /dev/null +++ b/src/server/apps/authserver/Main.cpp @@ -0,0 +1,298 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +/** +* @file main.cpp +* @brief Authentication Server main program +* +* This file contains the main program for the +* authentication server +*/ + +#include "AppenderDB.h" +#include "AuthSocketMgr.h" +#include "Banner.h" +#include "Config.h" +#include "DatabaseEnv.h" +#include "DatabaseLoader.h" +#include "DeadlineTimer.h" +#include "GitRevision.h" +#include "IPLocation.h" +#include "IoContext.h" +#include "Log.h" +#include "MySQLThreading.h" +#include "ProcessPriority.h" +#include "RealmList.h" +#include "SecretMgr.h" +#include "SharedDefines.h" +#include "Util.h" +#include <boost/asio/signal_set.hpp> +#include <boost/program_options.hpp> +#include <boost/version.hpp> +#include <csignal> +#include <filesystem> +#include <iostream> +#include <openssl/crypto.h> +#include <openssl/opensslv.h> + +#ifndef _ACORE_REALM_CONFIG +#define _ACORE_REALM_CONFIG "authserver.conf" +#endif + +using boost::asio::ip::tcp; +using namespace boost::program_options; +namespace fs = std::filesystem; + +bool StartDB(); +void StopDB(); +void SignalHandler(std::weak_ptr<Acore::Asio::IoContext> ioContextRef, boost::system::error_code const& error, int signalNumber); +void KeepDatabaseAliveHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> dbPingTimerRef, int32 dbPingInterval, boost::system::error_code const& error); +void BanExpiryHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimerRef, int32 banExpiryCheckInterval, boost::system::error_code const& error); +variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile); + +/// Launch the auth server +int main(int argc, char** argv) +{ + Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_AUTHSERVER; + signal(SIGABRT, &Acore::AbortHandler); + + // Command line parsing + auto configFile = fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_REALM_CONFIG)); + auto vm = GetConsoleArguments(argc, argv, configFile); + + // exit if help or version is enabled + if (vm.count("help")) + return 0; + + // Add file and args in config + sConfigMgr->Configure(configFile.generic_string(), std::vector<std::string>(argv, argv + argc)); + + if (!sConfigMgr->LoadAppConfigs()) + return 1; + + // Init logging + sLog->RegisterAppender<AppenderDB>(); + sLog->Initialize(nullptr); + + Acore::Banner::Show("authserver", + [](std::string_view text) + { + LOG_INFO("server.authserver", text); + }, + []() + { + LOG_INFO("server.authserver", "> Using configuration file {}", sConfigMgr->GetFilename()); + LOG_INFO("server.authserver", "> Using SSL version: {} (library: {})", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION)); + LOG_INFO("server.authserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100); + }); + + // authserver PID file creation + std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", ""); + if (!pidFile.empty()) + { + if (uint32 pid = CreatePIDFile(pidFile)) + LOG_INFO("server.authserver", "Daemon PID: {}\n", pid); // outError for red color in console + else + { + LOG_ERROR("server.authserver", "Cannot create PID file {} (possible error: permission)\n", pidFile); + return 1; + } + } + + // Initialize the database connection + if (!StartDB()) + return 1; + + sSecretMgr->Initialize(); + + // Load IP Location Database + sIPLocation->Load(); + + std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); }); + + std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>(); + + // Get the list of realms for the server + sRealmList->Initialize(*ioContext, sConfigMgr->GetOption<int32>("RealmsStateUpdateDelay", 20)); + + std::shared_ptr<void> sRealmListHandle(nullptr, [](void*) { sRealmList->Close(); }); + + if (sRealmList->GetRealms().empty()) + { + LOG_ERROR("server.authserver", "No valid realms specified."); + return 1; + } + + // Stop auth server if dry run + if (sConfigMgr->isDryRun()) + { + LOG_INFO("server.authserver", "Dry run completed, terminating."); + return 0; + } + + // Start the listening port (acceptor) for auth connections + int32 port = sConfigMgr->GetOption<int32>("RealmServerPort", 3724); + if (port < 0 || port > 0xFFFF) + { + LOG_ERROR("server.authserver", "Specified port out of allowed range (1-65535)"); + return 1; + } + + std::string bindIp = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0"); + + if (!sAuthSocketMgr.StartNetwork(*ioContext, bindIp, port)) + { + LOG_ERROR("server.authserver", "Failed to initialize network"); + return 1; + } + + std::shared_ptr<void> sAuthSocketMgrHandle(nullptr, [](void*) { sAuthSocketMgr.StopNetwork(); }); + + // Set signal handlers + boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM); +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + signals.add(SIGBREAK); +#endif + signals.async_wait(std::bind(&SignalHandler, std::weak_ptr<Acore::Asio::IoContext>(ioContext), std::placeholders::_1, std::placeholders::_2)); + + // Set process priority according to configuration settings + SetProcessPriority("server.authserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false)); + + // Enabled a timed callback for handling the database keep alive ping + int32 dbPingInterval = sConfigMgr->GetOption<int32>("MaxPingTime", 30); + std::shared_ptr<Acore::Asio::DeadlineTimer> dbPingTimer = std::make_shared<Acore::Asio::DeadlineTimer>(*ioContext); + dbPingTimer->expires_from_now(boost::posix_time::minutes(dbPingInterval)); + dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, std::weak_ptr<Acore::Asio::DeadlineTimer>(dbPingTimer), dbPingInterval, std::placeholders::_1)); + + int32 banExpiryCheckInterval = sConfigMgr->GetOption<int32>("BanExpiryCheckInterval", 60); + std::shared_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimer = std::make_shared<Acore::Asio::DeadlineTimer>(*ioContext); + banExpiryCheckTimer->expires_from_now(boost::posix_time::seconds(banExpiryCheckInterval)); + banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, std::weak_ptr<Acore::Asio::DeadlineTimer>(banExpiryCheckTimer), banExpiryCheckInterval, std::placeholders::_1)); + + // Start the io service worker loop + ioContext->run(); + + banExpiryCheckTimer->cancel(); + dbPingTimer->cancel(); + + LOG_INFO("server.authserver", "Halting process..."); + + signals.cancel(); + + return 0; +} + +/// Initialize connection to the database +bool StartDB() +{ + MySQL::Library_Init(); + + // Load databases + // NOTE: While authserver is singlethreaded you should keep synch_threads == 1. + // Increasing it is just silly since only 1 will be used ever. + DatabaseLoader loader("server.authserver"); + loader + .AddDatabase(LoginDatabase, "Login"); + + if (!loader.Load()) + return false; + + LOG_INFO("server.authserver", "Started auth database connection pool."); + sLog->SetRealmId(0); // Enables DB appenders when realm is set. + return true; +} + +/// Close the connection to the database +void StopDB() +{ + LoginDatabase.Close(); + MySQL::Library_End(); +} + +void SignalHandler(std::weak_ptr<Acore::Asio::IoContext> ioContextRef, boost::system::error_code const& error, int /*signalNumber*/) +{ + if (!error) + { + if (std::shared_ptr<Acore::Asio::IoContext> ioContext = ioContextRef.lock()) + { + ioContext->stop(); + } + } +} + +void KeepDatabaseAliveHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> dbPingTimerRef, int32 dbPingInterval, boost::system::error_code const& error) +{ + if (!error) + { + if (std::shared_ptr<Acore::Asio::DeadlineTimer> dbPingTimer = dbPingTimerRef.lock()) + { + LOG_INFO("server.authserver", "Ping MySQL to keep connection alive"); + LoginDatabase.KeepAlive(); + + dbPingTimer->expires_from_now(boost::posix_time::minutes(dbPingInterval)); + dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, dbPingTimerRef, dbPingInterval, std::placeholders::_1)); + } + } +} + +void BanExpiryHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimerRef, int32 banExpiryCheckInterval, boost::system::error_code const& error) +{ + if (!error) + { + if (std::shared_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimer = banExpiryCheckTimerRef.lock()) + { + LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); + LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS)); + + banExpiryCheckTimer->expires_from_now(boost::posix_time::seconds(banExpiryCheckInterval)); + banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, banExpiryCheckTimerRef, banExpiryCheckInterval, std::placeholders::_1)); + } + } +} + +variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile) +{ + options_description all("Allowed options"); + all.add_options() + ("help,h", "print usage message") + ("version,v", "print version build info") + ("dry-run,d", "Dry run") + ("config,c", value<fs::path>(&configFile)->default_value(fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_REALM_CONFIG))), "use <arg> as configuration file"); + + variables_map variablesMap; + + try + { + store(command_line_parser(argc, argv).options(all).allow_unregistered().run(), variablesMap); + notify(variablesMap); + } + catch (std::exception const& e) + { + std::cerr << e.what() << "\n"; + } + + if (variablesMap.count("help")) + { + std::cout << all << "\n"; + } + else if (variablesMap.count("dry-run")) + { + sConfigMgr->setDryRun(true); + } + + return variablesMap; +} diff --git a/src/server/apps/authserver/PrecompiledHeaders/authserverPCH.h b/src/server/apps/authserver/PrecompiledHeaders/authserverPCH.h new file mode 100644 index 0000000000..e252912e7c --- /dev/null +++ b/src/server/apps/authserver/PrecompiledHeaders/authserverPCH.h @@ -0,0 +1,22 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#include "Common.h" +#include "Config.h" +#include "DatabaseEnv.h" +#include "Log.h" +#include "RealmList.h" diff --git a/src/server/apps/authserver/Server/AuthSession.cpp b/src/server/apps/authserver/Server/AuthSession.cpp new file mode 100644 index 0000000000..ca411fa292 --- /dev/null +++ b/src/server/apps/authserver/Server/AuthSession.cpp @@ -0,0 +1,879 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#include "AuthSession.h" +#include "AES.h" +#include "AuthCodes.h" +#include "Config.h" +#include "CryptoGenerics.h" +#include "CryptoHash.h" +#include "CryptoRandom.h" +#include "DatabaseEnv.h" +#include "Errors.h" +#include "IPLocation.h" +#include "Log.h" +#include "RealmList.h" +#include "SecretMgr.h" +#include "TOTP.h" +#include "Timer.h" +#include "Util.h" +#include "StringConvert.h" +#include <boost/lexical_cast.hpp> +#include <openssl/crypto.h> + +using boost::asio::ip::tcp; + +enum eAuthCmd +{ + AUTH_LOGON_CHALLENGE = 0x00, + AUTH_LOGON_PROOF = 0x01, + AUTH_RECONNECT_CHALLENGE = 0x02, + AUTH_RECONNECT_PROOF = 0x03, + REALM_LIST = 0x10, + XFER_INITIATE = 0x30, + XFER_DATA = 0x31, + XFER_ACCEPT = 0x32, + XFER_RESUME = 0x33, + XFER_CANCEL = 0x34 +}; + +#pragma pack(push, 1) + +typedef struct AUTH_LOGON_CHALLENGE_C +{ + uint8 cmd; + uint8 error; + uint16 size; + uint8 gamename[4]; + uint8 version1; + uint8 version2; + uint8 version3; + uint16 build; + uint8 platform[4]; + uint8 os[4]; + uint8 country[4]; + uint32 timezone_bias; + uint32 ip; + uint8 I_len; + uint8 I[1]; +} sAuthLogonChallenge_C; +static_assert(sizeof(sAuthLogonChallenge_C) == (1 + 1 + 2 + 4 + 1 + 1 + 1 + 2 + 4 + 4 + 4 + 4 + 4 + 1 + 1)); + +typedef struct AUTH_LOGON_PROOF_C +{ + uint8 cmd; + Acore::Crypto::SRP6::EphemeralKey A; + Acore::Crypto::SHA1::Digest clientM; + Acore::Crypto::SHA1::Digest crc_hash; + uint8 number_of_keys; + uint8 securityFlags; +} sAuthLogonProof_C; +static_assert(sizeof(sAuthLogonProof_C) == (1 + 32 + 20 + 20 + 1 + 1)); + +typedef struct AUTH_LOGON_PROOF_S +{ + uint8 cmd; + uint8 error; + Acore::Crypto::SHA1::Digest M2; + uint32 AccountFlags; + uint32 SurveyId; + uint16 LoginFlags; +} sAuthLogonProof_S; +static_assert(sizeof(sAuthLogonProof_S) == (1 + 1 + 20 + 4 + 4 + 2)); + +typedef struct AUTH_LOGON_PROOF_S_OLD +{ + uint8 cmd; + uint8 error; + Acore::Crypto::SHA1::Digest M2; + uint32 unk2; +} sAuthLogonProof_S_Old; +static_assert(sizeof(sAuthLogonProof_S_Old) == (1 + 1 + 20 + 4)); + +typedef struct AUTH_RECONNECT_PROOF_C +{ + uint8 cmd; + uint8 R1[16]; + Acore::Crypto::SHA1::Digest R2, R3; + uint8 number_of_keys; +} sAuthReconnectProof_C; +static_assert(sizeof(sAuthReconnectProof_C) == (1 + 16 + 20 + 20 + 1)); + +#pragma pack(pop) + +std::array<uint8, 16> VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, 0x21, 0x57, 0xFC, 0x37, 0x3F, 0xB3, 0x69, 0xCD, 0xD2, 0xF1 } }; + +#define MAX_ACCEPTED_CHALLENGE_SIZE (sizeof(AUTH_LOGON_CHALLENGE_C) + 16) + +#define AUTH_LOGON_CHALLENGE_INITIAL_SIZE 4 +#define REALM_LIST_PACKET_SIZE 5 + +std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers() +{ + std::unordered_map<uint8, AuthHandler> handlers; + + handlers[AUTH_LOGON_CHALLENGE] = { STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleLogonChallenge }; + handlers[AUTH_LOGON_PROOF] = { STATUS_LOGON_PROOF, sizeof(AUTH_LOGON_PROOF_C), &AuthSession::HandleLogonProof }; + handlers[AUTH_RECONNECT_CHALLENGE] = { STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleReconnectChallenge }; + handlers[AUTH_RECONNECT_PROOF] = { STATUS_RECONNECT_PROOF, sizeof(AUTH_RECONNECT_PROOF_C), &AuthSession::HandleReconnectProof }; + handlers[REALM_LIST] = { STATUS_AUTHED, REALM_LIST_PACKET_SIZE, &AuthSession::HandleRealmList }; + + return handlers; +} + +std::unordered_map<uint8, AuthHandler> const Handlers = AuthSession::InitHandlers(); + +void AccountInfo::LoadResult(Field* fields) +{ + // 0 1 2 3 4 5 + // SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, + // 6 7 + // ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, + // 8 9 + // ipb.unbandate > UNIX_TIMESTAMP() OR ipb.unbandate = ipb.bandate, ipb.unbandate = ipb.bandate, + // 10 + // aa.gmlevel (, more query-specific fields) + // FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 LEFT JOIN ip_banned ipb ON ipb.ip = ? WHERE a.username = ? + + Id = fields[0].Get<uint32>(); + Login = fields[1].Get<std::string>(); + IsLockedToIP = fields[2].Get<bool>(); + LockCountry = fields[3].Get<std::string>(); + LastIP = fields[4].Get<std::string>(); + FailedLogins = fields[5].Get<uint32>(); + IsBanned = fields[6].Get<bool>() || fields[8].Get<bool>(); + IsPermanentlyBanned = fields[7].Get<bool>() || fields[9].Get<bool>(); + SecurityLevel = static_cast<AccountTypes>(fields[10].Get<uint8>()) > SEC_CONSOLE ? SEC_CONSOLE : static_cast<AccountTypes>(fields[10].Get<uint8>()); + + // Use our own uppercasing of the account name instead of using UPPER() in mysql query + // This is how the account was created in the first place and changing it now would result in breaking + // login for all accounts having accented characters in their name + Utf8ToUpperOnlyLatin(Login); +} + +AuthSession::AuthSession(tcp::socket&& socket) : + Socket(std::move(socket)), _status(STATUS_CHALLENGE), _build(0), _expversion(0) { } + +void AuthSession::Start() +{ + std::string ip_address = GetRemoteIpAddress().to_string(); + LOG_TRACE("session", "Accepted connection from {}", ip_address); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO); + stmt->SetData(0, ip_address); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::CheckIpCallback, this, std::placeholders::_1))); +} + +bool AuthSession::Update() +{ + if (!AuthSocket::Update()) + return false; + + _queryProcessor.ProcessReadyCallbacks(); + + return true; +} + +void AuthSession::CheckIpCallback(PreparedQueryResult result) +{ + if (result) + { + bool banned = false; + + do + { + Field* fields = result->Fetch(); + if (fields[0].Get<uint64>() != 0) + banned = true; + + } while (result->NextRow()); + + if (banned) + { + ByteBuffer pkt; + pkt << uint8(AUTH_LOGON_CHALLENGE); + pkt << uint8(0x00); + pkt << uint8(WOW_FAIL_BANNED); + SendPacket(pkt); + LOG_DEBUG("session", "[AuthSession::CheckIpCallback] Banned ip '{}:{}' tries to login!", GetRemoteIpAddress().to_string(), GetRemotePort()); + return; + } + } + + AsyncRead(); +} + +void AuthSession::ReadHandler() +{ + MessageBuffer& packet = GetReadBuffer(); + + while (packet.GetActiveSize()) + { + uint8 cmd = packet.GetReadPointer()[0]; + auto itr = Handlers.find(cmd); + if (itr == Handlers.end()) + { + // well we dont handle this, lets just ignore it + packet.Reset(); + break; + } + + if (_status != itr->second.status) + { + CloseSocket(); + return; + } + + uint16 size = uint16(itr->second.packetSize); + if (packet.GetActiveSize() < size) + break; + + if (cmd == AUTH_LOGON_CHALLENGE || cmd == AUTH_RECONNECT_CHALLENGE) + { + sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(packet.GetReadPointer()); + size += challenge->size; + if (size > MAX_ACCEPTED_CHALLENGE_SIZE) + { + CloseSocket(); + return; + } + } + + if (packet.GetActiveSize() < size) + break; + + if (!(*this.*itr->second.handler)()) + { + CloseSocket(); + return; + } + + packet.ReadCompleted(size); + } + + AsyncRead(); +} + +void AuthSession::SendPacket(ByteBuffer& packet) +{ + if (!IsOpen()) + return; + + if (!packet.empty()) + { + MessageBuffer buffer(packet.size()); + buffer.Write(packet.contents(), packet.size()); + QueuePacket(std::move(buffer)); + } +} + +bool AuthSession::HandleLogonChallenge() +{ + _status = STATUS_CLOSED; + + sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer()); + if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len) + return false; + + std::string login((char const*)challenge->I, challenge->I_len); + LOG_DEBUG("server.authserver", "[AuthChallenge] '{}'", login); + + _build = challenge->build; + _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); + std::array<char, 5> os; + os.fill('\0'); + memcpy(os.data(), challenge->os, sizeof(challenge->os)); + _os = os.data(); + + // Restore string order as its byte order is reversed + std::reverse(_os.begin(), _os.end()); + + _localizationName.resize(4); + for (int i = 0; i < 4; ++i) + _localizationName[i] = challenge->country[4 - i - 1]; + + // Get the account details from the account table + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE); + stmt->SetData(0, GetRemoteIpAddress().to_string()); + stmt->SetData(1, login); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::LogonChallengeCallback, this, std::placeholders::_1))); + return true; +} + +void AuthSession::LogonChallengeCallback(PreparedQueryResult result) +{ + ByteBuffer pkt; + pkt << uint8(AUTH_LOGON_CHALLENGE); + pkt << uint8(0x00); + + if (!result) + { + pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + SendPacket(pkt); + return; + } + + Field* fields = result->Fetch(); + + _accountInfo.LoadResult(fields); + + std::string ipAddress = GetRemoteIpAddress().to_string(); + uint16 port = GetRemotePort(); + + // If the IP is 'locked', check that the player comes indeed from the correct IP address + if (_accountInfo.IsLockedToIP) + { + LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is locked to IP - '{}' is logging in from '{}'", _accountInfo.Login, _accountInfo.LastIP, ipAddress); + if (_accountInfo.LastIP != ipAddress) + { + pkt << uint8(WOW_FAIL_LOCKED_ENFORCED); + SendPacket(pkt); + return; + } + } + else + { + if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ipAddress)) + _ipCountry = location->CountryCode; + + LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is not locked to ip", _accountInfo.Login); + if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00") + LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is not locked to country", _accountInfo.Login); + else if (!_ipCountry.empty()) + { + LOG_DEBUG("server.authserver", "[AuthChallenge] Account '{}' is locked to country: '{}' Player country is '{}'", _accountInfo.Login, _accountInfo.LockCountry, _ipCountry); + if (_ipCountry != _accountInfo.LockCountry) + { + pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK); + SendPacket(pkt); + return; + } + } + } + + // If the account is banned, reject the logon attempt + if (_accountInfo.IsBanned) + { + if (_accountInfo.IsPermanentlyBanned) + { + pkt << uint8(WOW_FAIL_BANNED); + SendPacket(pkt); + LOG_INFO("server.authserver.banned", "'{}:{}' [AuthChallenge] Banned account {} tried to login!", ipAddress, port, _accountInfo.Login); + return; + } + else + { + pkt << uint8(WOW_FAIL_SUSPENDED); + SendPacket(pkt); + LOG_INFO("server.authserver.banned", "'{}:{}' [AuthChallenge] Temporarily banned account {} tried to login!", ipAddress, port, _accountInfo.Login); + return; + } + } + + uint8 securityFlags = 0; + + // Check if a TOTP token is needed + if (!fields[11].IsNull()) + { + securityFlags = 4; + _totpSecret = fields[11].Get<Binary>(); + + if (auto const& secret = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY)) + { + bool success = Acore::Crypto::AEDecrypt<Acore::Crypto::AES>(*_totpSecret, *secret); + if (!success) + { + pkt << uint8(WOW_FAIL_DB_BUSY); + LOG_ERROR("server.authserver", "[AuthChallenge] Account '{}' has invalid ciphertext for TOTP token key stored", _accountInfo.Login); + SendPacket(pkt); + return; + } + } + } + + _srp6.emplace(_accountInfo.Login, + fields[12].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>(), + fields[13].Get<Binary, Acore::Crypto::SRP6::VERIFIER_LENGTH>()); + + // Fill the response packet with the result + if (AuthHelper::IsAcceptedClientBuild(_build)) + { + pkt << uint8(WOW_SUCCESS); + + pkt.append(_srp6->B); + pkt << uint8(1); + pkt.append(_srp6->g); + pkt << uint8(32); + pkt.append(_srp6->N); + pkt.append(_srp6->s); + pkt.append(VersionChallenge.data(), VersionChallenge.size()); + pkt << uint8(securityFlags); // security flags (0x0...0x04) + + if (securityFlags & 0x01) // PIN input + { + pkt << uint32(0); + pkt << uint64(0) << uint64(0); // 16 bytes hash? + } + + if (securityFlags & 0x02) // Matrix input + { + pkt << uint8(0); + pkt << uint8(0); + pkt << uint8(0); + pkt << uint8(0); + pkt << uint64(0); + } + + if (securityFlags & 0x04) // Security token input + pkt << uint8(1); + + LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] account {} is using '{}' locale ({})", + ipAddress, port, _accountInfo.Login, _localizationName, GetLocaleByName(_localizationName)); + + _status = STATUS_LOGON_PROOF; + } + else + pkt << uint8(WOW_FAIL_VERSION_INVALID); + + SendPacket(pkt); +} + +// Logon Proof command handler +bool AuthSession::HandleLogonProof() +{ + LOG_DEBUG("server.authserver", "Entering _HandleLogonProof"); + _status = STATUS_CLOSED; + + // Read the packet + sAuthLogonProof_C* logonProof = reinterpret_cast<sAuthLogonProof_C*>(GetReadBuffer().GetReadPointer()); + + // If the client has no valid version + if (_expversion == NO_VALID_EXP_FLAG) + { + // Check if we have the appropriate patch on the disk + LOG_DEBUG("network", "Client with invalid version, patching is not implemented"); + return false; + } + + // Check if SRP6 results match (password is correct), else send an error + if (Optional<SessionKey> K = _srp6->VerifyChallengeResponse(logonProof->A, logonProof->clientM)) + { + _sessionKey = *K; + // Check auth token + bool tokenSuccess = false; + bool sentToken = (logonProof->securityFlags & 0x04); + if (sentToken && _totpSecret) + { + uint8 size = *(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C)); + std::string token(reinterpret_cast<char*>(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C) + sizeof(size)), size); + GetReadBuffer().ReadCompleted(sizeof(size) + size); + + uint32 incomingToken = *Acore::StringTo<uint32>(token); + tokenSuccess = Acore::Crypto::TOTP::ValidateToken(*_totpSecret, incomingToken); + memset(_totpSecret->data(), 0, _totpSecret->size()); + } + else if (!sentToken && !_totpSecret) + tokenSuccess = true; + + if (!tokenSuccess) + { + ByteBuffer packet; + packet << uint8(AUTH_LOGON_PROOF); + packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + packet << uint16(0); // LoginFlags, 1 has account message + SendPacket(packet); + return true; + } + + if (!VerifyVersion(logonProof->A.data(), logonProof->A.size(), logonProof->crc_hash, false)) + { + ByteBuffer packet; + packet << uint8(AUTH_LOGON_PROOF); + packet << uint8(WOW_FAIL_VERSION_INVALID); + SendPacket(packet); + return true; + } + + LOG_DEBUG("server.authserver", "'{}:{}' User '{}' successfully authenticated", GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login); + + // Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account + // No SQL injection (escaped user name) and IP address as received by socket + + std::string address = sConfigMgr->GetOption<bool>("AllowLoggingIPAddressesInDatabase", true, true) ? GetRemoteIpAddress().to_string() : "0.0.0.0"; + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF); + stmt->SetData(0, _sessionKey); + stmt->SetData(1, address); + stmt->SetData(2, GetLocaleByName(_localizationName)); + stmt->SetData(3, _os); + stmt->SetData(4, _accountInfo.Login); + LoginDatabase.DirectExecute(stmt); + + // Finish SRP6 and send the final result to the client + Acore::Crypto::SHA1::Digest M2 = Acore::Crypto::SRP6::GetSessionVerifier(logonProof->A, logonProof->clientM, _sessionKey); + + ByteBuffer packet; + if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients + { + sAuthLogonProof_S proof; + proof.M2 = M2; + proof.cmd = AUTH_LOGON_PROOF; + proof.error = 0; + proof.AccountFlags = 0x00800000; // 0x01 = GM, 0x08 = Trial, 0x00800000 = Pro pass (arena tournament) + proof.SurveyId = 0; + proof.LoginFlags = 0; // 0x1 = has account message + + packet.resize(sizeof(proof)); + std::memcpy(packet.contents(), &proof, sizeof(proof)); + } + else + { + sAuthLogonProof_S_Old proof; + proof.M2 = M2; + proof.cmd = AUTH_LOGON_PROOF; + proof.error = 0; + proof.unk2 = 0x00; + + packet.resize(sizeof(proof)); + std::memcpy(packet.contents(), &proof, sizeof(proof)); + } + + SendPacket(packet); + _status = STATUS_AUTHED; + } + else + { + ByteBuffer packet; + packet << uint8(AUTH_LOGON_PROOF); + packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + packet << uint16(0); // LoginFlags, 1 has account message + SendPacket(packet); + + LOG_INFO("server.authserver.hack", "'{}:{}' [AuthChallenge] account {} tried to login with invalid password!", + GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login); + + uint32 MaxWrongPassCount = sConfigMgr->GetOption<int32>("WrongPass.MaxCount", 0); + + // We can not include the failed account login hook. However, this is a workaround to still log this. + if (sConfigMgr->GetOption<bool>("WrongPass.Logging", false)) + { + LoginDatabasePreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING); + logstmt->SetData(0, _accountInfo.Id); + logstmt->SetData(1, GetRemoteIpAddress().to_string()); + logstmt->SetData(2, "Login to WoW Failed - Incorrect Password"); + + LoginDatabase.Execute(logstmt); + } + + if (MaxWrongPassCount > 0) + { + //Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS); + stmt->SetData(0, _accountInfo.Login); + LoginDatabase.Execute(stmt); + + if (++_accountInfo.FailedLogins >= MaxWrongPassCount) + { + uint32 WrongPassBanTime = sConfigMgr->GetOption<int32>("WrongPass.BanTime", 600); + bool WrongPassBanType = sConfigMgr->GetOption<bool>("WrongPass.BanType", false); + + if (WrongPassBanType) + { + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED); + stmt->SetData(0, _accountInfo.Id); + stmt->SetData(1, WrongPassBanTime); + LoginDatabase.Execute(stmt); + + LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] account {} got banned for '{}' seconds because it failed to authenticate '{}' times", + GetRemoteIpAddress().to_string(), GetRemotePort(), _accountInfo.Login, WrongPassBanTime, _accountInfo.FailedLogins); + } + else + { + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED); + stmt->SetData(0, GetRemoteIpAddress().to_string()); + stmt->SetData(1, WrongPassBanTime); + LoginDatabase.Execute(stmt); + + LOG_DEBUG("server.authserver", "'{}:{}' [AuthChallenge] IP got banned for '{}' seconds because account {} failed to authenticate '{}' times", + GetRemoteIpAddress().to_string(), GetRemotePort(), WrongPassBanTime, _accountInfo.Login, _accountInfo.FailedLogins); + } + } + } + } + + return true; +} + +bool AuthSession::HandleReconnectChallenge() +{ + _status = STATUS_CLOSED; + + sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer()); + if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len) + return false; + + std::string login((char const*)challenge->I, challenge->I_len); + LOG_DEBUG("server.authserver", "[ReconnectChallenge] '{}'", login); + + _build = challenge->build; + _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); + + std::array<char, 5> os; + os.fill('\0'); + memcpy(os.data(), challenge->os, sizeof(challenge->os)); + _os = os.data(); + + // Restore string order as its byte order is reversed + std::reverse(_os.begin(), _os.end()); + + _localizationName.resize(4); + for (int i = 0; i < 4; ++i) + _localizationName[i] = challenge->country[4 - i - 1]; + + // Get the account details from the account table + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE); + stmt->SetData(0, GetRemoteIpAddress().to_string()); + stmt->SetData(1, login); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::ReconnectChallengeCallback, this, std::placeholders::_1))); + return true; +} + +void AuthSession::ReconnectChallengeCallback(PreparedQueryResult result) +{ + ByteBuffer pkt; + pkt << uint8(AUTH_RECONNECT_CHALLENGE); + + if (!result) + { + pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + SendPacket(pkt); + return; + } + + Field* fields = result->Fetch(); + + _accountInfo.LoadResult(fields); + _sessionKey = fields[11].Get<Binary, SESSION_KEY_LENGTH>(); + Acore::Crypto::GetRandomBytes(_reconnectProof); + _status = STATUS_RECONNECT_PROOF; + + pkt << uint8(WOW_SUCCESS); + pkt.append(_reconnectProof); + pkt.append(VersionChallenge.data(), VersionChallenge.size()); + + SendPacket(pkt); +} + +bool AuthSession::HandleReconnectProof() +{ + LOG_DEBUG("server.authserver", "Entering _HandleReconnectProof"); + _status = STATUS_CLOSED; + + sAuthReconnectProof_C* reconnectProof = reinterpret_cast<sAuthReconnectProof_C*>(GetReadBuffer().GetReadPointer()); + + if (_accountInfo.Login.empty()) + return false; + + BigNumber t1; + t1.SetBinary(reconnectProof->R1, 16); + + Acore::Crypto::SHA1 sha; + sha.UpdateData(_accountInfo.Login); + sha.UpdateData(t1.ToByteArray<16>()); + sha.UpdateData(_reconnectProof); + sha.UpdateData(_sessionKey); + sha.Finalize(); + + if (sha.GetDigest() == reconnectProof->R2) + { + if (!VerifyVersion(reconnectProof->R1, sizeof(reconnectProof->R1), reconnectProof->R3, true)) + { + ByteBuffer packet; + packet << uint8(AUTH_RECONNECT_PROOF); + packet << uint8(WOW_FAIL_VERSION_INVALID); + SendPacket(packet); + return true; + } + + // Sending response + ByteBuffer pkt; + pkt << uint8(AUTH_RECONNECT_PROOF); + pkt << uint8(WOW_SUCCESS); + pkt << uint16(0); // LoginFlags, 1 has account message + SendPacket(pkt); + _status = STATUS_AUTHED; + return true; + } + else + { + LOG_ERROR("server.authserver.hack", "'{}:{}' [ERROR] user {} tried to login, but session is invalid.", GetRemoteIpAddress().to_string(), + GetRemotePort(), _accountInfo.Login); + return false; + } +} + +bool AuthSession::HandleRealmList() +{ + LOG_DEBUG("server.authserver", "Entering _HandleRealmList"); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS); + stmt->SetData(0, _accountInfo.Id); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::RealmListCallback, this, std::placeholders::_1))); + _status = STATUS_WAITING_FOR_REALM_LIST; + return true; +} + +void AuthSession::RealmListCallback(PreparedQueryResult result) +{ + std::map<uint32, uint8> characterCounts; + if (result) + { + do + { + Field* fields = result->Fetch(); + characterCounts[fields[0].Get<uint32>()] = fields[1].Get<uint8>(); + } while (result->NextRow()); + } + + // Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm) + ByteBuffer pkt; + + size_t RealmListSize = 0; + for (auto const& [realmHandle, realm] : sRealmList->GetRealms()) + { + // don't work with realms which not compatible with the client + bool okBuild = ((_expversion & POST_BC_EXP_FLAG) && realm.Build == _build) || ((_expversion & PRE_BC_EXP_FLAG) && !AuthHelper::IsPreBCAcceptedClientBuild(realm.Build)); + + // No SQL injection. id of realm is controlled by the database. + uint32 flag = realm.Flags; + RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(realm.Build); + if (!okBuild) + { + if (!buildInfo) + continue; + + flag |= REALM_FLAG_OFFLINE | REALM_FLAG_SPECIFYBUILD; // tell the client what build the realm is for + } + + if (!buildInfo) + flag &= ~REALM_FLAG_SPECIFYBUILD; + + std::string name = realm.Name; + if (_expversion & PRE_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD) + { + std::ostringstream ss; + ss << name << " (" << buildInfo->MajorVersion << '.' << buildInfo->MinorVersion << '.' << buildInfo->BugfixVersion << ')'; + name = ss.str(); + } + + uint8 lock = (realm.AllowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0; + + pkt << uint8(realm.Type); // realm type + if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients + pkt << uint8(lock); // if 1, then realm locked + + pkt << uint8(flag); // RealmFlags + pkt << name; + pkt << boost::lexical_cast<std::string>(realm.GetAddressForClient(GetRemoteIpAddress())); + pkt << float(realm.PopulationLevel); + pkt << uint8(characterCounts[realm.Id.Realm]); + pkt << uint8(realm.Timezone); // realm category + + if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients + pkt << uint8(realm.Id.Realm); + else + pkt << uint8(0x0); // 1.12.1 and 1.12.2 clients + + if (_expversion & POST_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD) + { + pkt << uint8(buildInfo->MajorVersion); + pkt << uint8(buildInfo->MinorVersion); + pkt << uint8(buildInfo->BugfixVersion); + pkt << uint16(buildInfo->Build); + } + + ++RealmListSize; + } + + if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients + { + pkt << uint8(0x10); + pkt << uint8(0x00); + } + else // 1.12.1 and 1.12.2 clients + { + pkt << uint8(0x00); + pkt << uint8(0x02); + } + + // make a ByteBuffer which stores the RealmList's size + ByteBuffer RealmListSizeBuffer; + RealmListSizeBuffer << uint32(0); + + if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients + RealmListSizeBuffer << uint16(RealmListSize); + else + RealmListSizeBuffer << uint32(RealmListSize); + + ByteBuffer hdr; + hdr << uint8(REALM_LIST); + hdr << uint16(pkt.size() + RealmListSizeBuffer.size()); + hdr.append(RealmListSizeBuffer); // append RealmList's size buffer + hdr.append(pkt); // append realms in the realmlist + SendPacket(hdr); + + _status = STATUS_AUTHED; +} + +bool AuthSession::VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect) +{ + if (!sConfigMgr->GetOption<bool>("StrictVersionCheck", false)) + return true; + + Acore::Crypto::SHA1::Digest zeros{}; + Acore::Crypto::SHA1::Digest const* versionHash{ nullptr }; + + if (!isReconnect) + { + RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(_build); + if (!buildInfo) + return false; + + if (_os == "Win") + versionHash = &buildInfo->WindowsHash; + else if (_os == "OSX") + versionHash = &buildInfo->MacHash; + + if (!versionHash) + return false; + + if (zeros == *versionHash) + return true; // not filled serverside + } + else + versionHash = &zeros; + + Acore::Crypto::SHA1 version; + version.UpdateData(a, aLength); + version.UpdateData(*versionHash); + version.Finalize(); + + return (versionProof == version.GetDigest()); +} diff --git a/src/server/apps/authserver/Server/AuthSession.h b/src/server/apps/authserver/Server/AuthSession.h new file mode 100644 index 0000000000..4a333c8168 --- /dev/null +++ b/src/server/apps/authserver/Server/AuthSession.h @@ -0,0 +1,121 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __AUTHSESSION_H__ +#define __AUTHSESSION_H__ + +#include "AsyncCallbackProcessor.h" +#include "BigNumber.h" +#include "ByteBuffer.h" +#include "Common.h" +#include "CryptoHash.h" +#include "Optional.h" +#include "QueryResult.h" +#include "SRP6.h" +#include "Socket.h" +#include <boost/asio/ip/tcp.hpp> +#include <memory> + +using boost::asio::ip::tcp; + +class Field; +struct AuthHandler; + +enum AuthStatus +{ + STATUS_CHALLENGE = 0, + STATUS_LOGON_PROOF, + STATUS_RECONNECT_PROOF, + STATUS_AUTHED, + STATUS_WAITING_FOR_REALM_LIST, + STATUS_CLOSED +}; + +struct AccountInfo +{ + void LoadResult(Field* fields); + + uint32 Id = 0; + std::string Login; + bool IsLockedToIP = false; + std::string LockCountry; + std::string LastIP; + uint32 FailedLogins = 0; + bool IsBanned = false; + bool IsPermanentlyBanned = false; + AccountTypes SecurityLevel = SEC_PLAYER; +}; + +class AuthSession : public Socket<AuthSession> +{ + typedef Socket<AuthSession> AuthSocket; + +public: + static std::unordered_map<uint8, AuthHandler> InitHandlers(); + + AuthSession(tcp::socket&& socket); + + void Start() override; + bool Update() override; + + void SendPacket(ByteBuffer& packet); + +protected: + void ReadHandler() override; + +private: + bool HandleLogonChallenge(); + bool HandleLogonProof(); + bool HandleReconnectChallenge(); + bool HandleReconnectProof(); + bool HandleRealmList(); + + void CheckIpCallback(PreparedQueryResult result); + void LogonChallengeCallback(PreparedQueryResult result); + void ReconnectChallengeCallback(PreparedQueryResult result); + void RealmListCallback(PreparedQueryResult result); + + bool VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect); + + Optional<Acore::Crypto::SRP6> _srp6; + SessionKey _sessionKey = {}; + std::array<uint8, 16> _reconnectProof = {}; + + AuthStatus _status; + AccountInfo _accountInfo; + Optional<std::vector<uint8>> _totpSecret; + std::string _localizationName; + std::string _os; + std::string _ipCountry; + uint16 _build; + uint8 _expversion; + + QueryCallbackProcessor _queryProcessor; +}; + +#pragma pack(push, 1) + +struct AuthHandler +{ + AuthStatus status; + size_t packetSize; + bool (AuthSession::* handler)(); +}; + +#pragma pack(pop) + +#endif diff --git a/src/server/apps/authserver/Server/AuthSocketMgr.h b/src/server/apps/authserver/Server/AuthSocketMgr.h new file mode 100644 index 0000000000..53f46ff92a --- /dev/null +++ b/src/server/apps/authserver/Server/AuthSocketMgr.h @@ -0,0 +1,58 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef AuthSocketMgr_h__ +#define AuthSocketMgr_h__ + +#include "AuthSession.h" +#include "SocketMgr.h" + +class AuthSocketMgr : public SocketMgr<AuthSession> +{ + typedef SocketMgr<AuthSession> BaseSocketMgr; + +public: + static AuthSocketMgr& Instance() + { + static AuthSocketMgr instance; + return instance; + } + + bool StartNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount = 1) override + { + if (!BaseSocketMgr::StartNetwork(ioContext, bindIp, port, threadCount)) + return false; + + _acceptor->AsyncAcceptWithCallback<&AuthSocketMgr::OnSocketAccept>(); + return true; + } + +protected: + NetworkThread<AuthSession>* CreateThreads() const override + { + return new NetworkThread<AuthSession>[1]; + } + + static void OnSocketAccept(tcp::socket&& sock, uint32 threadIndex) + { + Instance().OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex); + } +}; + +#define sAuthSocketMgr AuthSocketMgr::Instance() + +#endif // AuthSocketMgr_h__ diff --git a/src/server/apps/authserver/authserver.conf.dist b/src/server/apps/authserver/authserver.conf.dist new file mode 100644 index 0000000000..923074c332 --- /dev/null +++ b/src/server/apps/authserver/authserver.conf.dist @@ -0,0 +1,423 @@ +############################################### +# AzerothCore Auth Server configuration file # +############################################### +[authserver] + +################################################################################################### +# SECTION INDEX +# +# EXAMPLE CONFIG +# AUTH SERVER SETTINGS +# MYSQL SETTINGS +# CRYPTOGRAPHY +# UPDATE SETTINGS +# LOGGING SYSTEM SETTINGS +# +################################################################################################### + +################################################################################################### +# EXAMPLE CONFIG +# +# Variable +# Description: Brief description what the variable is doing. +# Important: Annotation for important things about this variable. +# Example: "Example, i.e. if the value is a string" +# Default: 10 - (Enabled|Comment|Variable name in case of grouped config options) +# 0 - (Disabled|Comment|Variable name in case of grouped config options) +# +# Note to developers: +# - Copy this example to keep the formatting. +# - Line breaks should be at column 100. +################################################################################################### + +################################################################################################### +# AUTH SERVER SETTINGS +# +# LogsDir +# Description: Logs directory setting. +# Important: LogsDir needs to be quoted, as the string might contain space characters. +# Logs directory must exists, or log file creation will be disabled. +# Example: "/home/youruser/azerothcore/logs" +# Default: "" - (Log files will be stored in the current path) + +LogsDir = "" + +# +# MaxPingTime +# Description: Time (in minutes) between database pings. +# Default: 30 + +MaxPingTime = 30 + +# +# RealmServerPort +# Description: TCP port to reach the auth server. +# Default: 3724 + +RealmServerPort = 3724 + +# +# +# BindIP +# Description: Bind auth server to IP/hostname +# Default: "0.0.0.0" - (Bind to all IPs on the system) + +BindIP = "0.0.0.0" + +# +# PidFile +# Description: Auth server PID file. +# Example: "./authserver.pid" - (Enabled) +# Default: "" - (Disabled) + +PidFile = "" + +# +# UseProcessors +# Description: Processors mask for Windows and Linux based multi-processor systems. +# Example: For a computer with 3 CPUs: +# 1 - 1st CPU only +# 2 - 2nd CPU only +# 4 - 3rd CPU only +# 6 - 2nd + 3rd CPUs, because "2 | 4" -> 6 +# Default: 0 - (Selected by OS) +# 1+ - (Bit mask value of selected processors) + +UseProcessors = 0 + +# +# ProcessPriority +# Description: Process priority setting for Windows and Linux based systems. +# Details: On Linux, a nice value of -15 is used. (requires superuser). On Windows, process is set to HIGH class. +# Default: 0 - (Normal) +# 1 - (High) + +ProcessPriority = 0 + +# +# RealmsStateUpdateDelay +# Description: Time (in seconds) between realm list updates. +# Default: 20 - (Enabled) +# 0 - (Disabled) + +RealmsStateUpdateDelay = 20 + +# +# WrongPass.MaxCount +# Description: Number of login attempts with wrong password before the account or IP will be +# banned. +# Default: 0 - (Disabled) +# 1+ - (Enabled) + +WrongPass.MaxCount = 0 + +# +# WrongPass.BanTime +# Description: Time (in seconds) for banning account or IP for invalid login attempts. +# Default: 600 - (10 minutes) +# 0 - (Permanent ban) + +WrongPass.BanTime = 600 + +# +# WrongPass.BanType +# Description: Ban type for invalid login attempts. +# Default: 0 - (Ban IP) +# 1 - (Ban Account) + +WrongPass.BanType = 0 + +# +# WrongPass.Logging +# Description: Additionally log attempted wrong password logging +# Default: 0 - (Disabled) +# 1 - (Enabled) + +WrongPass.Logging = 0 + +# +# BanExpiryCheckInterval +# Description: Time (in seconds) between checks for expired bans +# Default: 60 +# + +BanExpiryCheckInterval = 60 + +# +# StrictVersionCheck +# Description: Prevent modified clients from connecting +# Default: 0 - (Disabled) +# 1 - (Enabled) +# + +StrictVersionCheck = 0 + +# +# SourceDirectory +# Description: The path to your AzerothCore source directory. +# If the path is left empty, the built-in CMAKE_SOURCE_DIR is used. +# Example: "../AzerothCore" +# Default: "" +# + +SourceDirectory = "" + +# +# MySQLExecutable +# Description: The path to your MySQL CLI binary. +# If the path is left empty, built-in path from cmake is used. +# Example: "C:/Program Files/MariaDB 10.5/bin/mysql.exe" +# "C:/Program Files/MySQL/MySQL Server 5.6/bin/mysql.exe" +# "mysql.exe" +# "/usr/bin/mysql" +# Default: "" +# + +MySQLExecutable = "" + +# +# IPLocationFile +# Description: The path to your IP2Location database CSV file. +# Example: "C:/acore/IP2LOCATION-LITE-DB1.CSV" +# "/home/acore/IP2LOCATION-LITE-DB1.CSV" +# Default: "" - (Disabled) + +IPLocationFile = "" + +# +# AllowLoggingIPAddressesInDatabase +# Description: Specifies if IP addresses can be logged to the database +# Default: 1 - (Enabled) +# 0 - (Disabled) +# + +AllowLoggingIPAddressesInDatabase = 1 + +# +################################################################################################### + +################################################################################################### +# MYSQL SETTINGS +# +# LoginDatabaseInfo +# Description: Database connection settings for the realm server. +# Example: "hostname;port;username;password;database" +# ".;somenumber;username;password;database" - (Use named pipes on Windows +# "enable-named-pipe" to [mysqld] +# section my.ini) +# ".;/path/to/unix_socket;username;password;database" - (use Unix sockets on +# Unix/Linux) +# Default: "127.0.0.1;3306;acore;acore;acore_auth" + +LoginDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_auth" + +# +# Database.Reconnect.Seconds +# Database.Reconnect.Attempts +# +# Description: How many seconds between every reconnection attempt +# and how many attempts will be performed in total +# Default: 20 attempts every 15 seconds +# + +Database.Reconnect.Seconds = 15 +Database.Reconnect.Attempts = 20 + +# +# LoginDatabase.WorkerThreads +# Description: The amount of worker threads spawned to handle asynchronous (delayed) MySQL +# statements. Each worker thread is mirrored with its own connection to the +# Default: 1 + +LoginDatabase.WorkerThreads = 1 + +# +# LoginDatabase.SynchThreads +# Description: The amount of MySQL connections spawned to handle. +# Default: 1 - (LoginDatabase.WorkerThreads) +# + +LoginDatabase.SynchThreads = 1 + +# +################################################################################################### + +################################################################################################### +# CRYPTOGRAPHY + +# +# EnableTOTP +# Description: Check if a TOTP token is needed on account login +# +# Default: 0 - (Disabled) +# 1 - (Enabled) + +EnableTOTP = 0 + +# TOTPMasterSecret +# Description: The master key used to encrypt TOTP secrets for database storage. +# If you want to change this, uncomment TOTPOldMasterSecret, then copy +# your old secret there and startup authserver once. Afterwards, you can re- +# comment that line and get rid of your old secret. +# +# Default: <blank> - (Store TOTP secrets unencrypted) +# Example: 000102030405060708090A0B0C0D0E0F + +TOTPMasterSecret = +# TOTPOldMasterSecret = + +# +################################################################################################### + +################################################################################################### +# UPDATE SETTINGS +# +# Updates.EnableDatabases +# Description: A mask that describes which databases shall be updated. +# +# Following flags are available +# DATABASE_LOGIN = 1, // Auth database +# +# Default: 0 - (All Disabled) +# 1 - (All Enabled) + +Updates.EnableDatabases = 1 + +# +# Updates.AutoSetup +# Description: Auto populate empty databases. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.AutoSetup = 1 + +# +# Updates.Redundancy +# Description: Perform data redundancy checks through hashing +# to detect changes on sql updates and reapply it. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.Redundancy = 1 + +# +# Updates.ArchivedRedundancy +# Description: Check hashes of archived updates (slows down startup). +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Updates.ArchivedRedundancy = 0 + +# +# Updates.AllowRehash +# Description: Inserts the current file hash in the database if it is left empty. +# Useful if you want to mark a file as applied but you don't know its hash. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.AllowRehash = 1 + +# +# Updates.CleanDeadRefMaxCount +# Description: Cleans dead/ orphaned references that occur if an update was removed or renamed and edited in one step. +# It only starts the clean up if the count of the missing updates is below or equal the Updates.CleanDeadRefMaxCount value. +# This way prevents erasing of the update history due to wrong source directory state (maybe wrong branch or bad revision). +# Disable this if you want to know if the database is in a possible "dirty state". +# Default: 3 - (Enabled) +# 0 - (Disabled) +# -1 - (Enabled - unlimited) + +Updates.CleanDeadRefMaxCount = 3 +################################################################################################### + +################################################################################################### +# +# LOGGING SYSTEM SETTINGS +# +# Appender config values: Given an appender "name" +# Appender.name +# Description: Defines 'where to log' +# Format: Type,LogLevel,Flags,optional1,optional2,optional3 +# +# Type +# 0 - (None) +# 1 - (Console) +# 2 - (File) +# 3 - (DB) +# +# LogLevel +# 0 - (Disabled) +# 1 - (Fatal) +# 2 - (Error) +# 3 - (Warning) +# 4 - (Info) +# 5 - (Debug) +# 6 - (Trace) +# +# Flags: +# 0 - None +# 1 - Prefix Timestamp to the text +# 2 - Prefix Log Level to the text +# 4 - Prefix Log Filter type to the text +# 8 - Append timestamp to the log file name. Format: YYYY-MM-DD_HH-MM-SS (Only used with Type = 2) +# 16 - Make a backup of existing file before overwrite (Only used with Mode = w) +# +# Colors (read as optional1 if Type = Console) +# Format: "fatal error warn info debug trace" +# 0 - BLACK +# 1 - RED +# 2 - GREEN +# 3 - BROWN +# 4 - BLUE +# 5 - MAGENTA +# 6 - CYAN +# 7 - GREY +# 8 - YELLOW +# 9 - LRED +# 10 - LGREEN +# 11 - LBLUE +# 12 - LMAGENTA +# 13 - LCYAN +# 14 - WHITE +# Example: "1 9 3 6 5 8" +# +# File: Name of the file (read as optional1 if Type = File) +# Allows to use one "%s" to create dynamic files +# +# Mode: Mode to open the file (read as optional2 if Type = File) +# a - (Append) +# w - (Overwrite) +# +# MaxFileSize: Maximum file size of the log file before creating a new log file +# (read as optional3 if Type = File) +# Size is measured in bytes expressed in a 64-bit unsigned integer. +# Maximum value is 4294967295 (4 GB). Leave blank for no limit. +# NOTE: Does not work with dynamic filenames. +# Example: 536870912 (512 MB) +# + +Appender.Console=1,5,0,"1 9 3 6 5 8" +Appender.Auth=2,5,0,Auth.log,w + +# Logger config values: Given a logger "name" +# Logger.name +# Description: Defines 'What to log' +# Format: LogLevel,AppenderList +# +# LogLevel +# 0 - (Disabled) +# 1 - (Fatal) +# 2 - (Error) +# 3 - (Warning) +# 4 - (Info) +# 5 - (Debug) +# 6 - (Trace) +# +# AppenderList: List of appenders linked to logger +# (Using spaces as separator). +# + +Logger.root=4,Console Auth + +# +################################################################################################### diff --git a/src/server/apps/authserver/authserver.ico b/src/server/apps/authserver/authserver.ico Binary files differnew file mode 100644 index 0000000000..2d500278c9 --- /dev/null +++ b/src/server/apps/authserver/authserver.ico diff --git a/src/server/apps/authserver/authserver.rc b/src/server/apps/authserver/authserver.rc new file mode 100644 index 0000000000..0b30740e63 --- /dev/null +++ b/src/server/apps/authserver/authserver.rc @@ -0,0 +1,93 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#include "resource.h" +#include "revision.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "windows.h" //"afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APPICON ICON "authserver.ico" + +///////////////////////////////////////////////////////////////////////////// +// Neutre (Par défaut système) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUSD) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION + +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + +#ifndef _DEBUG + FILEFLAGS 0 +#else + #define VER_PRERELEASE VS_FF_PRERELEASE + #define VER_PRIVATEBUILD VS_FF_PRIVATEBUILD + #define VER_DEBUG 0 + FILEFLAGS (VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG) +#endif + +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP + +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080004b0" + BEGIN + VALUE "CompanyName", VER_COMPANYNAME_STR + VALUE "FileDescription", "Authentication Server Daemon" + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", "authserver" + VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR + VALUE "OriginalFilename", "authserver.exe" + VALUE "ProductName", "Authentication Server" + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x800, 1200 + END +END +#endif diff --git a/src/server/apps/authserver/resource.h b/src/server/apps/authserver/resource.h new file mode 100644 index 0000000000..5415d15d80 --- /dev/null +++ b/src/server/apps/authserver/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by SunwellCore.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/server/apps/worldserver/ACSoap/ACSoap.cpp b/src/server/apps/worldserver/ACSoap/ACSoap.cpp new file mode 100644 index 0000000000..a63542ec25 --- /dev/null +++ b/src/server/apps/worldserver/ACSoap/ACSoap.cpp @@ -0,0 +1,149 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#include "ACSoap.h" +#include "AccountMgr.h" +#include "Log.h" +#include "World.h" +#include "soapStub.h" + +void ACSoapThread(const std::string& host, uint16 port) +{ + struct soap soap; + soap_init(&soap); + soap_set_imode(&soap, SOAP_C_UTFSTRING); + soap_set_omode(&soap, SOAP_C_UTFSTRING); + + // check every 3 seconds if world ended + soap.accept_timeout = 3; + soap.recv_timeout = 5; + soap.send_timeout = 5; + + if (!soap_valid_socket(soap_bind(&soap, host.c_str(), port, 100))) + { + LOG_ERROR("network.soap", "ACSoap: couldn't bind to {}:{}", host, port); + exit(-1); + } + + LOG_INFO("network.soap", "ACSoap: bound to http://{}:{}", host, port); + + while (!World::IsStopped()) + { + if (!soap_valid_socket(soap_accept(&soap))) + continue; // ran into an accept timeout + + LOG_DEBUG("network.soap", "ACSoap: accepted connection from IP={}.{}.{}.{}", (int)(soap.ip >> 24) & 0xFF, (int)(soap.ip >> 16) & 0xFF, (int)(soap.ip >> 8) & 0xFF, (int)soap.ip & 0xFF); + struct soap* thread_soap = soap_copy(&soap);// make a safe copy + + process_message(thread_soap); + } + + soap_destroy(&soap); + soap_end(&soap); + soap_done(&soap); +} + +void process_message(struct soap* soap_message) +{ + LOG_TRACE("network.soap", "SOAPWorkingThread::process_message"); + + soap_serve(soap_message); + soap_destroy(soap_message); // dealloc C++ data + soap_end(soap_message); // dealloc data and clean up + soap_free(soap_message); // detach soap struct and fre up the memory +} + +/* + Code used for generating stubs: + int ns1__executeCommand(char* command, char** result); +*/ +int ns1__executeCommand(soap* soap, char* command, char** result) +{ + // security check + if (!soap->userid || !soap->passwd) + { + LOG_DEBUG("network.soap", "ACSoap: Client didn't provide login information"); + return 401; + } + + uint32 accountId = AccountMgr::GetId(soap->userid); + if (!accountId) + { + LOG_DEBUG("network", "ACSoap: Client used invalid username '{}'", soap->userid); + return 401; + } + + if (!AccountMgr::CheckPassword(accountId, soap->passwd)) + { + LOG_DEBUG("network.soap", "ACSoap: invalid password for account '{}'", soap->userid); + return 401; + } + + if (AccountMgr::GetSecurity(accountId) < SEC_ADMINISTRATOR) + { + LOG_DEBUG("network.soap", "ACSoap: {}'s gmlevel is too low", soap->userid); + return 403; + } + + if (!command || !*command) + return soap_sender_fault(soap, "Command can not be empty", "The supplied command was an empty string"); + + LOG_DEBUG("network.soap", "ACSoap: got command '{}'", command); + SOAPCommand connection; + + // commands are executed in the world thread. We have to wait for them to be completed + { + // CliCommandHolder will be deleted from world, accessing after queueing is NOT save + CliCommandHolder* cmd = new CliCommandHolder(&connection, command, &SOAPCommand::print, &SOAPCommand::commandFinished); + sWorld->QueueCliCommand(cmd); + } + + // Wait until the command has finished executing + connection.finishedPromise.get_future().wait(); + + // The command has finished executing already + char* printBuffer = soap_strdup(soap, connection.m_printBuffer.c_str()); + if (connection.hasCommandSucceeded()) + { + *result = printBuffer; + return SOAP_OK; + } + else + return soap_sender_fault(soap, printBuffer, printBuffer); +} + +void SOAPCommand::commandFinished(void* soapconnection, bool success) +{ + SOAPCommand* con = (SOAPCommand*)soapconnection; + con->setCommandSuccess(success); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Namespace Definition Table +// +//////////////////////////////////////////////////////////////////////////////// + +struct Namespace namespaces[] = +{ + { "SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", nullptr, nullptr }, // must be first + { "SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", nullptr, nullptr }, // must be second + { "xsi", "http://www.w3.org/1999/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", nullptr }, + { "xsd", "http://www.w3.org/1999/XMLSchema", "http://www.w3.org/*/XMLSchema", nullptr }, + { "ns1", "urn:AC", nullptr, nullptr }, // "ns1" namespace prefix + { nullptr, nullptr, nullptr, nullptr } +}; diff --git a/src/server/apps/worldserver/ACSoap/ACSoap.h b/src/server/apps/worldserver/ACSoap/ACSoap.h new file mode 100644 index 0000000000..fd62012b58 --- /dev/null +++ b/src/server/apps/worldserver/ACSoap/ACSoap.h @@ -0,0 +1,64 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _ACSOAP_H +#define _ACSOAP_H + +#include "Define.h" +#include <future> +#include <mutex> + +void process_message(struct soap* soap_message); +void ACSoapThread(const std::string& host, uint16 port); + +class SOAPCommand +{ +public: + SOAPCommand() : + m_success(false) { } + + ~SOAPCommand() { } + + void appendToPrintBuffer(std::string_view msg) + { + m_printBuffer += msg; + } + + void setCommandSuccess(bool val) + { + m_success = val; + finishedPromise.set_value(); + } + + bool hasCommandSucceeded() const + { + return m_success; + } + + static void print(void* callbackArg, std::string_view msg) + { + ((SOAPCommand*)callbackArg)->appendToPrintBuffer(msg); + } + + static void commandFinished(void* callbackArg, bool success); + + bool m_success; + std::string m_printBuffer; + std::promise<void> finishedPromise; +}; + +#endif diff --git a/src/server/apps/worldserver/CommandLine/CliRunnable.cpp b/src/server/apps/worldserver/CommandLine/CliRunnable.cpp new file mode 100644 index 0000000000..c2cc94edbc --- /dev/null +++ b/src/server/apps/worldserver/CommandLine/CliRunnable.cpp @@ -0,0 +1,194 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +/// \addtogroup Acored +/// @{ +/// \file + +#include "CliRunnable.h" +#include "Config.h" +#include "Errors.h" +#include "ObjectMgr.h" +#include "World.h" +#include <fmt/core.h> + +#if AC_PLATFORM != AC_PLATFORM_WINDOWS +#include "Chat.h" +#include "ChatCommand.h" +#include <cstring> +#include <readline/history.h> +#include <readline/readline.h> +#endif + +static constexpr char CLI_PREFIX[] = "AC> "; + +static inline void PrintCliPrefix() +{ + fmt::print(CLI_PREFIX); +} + +#if AC_PLATFORM != AC_PLATFORM_WINDOWS +namespace Acore::Impl::Readline +{ + static std::vector<std::string> vec; + char* cli_unpack_vector(char const*, int state) + { + static size_t i=0; + if (!state) + i = 0; + if (i < vec.size()) + return strdup(vec[i++].c_str()); + else + return nullptr; + } + + char** cli_completion(char const* text, int /*start*/, int /*end*/) + { + ::rl_attempted_completion_over = 1; + vec = Acore::ChatCommands::GetAutoCompletionsFor(CliHandler(nullptr,nullptr), text); + return ::rl_completion_matches(text, &cli_unpack_vector); + } + + int cli_hook_func() + { + if (World::IsStopped()) + ::rl_done = 1; + return 0; + } +} +#endif + +void utf8print(void* /*arg*/, std::string_view str) +{ +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + fmt::print(str); +#else +{ + fmt::print(str); + fflush(stdout); +} +#endif +} + +void commandFinished(void*, bool /*success*/) +{ + PrintCliPrefix(); + fflush(stdout); +} + +#ifdef linux +// Non-blocking keypress detector, when return pressed, return 1, else always return 0 +int kb_hit_return() +{ + struct timeval tv; + fd_set fds; + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + select(STDIN_FILENO+1, &fds, nullptr, nullptr, &tv); + return FD_ISSET(STDIN_FILENO, &fds); +} +#endif + +/// %Thread start +void CliThread() +{ +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + // print this here the first time + // later it will be printed after command queue updates + PrintCliPrefix(); +#else + ::rl_attempted_completion_function = &Acore::Impl::Readline::cli_completion; + { + static char BLANK = '\0'; + ::rl_completer_word_break_characters = &BLANK; + } + ::rl_event_hook = &Acore::Impl::Readline::cli_hook_func; +#endif + + if (sConfigMgr->GetOption<bool>("BeepAtStart", true)) + printf("\a"); // \a = Alert + +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + if (sConfigMgr->GetOption<bool>("FlashAtStart", true)) + { + FLASHWINFO fInfo; + fInfo.cbSize = sizeof(FLASHWINFO); + fInfo.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG; + fInfo.hwnd = GetConsoleWindow(); + fInfo.uCount = 0; + fInfo.dwTimeout = 0; + FlashWindowEx(&fInfo); + } +#endif + + ///- As long as the World is running (no World::m_stopEvent), get the command line and handle it + while (!World::IsStopped()) + { + fflush(stdout); + + std::string command; + +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + wchar_t commandbuf[256]; + if (fgetws(commandbuf, sizeof(commandbuf), stdin)) + { + if (!WStrToUtf8(commandbuf, wcslen(commandbuf), command)) + { + PrintCliPrefix(); + continue; + } + } +#else + char* command_str = readline(CLI_PREFIX); + ::rl_bind_key('\t', ::rl_complete); + if (command_str != nullptr) + { + command = command_str; + free(command_str); + } +#endif + + if (!command.empty()) + { + std::size_t nextLineIndex = command.find_first_of("\r\n"); + if (nextLineIndex != std::string::npos) + { + if (nextLineIndex == 0) + { +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + PrintCliPrefix(); +#endif + continue; + } + + command.erase(nextLineIndex); + } + + fflush(stdout); + sWorld->QueueCliCommand(new CliCommandHolder(nullptr, command.c_str(), &utf8print, &commandFinished)); +#if AC_PLATFORM != AC_PLATFORM_WINDOWS + add_history(command.c_str()); +#endif + } + else if (feof(stdin)) + { + World::StopNow(SHUTDOWN_EXIT_CODE); + } + } +} diff --git a/src/server/apps/worldserver/CommandLine/CliRunnable.h b/src/server/apps/worldserver/CommandLine/CliRunnable.h new file mode 100644 index 0000000000..b4ac398a1d --- /dev/null +++ b/src/server/apps/worldserver/CommandLine/CliRunnable.h @@ -0,0 +1,30 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +/// \addtogroup Acored +/// @{ +/// \file + +#ifndef __CLIRUNNABLE_H +#define __CLIRUNNABLE_H + +/// Command Line Interface handling thread +void CliThread(); + +#endif + +/// @} diff --git a/src/server/apps/worldserver/Main.cpp b/src/server/apps/worldserver/Main.cpp new file mode 100644 index 0000000000..d8361c468b --- /dev/null +++ b/src/server/apps/worldserver/Main.cpp @@ -0,0 +1,761 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +/// \addtogroup Acored Acore Daemon +/// @{ +/// \file + +#include "ACSoap.h" +#include "AppenderDB.h" +#include "AsyncAcceptor.h" +#include "AsyncAuctionListing.h" +#include "Banner.h" +#include "BattlegroundMgr.h" +#include "BigNumber.h" +#include "CliRunnable.h" +#include "Common.h" +#include "Config.h" +#include "DatabaseEnv.h" +#include "DatabaseLoader.h" +#include "DeadlineTimer.h" +#include "GitRevision.h" +#include "IoContext.h" +#include "MapMgr.h" +#include "Metric.h" +#include "ModuleMgr.h" +#include "ModulesScriptLoader.h" +#include "MySQLThreading.h" +#include "OpenSSLCrypto.h" +#include "OutdoorPvPMgr.h" +#include "ProcessPriority.h" +#include "RASession.h" +#include "RealmList.h" +#include "Resolver.h" +#include "ScriptLoader.h" +#include "ScriptMgr.h" +#include "SecretMgr.h" +#include "SharedDefines.h" +#include "World.h" +#include "WorldSocket.h" +#include "WorldSocketMgr.h" +#include <boost/asio/signal_set.hpp> +#include <boost/program_options.hpp> +#include <csignal> +#include <filesystem> +#include <iostream> +#include <openssl/crypto.h> +#include <openssl/opensslv.h> + +#if AC_PLATFORM == AC_PLATFORM_WINDOWS +#include "ServiceWin32.h" +char serviceName[] = "worldserver"; +char serviceLongName[] = "AzerothCore world service"; +char serviceDescription[] = "AzerothCore World of Warcraft emulator world service"; +/* + * -1 - not in service mode + * 0 - stopped + * 1 - running + * 2 - paused + */ +int m_ServiceStatus = -1; +#endif + +#ifndef _ACORE_CORE_CONFIG +#define _ACORE_CORE_CONFIG "worldserver.conf" +#endif + +#define WORLD_SLEEP_CONST 10 +using namespace boost::program_options; +namespace fs = std::filesystem; + +class FreezeDetector +{ +public: + FreezeDetector(Acore::Asio::IoContext& ioContext, uint32 maxCoreStuckTime) + : _timer(ioContext), _worldLoopCounter(0), _lastChangeMsTime(getMSTime()), _maxCoreStuckTimeInMs(maxCoreStuckTime) { } + + static void Start(std::shared_ptr<FreezeDetector> const& freezeDetector) + { + freezeDetector->_timer.expires_from_now(boost::posix_time::seconds(5)); + freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, std::weak_ptr<FreezeDetector>(freezeDetector), std::placeholders::_1)); + } + + static void Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error); + +private: + Acore::Asio::DeadlineTimer _timer; + uint32 _worldLoopCounter; + uint32 _lastChangeMsTime; + uint32 _maxCoreStuckTimeInMs; +}; + +void SignalHandler(boost::system::error_code const& error, int signalNumber); +void ClearOnlineAccounts(); +bool StartDB(); +void StopDB(); +bool LoadRealmInfo(Acore::Asio::IoContext& ioContext); +AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext); +void ShutdownCLIThread(std::thread* cliThread); +void AuctionListingRunnable(); +void ShutdownAuctionListingThread(std::thread* thread); +void WorldUpdateLoop(); +variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [[maybe_unused]] std::string& cfg_service); + +/// Launch the Azeroth server +int main(int argc, char** argv) +{ + Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_WORLDSERVER; + signal(SIGABRT, &Acore::AbortHandler); + + // Command line parsing + auto configFile = fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_CORE_CONFIG)); + std::string configService; + auto vm = GetConsoleArguments(argc, argv, configFile, configService); + + // exit if help or version is enabled + if (vm.count("help")) + return 0; + +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + if (configService.compare("install") == 0) + return WinServiceInstall() == true ? 0 : 1; + else if (configService.compare("uninstall") == 0) + return WinServiceUninstall() == true ? 0 : 1; + else if (configService.compare("run") == 0) + WinServiceRun(); +#endif + + // Add file and args in config + sConfigMgr->Configure(configFile.generic_string(), {argv, argv + argc}, CONFIG_FILE_LIST); + + if (!sConfigMgr->LoadAppConfigs()) + return 1; + + std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>(); + + // Init all logs + sLog->RegisterAppender<AppenderDB>(); + // If logs are supposed to be handled async then we need to pass the IoContext into the Log singleton + sLog->Initialize(sConfigMgr->GetOption<bool>("Log.Async.Enable", false) ? ioContext.get() : nullptr); + + Acore::Banner::Show("worldserver-daemon", + [](std::string_view text) + { + LOG_INFO("server.worldserver", text); + }, + []() + { + LOG_INFO("server.worldserver", "> Using configuration file {}", sConfigMgr->GetFilename()); + LOG_INFO("server.worldserver", "> Using SSL version: {} (library: {})", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION)); + LOG_INFO("server.worldserver", "> Using Boost version: {}.{}.{}", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100); + }); + + OpenSSLCrypto::threadsSetup(); + + std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); }); + + // Seed the OpenSSL's PRNG here. + // That way it won't auto-seed when calling BigNumber::SetRand and slow down the first world login + BigNumber seed; + seed.SetRand(16 * 8); + + /// worldserver PID file creation + std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", ""); + if (!pidFile.empty()) + { + if (uint32 pid = CreatePIDFile(pidFile)) + LOG_ERROR("server", "Daemon PID: {}\n", pid); // outError for red color in console + else + { + LOG_ERROR("server", "Cannot create PID file {} (possible error: permission)\n", pidFile); + return 1; + } + } + + // Set signal handlers (this must be done before starting IoContext threads, because otherwise they would unblock and exit) + boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM); +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + signals.add(SIGBREAK); +#endif + signals.async_wait(SignalHandler); + + // Start the Boost based thread pool + int numThreads = sConfigMgr->GetOption<int32>("ThreadPool", 1); + std::shared_ptr<std::vector<std::thread>> threadPool(new std::vector<std::thread>(), [ioContext](std::vector<std::thread>* del) + { + ioContext->stop(); + for (std::thread& thr : *del) + thr.join(); + + delete del; + }); + + if (numThreads < 1) + { + numThreads = 1; + } + + for (int i = 0; i < numThreads; ++i) + { + threadPool->push_back(std::thread([ioContext]() + { + ioContext->run(); + })); + } + + // Set process priority according to configuration settings + SetProcessPriority("server.worldserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false)); + + // Loading modules configs before scripts + sConfigMgr->LoadModulesConfigs(); + + sScriptMgr->SetScriptLoader(AddScripts); + sScriptMgr->SetModulesLoader(AddModulesScripts); + + std::shared_ptr<void> sScriptMgrHandle(nullptr, [](void*) + { + sScriptMgr->Unload(); + //sScriptReloadMgr->Unload(); + }); + + LOG_INFO("server.loading", "Initializing Scripts..."); + sScriptMgr->Initialize(); + + // Start the databases + if (!StartDB()) + return 1; + + std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); }); + + // set server offline (not connectable) + LoginDatabase.DirectExecute("UPDATE realmlist SET flag = (flag & ~{}) | {} WHERE id = '{}'", REALM_FLAG_OFFLINE, REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm); + + LoadRealmInfo(*ioContext); + + sMetric->Initialize(realm.Name, *ioContext, []() + { + METRIC_VALUE("online_players", sWorld->GetPlayerCount()); + METRIC_VALUE("db_queue_login", uint64(LoginDatabase.QueueSize())); + METRIC_VALUE("db_queue_character", uint64(CharacterDatabase.QueueSize())); + METRIC_VALUE("db_queue_world", uint64(WorldDatabase.QueueSize())); + }); + + METRIC_EVENT("events", "Worldserver started", ""); + + std::shared_ptr<void> sMetricHandle(nullptr, [](void*) + { + METRIC_EVENT("events", "Worldserver shutdown", ""); + sMetric->Unload(); + }); + + Acore::Module::SetEnableModulesList(AC_MODULES_LIST); + + ///- Initialize the World + sSecretMgr->Initialize(); + sWorld->SetInitialWorldSettings(); + + std::shared_ptr<void> mapManagementHandle(nullptr, [](void*) + { + // unload battleground templates before different singletons destroyed + sBattlegroundMgr->DeleteAllBattlegrounds(); + + sOutdoorPvPMgr->Die(); // unload it before MapMgr + sMapMgr->UnloadAll(); // unload all grids (including locked in memory) + + sScriptMgr->OnAfterUnloadAllMaps(); + }); + + // Start the Remote Access port (acceptor) if enabled + std::unique_ptr<AsyncAcceptor> raAcceptor; + if (sConfigMgr->GetOption<bool>("Ra.Enable", false)) + { + raAcceptor.reset(StartRaSocketAcceptor(*ioContext)); + } + + // Start soap serving thread if enabled + std::shared_ptr<std::thread> soapThread; + if (sConfigMgr->GetOption<bool>("SOAP.Enabled", false)) + { + soapThread.reset(new std::thread(ACSoapThread, sConfigMgr->GetOption<std::string>("SOAP.IP", "127.0.0.1"), uint16(sConfigMgr->GetOption<int32>("SOAP.Port", 7878))), + [](std::thread* thr) + { + thr->join(); + delete thr; + }); + } + + // Launch the worldserver listener socket + uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD)); + std::string worldListener = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0"); + + int networkThreads = sConfigMgr->GetOption<int32>("Network.Threads", 1); + + if (networkThreads <= 0) + { + LOG_ERROR("server.worldserver", "Network.Threads must be greater than 0"); + World::StopNow(ERROR_EXIT_CODE); + return 1; + } + + if (!sWorldSocketMgr.StartWorldNetwork(*ioContext, worldListener, worldPort, networkThreads)) + { + LOG_ERROR("server.worldserver", "Failed to initialize network"); + World::StopNow(ERROR_EXIT_CODE); + return 1; + } + + std::shared_ptr<void> sWorldSocketMgrHandle(nullptr, [](void*) + { + sWorld->KickAll(); // save and kick all players + sWorld->UpdateSessions(1); // real players unload required UpdateSessions call + + sWorldSocketMgr.StopNetwork(); + + ///- Clean database before leaving + ClearOnlineAccounts(); + }); + + // Set server online (allow connecting now) + LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag & ~{}, population = 0 WHERE id = '{}'", REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm); + realm.PopulationLevel = 0.0f; + realm.Flags = RealmFlags(realm.Flags & ~uint32(REALM_FLAG_VERSION_MISMATCH)); + + // Start the freeze check callback cycle in 5 seconds (cycle itself is 1 sec) + std::shared_ptr<FreezeDetector> freezeDetector; + if (int32 coreStuckTime = sConfigMgr->GetOption<int32>("MaxCoreStuckTime", 60)) + { + freezeDetector = std::make_shared<FreezeDetector>(*ioContext, coreStuckTime * 1000); + FreezeDetector::Start(freezeDetector); + LOG_INFO("server.worldserver", "Starting up anti-freeze thread ({} seconds max stuck time)...", coreStuckTime); + } + + LOG_INFO("server.worldserver", "{} (worldserver-daemon) ready...", GitRevision::GetFullVersion()); + + sScriptMgr->OnStartup(); + + // Launch CliRunnable thread + std::shared_ptr<std::thread> cliThread; +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + if (sConfigMgr->GetOption<bool>("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/) +#else + if (sConfigMgr->GetOption<bool>("Console.Enable", true)) +#endif + { + cliThread.reset(new std::thread(CliThread), &ShutdownCLIThread); + } + + // Launch CliRunnable thread + std::shared_ptr<std::thread> auctionLisingThread; + auctionLisingThread.reset(new std::thread(AuctionListingRunnable), + [](std::thread* thr) + { + thr->join(); + delete thr; + }); + + WorldUpdateLoop(); + + // Shutdown starts here + threadPool.reset(); + + sLog->SetSynchronous(); + + sScriptMgr->OnShutdown(); + + // set server offline + LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag | {} WHERE id = '{}'", REALM_FLAG_OFFLINE, realm.Id.Realm); + + LOG_INFO("server.worldserver", "Halting process..."); + + // 0 - normal shutdown + // 1 - shutdown at error + // 2 - restart command used, this code can be used by restarter for restart Warheadd + + return World::GetExitCode(); +} + +/// Initialize connection to the databases +bool StartDB() +{ + MySQL::Library_Init(); + + // Load databases + DatabaseLoader loader("server.worldserver", DatabaseLoader::DATABASE_NONE, AC_MODULES_LIST); + loader + .AddDatabase(LoginDatabase, "Login") + .AddDatabase(CharacterDatabase, "Character") + .AddDatabase(WorldDatabase, "World"); + + if (!loader.Load()) + return false; + + ///- Get the realm Id from the configuration file + realm.Id.Realm = sConfigMgr->GetOption<uint32>("RealmID", 0); + if (!realm.Id.Realm) + { + LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file"); + return false; + } + else if (realm.Id.Realm > 255) + { + /* + * Due to the client only being able to read a realm.Id.Realm + * with a size of uint8 we can "only" store up to 255 realms + * anything further the client will behave anormaly + */ + LOG_ERROR("server.worldserver", "Realm ID must range from 1 to 255"); + return false; + } + + LOG_INFO("server.loading", "Loading world information..."); + LOG_INFO("server.loading", "> RealmID: {}", realm.Id.Realm); + + ///- Clean the database before starting + ClearOnlineAccounts(); + + ///- Insert version info into DB + WorldDatabase.Execute("UPDATE version SET core_version = '{}', core_revision = '{}'", GitRevision::GetFullVersion(), GitRevision::GetHash()); // One-time query + + sWorld->LoadDBVersion(); + + LOG_INFO("server.loading", "> Version DB world: {}", sWorld->GetDBVersion()); + + sScriptMgr->OnAfterDatabasesLoaded(loader.GetUpdateFlags()); + + return true; +} + +void StopDB() +{ + CharacterDatabase.Close(); + WorldDatabase.Close(); + LoginDatabase.Close(); + + MySQL::Library_End(); +} + +/// Clear 'online' status for all accounts with characters in this realm +void ClearOnlineAccounts() +{ + // Reset online status for all accounts with characters on the current realm + // pussywizard: tc query would set online=0 even if logged in on another realm >_> + LoginDatabase.DirectExecute("UPDATE account SET online = 0 WHERE online = {}", realm.Id.Realm); + + // Reset online status for all characters + CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0"); +} + +void ShutdownCLIThread(std::thread* cliThread) +{ + if (cliThread) + { +#ifdef _WIN32 + // First try to cancel any I/O in the CLI thread + if (!CancelSynchronousIo(cliThread->native_handle())) + { + // if CancelSynchronousIo() fails, print the error and try with old way + DWORD errorCode = GetLastError(); + LPCSTR errorBuffer; + + DWORD formatReturnCode = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, errorCode, 0, (LPTSTR)&errorBuffer, 0, nullptr); + if (!formatReturnCode) + errorBuffer = "Unknown error"; + + LOG_DEBUG("server.worldserver", "Error cancelling I/O of CliThread, error code {}, detail: {}", uint32(errorCode), errorBuffer); + + if (!formatReturnCode) + LocalFree((LPSTR)errorBuffer); + + // send keyboard input to safely unblock the CLI thread + INPUT_RECORD b[4]; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + b[0].EventType = KEY_EVENT; + b[0].Event.KeyEvent.bKeyDown = TRUE; + b[0].Event.KeyEvent.uChar.AsciiChar = 'X'; + b[0].Event.KeyEvent.wVirtualKeyCode = 'X'; + b[0].Event.KeyEvent.wRepeatCount = 1; + + b[1].EventType = KEY_EVENT; + b[1].Event.KeyEvent.bKeyDown = FALSE; + b[1].Event.KeyEvent.uChar.AsciiChar = 'X'; + b[1].Event.KeyEvent.wVirtualKeyCode = 'X'; + b[1].Event.KeyEvent.wRepeatCount = 1; + + b[2].EventType = KEY_EVENT; + b[2].Event.KeyEvent.bKeyDown = TRUE; + b[2].Event.KeyEvent.dwControlKeyState = 0; + b[2].Event.KeyEvent.uChar.AsciiChar = '\r'; + b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; + b[2].Event.KeyEvent.wRepeatCount = 1; + b[2].Event.KeyEvent.wVirtualScanCode = 0x1c; + + b[3].EventType = KEY_EVENT; + b[3].Event.KeyEvent.bKeyDown = FALSE; + b[3].Event.KeyEvent.dwControlKeyState = 0; + b[3].Event.KeyEvent.uChar.AsciiChar = '\r'; + b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN; + b[3].Event.KeyEvent.wVirtualScanCode = 0x1c; + b[3].Event.KeyEvent.wRepeatCount = 1; + DWORD numb; + WriteConsoleInput(hStdIn, b, 4, &numb); + } +#endif + cliThread->join(); + delete cliThread; + } +} + +void WorldUpdateLoop() +{ + uint32 realCurrTime = 0; + uint32 realPrevTime = getMSTime(); + + LoginDatabase.WarnAboutSyncQueries(true); + CharacterDatabase.WarnAboutSyncQueries(true); + WorldDatabase.WarnAboutSyncQueries(true); + + ///- While we have not World::m_stopEvent, update the world + while (!World::IsStopped()) + { + ++World::m_worldLoopCounter; + realCurrTime = getMSTime(); + + uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime); + + sWorld->Update(diff); + realPrevTime = realCurrTime; + + uint32 executionTimeDiff = getMSTimeDiff(realCurrTime, getMSTime()); + + // we know exactly how long it took to update the world, if the update took less than WORLD_SLEEP_CONST, sleep for WORLD_SLEEP_CONST - world update time + if (executionTimeDiff < WORLD_SLEEP_CONST) + { + std::this_thread::sleep_for(Milliseconds(WORLD_SLEEP_CONST - executionTimeDiff)); + } + +#ifdef _WIN32 + if (m_ServiceStatus == 0) + World::StopNow(SHUTDOWN_EXIT_CODE); + + while (m_ServiceStatus == 2) + Sleep(1000); +#endif + } + + LoginDatabase.WarnAboutSyncQueries(false); + CharacterDatabase.WarnAboutSyncQueries(false); + WorldDatabase.WarnAboutSyncQueries(false); +} + +void SignalHandler(boost::system::error_code const& error, int /*signalNumber*/) +{ + if (!error) + World::StopNow(SHUTDOWN_EXIT_CODE); +} + +void FreezeDetector::Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error) +{ + if (!error) + { + if (std::shared_ptr<FreezeDetector> freezeDetector = freezeDetectorRef.lock()) + { + uint32 curtime = getMSTime(); + + uint32 worldLoopCounter = World::m_worldLoopCounter; + if (freezeDetector->_worldLoopCounter != worldLoopCounter) + { + freezeDetector->_lastChangeMsTime = curtime; + freezeDetector->_worldLoopCounter = worldLoopCounter; + } + // possible freeze + else if (getMSTimeDiff(freezeDetector->_lastChangeMsTime, curtime) > freezeDetector->_maxCoreStuckTimeInMs) + { + LOG_ERROR("server.worldserver", "World Thread hangs, kicking out server!"); + ABORT(); + } + + freezeDetector->_timer.expires_from_now(boost::posix_time::seconds(1)); + freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, freezeDetectorRef, std::placeholders::_1)); + } + } +} + +AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext) +{ + uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443)); + std::string raListener = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0"); + + AsyncAcceptor* acceptor = new AsyncAcceptor(ioContext, raListener, raPort); + if (!acceptor->Bind()) + { + LOG_ERROR("server.worldserver", "Failed to bind RA socket acceptor"); + delete acceptor; + return nullptr; + } + + acceptor->AsyncAccept<RASession>(); + return acceptor; +} + +bool LoadRealmInfo(Acore::Asio::IoContext& ioContext) +{ + QueryResult result = LoginDatabase.Query("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE id = {}", realm.Id.Realm); + if (!result) + return false; + + Acore::Asio::Resolver resolver(ioContext); + + Field* fields = result->Fetch(); + realm.Name = fields[1].Get<std::string>(); + + Optional<boost::asio::ip::tcp::endpoint> externalAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[2].Get<std::string>(), ""); + if (!externalAddress) + { + LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[2].Get<std::string>()); + return false; + } + + realm.ExternalAddress = std::make_unique<boost::asio::ip::address>(externalAddress->address()); + + Optional<boost::asio::ip::tcp::endpoint> localAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[3].Get<std::string>(), ""); + if (!localAddress) + { + LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[3].Get<std::string>()); + return false; + } + + realm.LocalAddress = std::make_unique<boost::asio::ip::address>(localAddress->address()); + + Optional<boost::asio::ip::tcp::endpoint> localSubmask = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[4].Get<std::string>(), ""); + if (!localSubmask) + { + LOG_ERROR("server.worldserver", "Could not resolve address {}", fields[4].Get<std::string>()); + return false; + } + + realm.LocalSubnetMask = std::make_unique<boost::asio::ip::address>(localSubmask->address()); + + realm.Port = fields[5].Get<uint16>(); + realm.Type = fields[6].Get<uint8>(); + realm.Flags = RealmFlags(fields[7].Get<uint8>()); + realm.Timezone = fields[8].Get<uint8>(); + realm.AllowedSecurityLevel = AccountTypes(fields[9].Get<uint8>()); + realm.PopulationLevel = fields[10].Get<float>(); + realm.Build = fields[11].Get<uint32>(); + return true; +} + +void AuctionListingRunnable() +{ + LOG_INFO("server", "Starting up Auction House Listing thread..."); + + while (!World::IsStopped()) + { + if (AsyncAuctionListingMgr::IsAuctionListingAllowed()) + { + uint32 diff = AsyncAuctionListingMgr::GetDiff(); + AsyncAuctionListingMgr::ResetDiff(); + + if (AsyncAuctionListingMgr::GetTempList().size() || AsyncAuctionListingMgr::GetList().size()) + { + std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetLock()); + + { + std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetTempLock()); + + for (auto const& delayEvent : AsyncAuctionListingMgr::GetTempList()) + AsyncAuctionListingMgr::GetList().emplace_back(delayEvent); + + AsyncAuctionListingMgr::GetTempList().clear(); + } + + for (auto& itr : AsyncAuctionListingMgr::GetList()) + { + if (itr._msTimer <= diff) + itr._msTimer = 0; + else + itr._msTimer -= diff; + } + + for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr) + { + if ((*itr)._msTimer != 0) + continue; + + if ((*itr).Execute()) + AsyncAuctionListingMgr::GetList().erase(itr); + + break; + } + } + } + std::this_thread::sleep_for(1ms); + } + + LOG_INFO("server", "Auction House Listing thread exiting without problems."); +} + +void ShutdownAuctionListingThread(std::thread* thread) +{ + if (thread) + { + thread->join(); + delete thread; + } +} + +variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [[maybe_unused]] std::string& configService) +{ + options_description all("Allowed options"); + all.add_options() + ("help,h", "print usage message") + ("version,v", "print version build info") + ("dry-run,d", "Dry run") + ("config,c", value<fs::path>(&configFile)->default_value(fs::path(sConfigMgr->GetConfigPath() + std::string(_ACORE_CORE_CONFIG))), "use <arg> as configuration file"); + +#if AC_PLATFORM == WARHEAD_PLATFORM_WINDOWS + options_description win("Windows platform specific options"); + win.add_options() + ("service,s", value<std::string>(&configService)->default_value(""), "Windows service options: [install | uninstall]"); + + all.add(win); +#endif + + variables_map vm; + + try + { + store(command_line_parser(argc, argv).options(all).allow_unregistered().run(), vm); + notify(vm); + } + catch (std::exception const& e) + { + std::cerr << e.what() << "\n"; + } + + if (vm.count("help")) + { + std::cout << all << "\n"; + } + else if (vm.count("dry-run")) + { + sConfigMgr->setDryRun(true); + } + + return vm; +} diff --git a/src/server/apps/worldserver/PrecompiledHeaders/worldserverPCH.h b/src/server/apps/worldserver/PrecompiledHeaders/worldserverPCH.h new file mode 100644 index 0000000000..8b86ed3cc0 --- /dev/null +++ b/src/server/apps/worldserver/PrecompiledHeaders/worldserverPCH.h @@ -0,0 +1,24 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#include "Common.h" +#include "Config.h" +#include "DatabaseEnv.h" +#include "Log.h" +#include "Util.h" +#include "World.h" +#include "WorldSocket.h" diff --git a/src/server/apps/worldserver/RemoteAccess/RASession.cpp b/src/server/apps/worldserver/RemoteAccess/RASession.cpp new file mode 100644 index 0000000000..4bbd1b5385 --- /dev/null +++ b/src/server/apps/worldserver/RemoteAccess/RASession.cpp @@ -0,0 +1,223 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#include "RASession.h" +#include "AccountMgr.h" +#include "Config.h" +#include "DatabaseEnv.h" +#include "Duration.h" +#include "Log.h" +#include "SRP6.h" +#include "ServerMotd.h" +#include "Util.h" +#include "World.h" +#include <boost/asio/buffer.hpp> +#include <boost/asio/read_until.hpp> +#include <thread> + +using boost::asio::ip::tcp; + +void RASession::Start() +{ + // wait 1 second for active connections to send negotiation request + for (int counter = 0; counter < 10 && _socket.available() == 0; counter++) + std::this_thread::sleep_for(100ms); + + // Check if there are bytes available, if they are, then the client is requesting the negotiation + if (_socket.available() > 0) + { + // Handle subnegotiation + char buf[1024] = { }; + _socket.read_some(boost::asio::buffer(buf)); + + // Send the end-of-negotiation packet + uint8 const reply[2] = { 0xFF, 0xF0 }; + _socket.write_some(boost::asio::buffer(reply)); + } + + Send("Authentication Required\r\n"); + Send("Username: "); + + std::string username = ReadString(); + + if (username.empty()) + return; + + LOG_INFO("commands.ra", "Accepting RA connection from user {} (IP: {})", username, GetRemoteIpAddress()); + + Send("Password: "); + + std::string password = ReadString(); + if (password.empty()) + return; + + if (!CheckAccessLevel(username) || !CheckPassword(username, password)) + { + Send("Authentication failed\r\n"); + _socket.close(); + return; + } + + LOG_INFO("commands.ra", "User {} (IP: {}) authenticated correctly to RA", username, GetRemoteIpAddress()); + + // Authentication successful, send the motd + Send(std::string(std::string(Motd::GetMotd()) + "\r\n").c_str()); + + // Read commands + for (;;) + { + Send("AC>"); + std::string command = ReadString(); + + if (ProcessCommand(command)) + break; + } + + _socket.close(); +} + +int RASession::Send(std::string_view data) +{ + std::ostream os(&_writeBuffer); + os << data; + size_t written = _socket.send(_writeBuffer.data()); + _writeBuffer.consume(written); + return written; +} + +std::string RASession::ReadString() +{ + boost::system::error_code error; + size_t read = boost::asio::read_until(_socket, _readBuffer, "\r\n", error); + if (!read) + { + _socket.close(); + return ""; + } + + std::string line; + std::istream is(&_readBuffer); + std::getline(is, line); + + if (*line.rbegin() == '\r') + line.erase(line.length() - 1); + + return line; +} + +bool RASession::CheckAccessLevel(const std::string& user) +{ + std::string safeUser = user; + + Utf8ToUpperOnlyLatin(safeUser); + + auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ACCESS); + stmt->SetData(0, safeUser); + + PreparedQueryResult result = LoginDatabase.Query(stmt); + if (!result) + { + LOG_INFO("commands.ra", "User {} does not exist in database", user); + return false; + } + + Field* fields = result->Fetch(); + + if (fields[1].Get<uint8>() < sConfigMgr->GetOption<int32>("Ra.MinLevel", 3)) + { + LOG_INFO("commands.ra", "User {} has no privilege to login", user); + return false; + } + else if (fields[2].Get<int32>() != -1) + { + LOG_INFO("commands.ra", "User {} has to be assigned on all realms (with RealmID = '-1')", user); + return false; + } + + return true; +} + +bool RASession::CheckPassword(const std::string& user, const std::string& pass) +{ + std::string safe_user = user; + std::transform(safe_user.begin(), safe_user.end(), safe_user.begin(), ::toupper); + Utf8ToUpperOnlyLatin(safe_user); + + std::string safe_pass = pass; + Utf8ToUpperOnlyLatin(safe_pass); + std::transform(safe_pass.begin(), safe_pass.end(), safe_pass.begin(), ::toupper); + + auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME); + + stmt->SetData(0, safe_user); + + if (PreparedQueryResult result = LoginDatabase.Query(stmt)) + { + Acore::Crypto::SRP6::Salt salt = (*result)[0].Get<Binary, Acore::Crypto::SRP6::SALT_LENGTH>(); + Acore::Crypto::SRP6::Verifier verifier = (*result)[1].Get<Binary, Acore::Crypto::SRP6::VERIFIER_LENGTH>(); + + if (Acore::Crypto::SRP6::CheckLogin(safe_user, safe_pass, salt, verifier)) + return true; + } + + LOG_INFO("commands.ra", "Wrong password for user: {}", user); + return false; +} + +bool RASession::ProcessCommand(std::string& command) +{ + if (command.length() == 0) + return true; + + LOG_INFO("commands.ra", "Received command: {}", command); + + // handle quit, exit and logout commands to terminate connection + if (command == "quit" || command == "exit" || command == "logout") + { + Send("Bye\r\n"); + return true; + } + + // Obtain a new promise per command + delete _commandExecuting; + _commandExecuting = new std::promise<void>(); + + CliCommandHolder* cmd = new CliCommandHolder(this, command.c_str(), &RASession::CommandPrint, &RASession::CommandFinished); + sWorld->QueueCliCommand(cmd); + + // Wait for the command to finish + _commandExecuting->get_future().wait(); + + return false; +} + +void RASession::CommandPrint(void* callbackArg, std::string_view text) +{ + if (text.empty()) + { + return; + } + + RASession* session = static_cast<RASession*>(callbackArg); + session->Send(text); +} + +void RASession::CommandFinished(void* callbackArg, bool /*success*/) +{ + RASession* session = static_cast<RASession*>(callbackArg); + session->_commandExecuting->set_value(); +} diff --git a/src/server/apps/worldserver/RemoteAccess/RASession.h b/src/server/apps/worldserver/RemoteAccess/RASession.h new file mode 100644 index 0000000000..c4dd6d861f --- /dev/null +++ b/src/server/apps/worldserver/RemoteAccess/RASession.h @@ -0,0 +1,58 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __RASESSION_H__ +#define __RASESSION_H__ + +#include "Common.h" +#include <boost/asio/ip/tcp.hpp> +#include <boost/asio/streambuf.hpp> +#include <future> +#include <memory> + +using boost::asio::ip::tcp; + +const size_t bufferSize = 4096; + +class RASession : public std::enable_shared_from_this<RASession> +{ +public: + RASession(tcp::socket&& socket) : + _socket(std::move(socket)), _commandExecuting(nullptr) { } + + void Start(); + + const std::string GetRemoteIpAddress() const { return _socket.remote_endpoint().address().to_string(); } + unsigned short GetRemotePort() const { return _socket.remote_endpoint().port(); } + +private: + int Send(std::string_view data); + std::string ReadString(); + bool CheckAccessLevel(const std::string& user); + bool CheckPassword(const std::string& user, const std::string& pass); + bool ProcessCommand(std::string& command); + + static void CommandPrint(void* callbackArg, std::string_view text); + static void CommandFinished(void* callbackArg, bool); + + tcp::socket _socket; + boost::asio::streambuf _readBuffer; + boost::asio::streambuf _writeBuffer; + std::promise<void>* _commandExecuting; +}; + +#endif diff --git a/src/server/apps/worldserver/resource.h b/src/server/apps/worldserver/resource.h new file mode 100644 index 0000000000..5415d15d80 --- /dev/null +++ b/src/server/apps/worldserver/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by SunwellCore.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist new file mode 100644 index 0000000000..66e5b06364 --- /dev/null +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -0,0 +1,4038 @@ +################################################ +# AzerothCore World Server configuration file # +################################################ +[worldserver] + +################################################################################################### +# SECTION INDEX +# +# EXAMPLE CONFIG +# CONNECTIONS AND DIRECTORIES +# PERFORMANCE SETTINGS +# SERVER LOGGING +# SERVER SETTINGS +# CRYPTOGRAPHY +# WARDEN SETTINGS +# PLAYER INTERACTION +# CREATURE SETTINGS +# CHAT SETTINGS +# GAME MASTER SETTINGS +# VISIBILITY AND DISTANCES +# SERVER RATES +# STATS LIMITS +# AUTO BROADCAST +# BATTLEGROUND CONFIG +# BATTLEFIELD CONFIG +# ARENA CONFIG +# NETWORK CONFIG +# CONSOLE AND REMOTE ACCESS +# CHARACTER DELETE OPTIONS +# ITEM DELETE OPTIONS +# CUSTOM SERVER OPTIONS +# UPDATE SETTINGS +# LOGGING SYSTEM SETTINGS +# PACKET SPOOF PROTECTION SETTINGS +# DEBUG +# METRIC SETTINGS +# +################################################################################################### + +################################################################################################### +# EXAMPLE CONFIG +# +# Variable +# Description: Brief description what the variable is doing. +# Important: Annotation for important things about this variable. +# Example: "Example, i.e. if the value is a string" +# Default: 10 - (Enabled|Comment|Variable name in case of grouped config options) +# 0 - (Disabled|Comment|Variable name in case of grouped config options) +# +# Note to developers: +# - Copy this example to keep the formatting. +# - Line breaks should be at column 100. +################################################################################################### + +################################################################################################### +# CONNECTIONS AND DIRECTORIES +# +# RealmID +# Description: ID of the Realm using this config. +# Important: RealmID must match the realmlist inside the auth database. +# Default: 1 + +RealmID = 1 + +# +# DataDir +# Description: Data directory setting. +# Important: DataDir needs to be quoted, as the string might contain space characters. +# Example: "@prefix@\home\youruser\azerothcore\data" +# Default: "." + +DataDir = "." + +# +# LogsDir +# Description: Logs directory setting. +# Important: LogsDir needs to be quoted, as the string might contain space characters. +# Logs directory must exists, or log file creation will be disabled. +# Example: "/home/youruser/azerothcore/logs" +# Default: "" - (Log files will be stored in the current path) + +LogsDir = "" + +# +# LoginDatabaseInfo +# WorldDatabaseInfo +# CharacterDatabaseInfo +# Description: Database connection settings for the world server. +# Example: "hostname;port;username;password;database" +# ".;somenumber;username;password;database" - (Use named pipes on Windows +# "enable-named-pipe" to [mysqld] +# section my.ini) +# ".;/path/to/unix_socket;username;password;database" - (use Unix sockets on +# Unix/Linux) +# Default: "127.0.0.1;3306;acore;acore;acore_auth" - (LoginDatabaseInfo) +# "127.0.0.1;3306;acore;acore;acore_world" - (WorldDatabaseInfo) +# "127.0.0.1;3306;acore;acore;acore_characters" - (CharacterDatabaseInfo) + +LoginDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_auth" +WorldDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_world" +CharacterDatabaseInfo = "127.0.0.1;3306;acore;acore;acore_characters" + +# +# Database.Reconnect.Seconds +# Database.Reconnect.Attempts +# +# Description: How many seconds between every reconnection attempt +# and how many attempts will be performed in total +# Default: 20 attempts every 15 seconds +# + +Database.Reconnect.Seconds = 15 +Database.Reconnect.Attempts = 20 + +# +# LoginDatabase.WorkerThreads +# WorldDatabase.WorkerThreads +# CharacterDatabase.WorkerThreads +# Description: The amount of worker threads spawned to handle asynchronous (delayed) MySQL +# statements. Each worker thread is mirrored with its own connection to the +# MySQL server and their own thread on the MySQL server. +# Default: 1 - (LoginDatabase.WorkerThreads) +# 1 - (WorldDatabase.WorkerThreads) +# 1 - (CharacterDatabase.WorkerThreads) + +LoginDatabase.WorkerThreads = 1 +WorldDatabase.WorkerThreads = 1 +CharacterDatabase.WorkerThreads = 1 + +# +# LoginDatabase.SynchThreads +# WorldDatabase.SynchThreads +# CharacterDatabase.SynchThreads +# Description: The amount of MySQL connections spawned to handle. +# Default: 1 - (LoginDatabase.WorkerThreads) +# 1 - (WorldDatabase.WorkerThreads) +# 2 - (CharacterDatabase.WorkerThreads) + +LoginDatabase.SynchThreads = 1 +WorldDatabase.SynchThreads = 1 +CharacterDatabase.SynchThreads = 2 + +# +# MaxPingTime +# Description: Time (in minutes) between database pings. +# Default: 30 + +MaxPingTime = 30 + +# +# WorldServerPort +# Description: TCP port to reach the world server. +# Default: 8085 + +WorldServerPort = 8085 + +# +# BindIP +# Description: Bind world server to IP/hostname +# Default: "0.0.0.0" - (Bind to all IPs on the system) + +BindIP = "0.0.0.0" + +# +# CMakeCommand +# Description: The path to your CMake binary. +# If the path is left empty, the built-in CMAKE_COMMAND is used. +# Example: "C:/Program Files/CMake/bin/cmake.exe" +# "/usr/bin/cmake" +# Default: "" + +CMakeCommand = "" + +# +# BuildDirectory +# Description: The path to your build directory. +# If the path is left empty, the built-in CMAKE_BINARY_DIR is used. +# Example: "../AzerothCore" +# Default: "" + +BuildDirectory = "" + +# +# SourceDirectory +# Description: The path to your AzerothCore source directory. +# If the path is left empty, the built-in CMAKE_SOURCE_DIR is used. +# Example: "../AzerothCore" +# Default: "" + +SourceDirectory = "" + +# +# MySQLExecutable +# Description: The path to your MySQL CLI binary. +# If the path is left empty, built-in path from cmake is used. +# Example: "C:/Program Files/MySQL/MySQL Server 5.7/bin/mysql.exe" +# "mysql.exe" +# "/usr/bin/mysql" +# Default: "" + +MySQLExecutable = "" + +# +# ThreadPool +# Description: Number of threads to be used for the global thread pool +# The thread pool is currently used for: +# - Signal handling +# - Remote access +# - Database keep-alive ping +# - Core freeze check +# - World socket networking +# Default: 2 + +ThreadPool = 2 + +# +# IPLocationFile +# Description: The path to your IP2Location database CSV file. +# Example: "C:/acore/IP2LOCATION-LITE-DB1.CSV" +# "/home/acore/IP2LOCATION-LITE-DB1.CSV" +# Default: "" - (Disabled) +# + +IPLocationFile = "" + +# +# AllowLoggingIPAddressesInDatabase +# Description: Specifies if IP addresses can be logged to the database +# Default: 1 - (Enabled) +# 0 - (Disabled) +# + +AllowLoggingIPAddressesInDatabase = 1 + +# +################################################################################################### + +################################################################################################### +# PERFORMANCE SETTINGS +# +# UseProcessors +# Description: Processors mask for Windows and Linux based multi-processor systems. +# Example: For a computer with 3 CPUs: +# 1 - 1st CPU only +# 2 - 2nd CPU only +# 4 - 3rd CPU only +# 6 - 2nd + 3rd CPUs, because "2 | 4" -> 6 +# Default: 0 - (Selected by OS) +# 1+ - (Bit mask value of selected processors) + +UseProcessors = 0 + +# +# ProcessPriority +# Description: Process priority setting for Windows based systems. +# Default: 1 - (High) +# 0 - (Normal) + +ProcessPriority = 1 + +# +# Compression +# Description: Compression level for client update packages +# Range: 1-9 +# Default: 1 - (Speed) +# 9 - (Best compression) + +Compression = 1 + +# +# PlayerLimit +# Description: Maximum number of players in the world. Excluding Mods, GMs and Admins. +# Important: If you want to block players and only allow Mods, GMs or Admins to join the +# server, use the DB field "auth.realmlist.allowedSecurityLevel". +# Default: 1000 - (Enabled) +# 1+ - (Enabled) +# 0 - (Disabled, No limit) + +PlayerLimit = 1000 + +# +# SaveRespawnTimeImmediately +# Description: Save respawn time for creatures at death and gameobjects at use/open. +# Default: 1 - (Enabled, Save respawn time immediately) +# 0 - (Disabled, Save respawn time at grid unloading) + +SaveRespawnTimeImmediately = 1 + +# +# MaxOverspeedPings +# Description: Maximum overspeed ping count before character is disconnected. +# Default: 2 - (Enabled, Minimum value) +# 3+ - (Enabled, More checks before kick) +# 0 - (Disabled) + +MaxOverspeedPings = 2 + +# +# CloseIdleConnections +# Description: Automatically close idle connections. +# SocketTimeOutTime and SocketTimeOutTimeActive determine when a connection is considered as idle. +# Default: 1 - (enable, Automatically close idle connections) +# 0 - (disable, Do not close idle connections) + +CloseIdleConnections = 1 + +# +# SocketTimeOutTime +# Description: Time (in milliseconds) after which a connection being idle on the character +# selection screen is disconnected. +# Default: 900000 - (15 minutes) + +SocketTimeOutTime = 900000 + +# +# SocketTimeOutTimeActive +# Description: Time (in milliseconds) after which an idle connection is dropped while +# logged into the world. +# The client sends keepalive packets every 30 seconds. Values <= 30s are not recommended. +# Default: 60000 - (1 minute) + +SocketTimeOutTimeActive = 60000 + +# +# SessionAddDelay +# Description: Time (in microseconds) that a network thread will sleep after authentication +# protocol handling before adding a connection to the world session map. +# Default: 10000 - (10 milliseconds, 0.01 second) + +SessionAddDelay = 10000 + +# +# MapUpdateInterval +# Description: Time (milliseconds) for map update interval. +# Default: 100 - (0.1 second) + +MapUpdateInterval = 100 + +# +# ChangeWeatherInterval +# Description: Time (in milliseconds) for weather update interval. +# Default: 600000 - (10 min) + +ChangeWeatherInterval = 600000 + +# +# PlayerSaveInterval +# Description: Time (in milliseconds) for player save interval. +# Default: 900000 - (15 min) + +PlayerSaveInterval = 900000 + +# +# PlayerSave.Stats.MinLevel +# Description: Minimum level for saving character stats in the database for external usage. +# Default: 0 - (Disabled, Do not save character stats) +# 1+ - (Enabled, Level beyond which character stats are saved) + +PlayerSave.Stats.MinLevel = 0 + +# +# PlayerSave.Stats.SaveOnlyOnLogout +# Description: Save player stats only on logout. +# Default: 1 - (Enabled, Only save on logout) +# 0 - (Disabled, Save on every player save) + +PlayerSave.Stats.SaveOnlyOnLogout = 1 + +# +# vmap.enableLOS +# vmap.enableHeight +# Description: VMmap support for line of sight and height calculation. +# Default: 1 - (Enabled, vmap.enableLOS) +# 1 - (Enabled, vmap.enableHeight) +# 0 - (Disabled) + +vmap.enableLOS = 1 +vmap.enableHeight = 1 + +# +# vmap.petLOS +# Description: Check line of sight for pets, to avoid them attacking through walls. +# Default: 1 - (Enabled, each pet attack will be checked for line of sight) +# 0 - (Disabled, somewhat less CPU usage) + +vmap.petLOS = 1 + +# vmap.BlizzlikePvPLOS +# Description: Check line of sight for battleground and arena gameobjects and other doodads (such as WSG treestumps). +# Default: 1 - (Enabled, players will be able to fire spells through treestumps and other objects). +# 0 - (Disabled, players will NOT be able to fire spells through treestumps and other objects). + +vmap.BlizzlikePvPLOS = 1 + +# +# vmap.enableIndoorCheck +# Description: VMap based indoor check to remove outdoor-only auras (mounts etc.). +# Default: 1 - (Enabled) +# 0 - (Disabled, somewhat less CPU usage) + +vmap.enableIndoorCheck = 1 + +# +# DetectPosCollision +# Description: Check final move position, summon position, etc for visible collision with +# other objects or walls (walls only if vmaps are enabled). +# Default: 1 - (Enabled) +# 0 - (Disabled, Less position precision but less CPU usage) + +DetectPosCollision = 1 + +# +# CheckGameObjectLoS +# Description: Include dynamic game objects (doors, chests etc.) in line of sight checks. +# This increases CPU usage somewhat. +# Default: 1 - (Enabled) +# 0 - (Disabled, may break some boss encounters) + +CheckGameObjectLoS = 1 + +# +# TargetPosRecalculateRange +# Description: Max distance from movement target point (+moving unit size) and targeted +# object (+size) after that new target movement point calculated. +# Range: 0.5-5.0 +# Default: 1.5 +# 0.5 - (Minimum, Contact Range, More sensitive reaction to target movement) +# 5.0 - (Maximum, Melee attack range, Less CPU usage) + +TargetPosRecalculateRange = 1.5 + +# +# UpdateUptimeInterval +# Description: Update realm uptime period (in minutes). +# Default: 10 - (10 minutes) +# 1+ + +UpdateUptimeInterval = 1 + +# +# LogDB.Opt.ClearInterval +# Description: Time (in minutes) for the WUPDATE_CLEANDB timer that clears the `logs` table +# of old entries. +# Default: 10 - (10 minutes) +# 1+ + +LogDB.Opt.ClearInterval = 10 + +# +# LogDB.Opt.ClearTime +# Description: Time (in seconds) for keeping old `logs` table entries. +# Default: 1209600 - (Enabled, 14 days) +# 0 - (Disabled, Do not clear entries) + +LogDB.Opt.ClearTime = 1209600 + +# +# MaxCoreStuckTime +# Description: Time (in seconds) before the server is forced to crash if it is frozen. +# Default: 0 - (Disabled) +# 10+ - (Enabled, Recommended 30+) +# Note: If enabled and the setting is too low, it can cause unexpected crash. + +MaxCoreStuckTime = 0 + +# +# AddonChannel +# Description: Configure the use of the addon channel through the server (some client side +# addons will not work correctly with disabled addon channel) +# Default: 1 - (Enabled) +# 0 - (Disabled) + +AddonChannel = 1 + +# +# MapUpdate.Threads +# Description: Number of threads to update maps. +# Default: 1 + +MapUpdate.Threads = 1 + +# +# CleanCharacterDB +# Description: Clean out deprecated achievements, skills, spells and talents from the db. +# Default: 0 - (Disabled) +# 1 - (Enable) + +CleanCharacterDB = 0 + +# +# PersistentCharacterCleanFlags +# Description: Determines the character clean flags that remain set after cleanups. +# This is a bitmask value, you can use one of the following values: +# +# CLEANING_FLAG_ACHIEVEMENT_PROGRESS = 0x1 +# CLEANING_FLAG_SKILLS = 0x2 +# CLEANING_FLAG_SPELLS = 0x4 +# CLEANING_FLAG_TALENTS = 0x8 +# CLEANING_FLAG_QUESTSTATUS = 0x10 +# +# Before use this feature, make a backup of your database. +# +# Example: 14 - (CLEANING_FLAG_SKILLS + CLEANING_FLAG_SPELLS + CLEANING_FLAG_TALENTS +# 2+4+8 => 14. This will clean up skills, talents and spells will +# remain enabled after the next cleanup) +# Default: 0 - (All cleanup methods will be disabled after the next cleanup) + +PersistentCharacterCleanFlags = 0 + +# +# PreloadAllNonInstancedMapGrids +# Description: Preload all grids on all non-instanced maps. This will take a great amount +# of additional RAM (ca. 9 GB) and causes the server to take longer to start, +# but can increase performance if used on a server with a high amount of players. +# It will also activate all creatures which are set active (e.g. the Fel Reavers +# in Hellfire Peninsula) on server start. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PreloadAllNonInstancedMapGrids = 0 + +# +# SetAllCreaturesWithWaypointMovementActive +# Description: Set all creatures with waypoint movement active. This means that they will start +# movement once they are loaded (which happens on grid load) and keep moving even +# when no player is near. This will increase CPU usage significantly and can be +# used with enabled "PreloadAllNonInstancedMapGrids" to start waypoint movement on +# server startup. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +SetAllCreaturesWithWaypointMovementActive = 0 + +# +################################################################################################### + +################################################################################################### +# SERVER LOGGING +# +# PidFile +# Description: World daemon PID file. +# Example: "./world.pid" - (Enabled) +# Default: "" - (Disabled) + +PidFile = "" + +# +# PacketLogFile +# Description: Binary packet logging file for the world server. +# Filename extension must be .pkt to be parsable with WowPacketParser. +# Example: "World.pkt" - (Enabled) +# Default: "" - (Disabled) + +PacketLogFile = "" + +# Extended Logging system configuration moved to end of file (on purpose) +# +################################################################################################### + +################################################################################################### +# SERVER SETTINGS +# +# GameType +# Description: Server realm type. +# Default: 0 - (NORMAL) +# 1 - (PVP) +# 4 - (NORMAL) +# 6 - (RP) +# 8 - (RPPVP) +# 16 - (FFA_PVP, Free for all PvP mode like arena PvP in all zones except rest +# activated places and sanctuaries) + +GameType = 0 + +# +# RealmZone +# Description: Server realm zone. Set allowed alphabet in character, etc. names. +# Default 1 - (Development - any language) +# 2 - (United States - extended-Latin) +# 3 - (Oceanic - extended-Latin) +# 4 - (Latin America - extended-Latin) +# 5 - (Tournament - basic-Latin at create, any at login) +# 6 - (Korea - East-Asian) +# 7 - (Tournament - basic-Latin at create, any at login) +# 8 - (English - extended-Latin) +# 9 - (German - extended-Latin) +# 10 - (French - extended-Latin) +# 11 - (Spanish - extended-Latin) +# 12 - (Russian - Cyrillic) +# 13 - (Tournament - basic-Latin at create, any at login) +# 14 - (Taiwan - East-Asian) +# 15 - (Tournament - basic-Latin at create, any at login) +# 16 - (China - East-Asian) +# 17 - (CN1 - basic-Latin at create, any at login) +# 18 - (CN2 - basic-Latin at create, any at login) +# 19 - (CN3 - basic-Latin at create, any at login) +# 20 - (CN4 - basic-Latin at create, any at login) +# 21 - (CN5 - basic-Latin at create, any at login) +# 22 - (CN6 - basic-Latin at create, any at login) +# 23 - (CN7 - basic-Latin at create, any at login) +# 24 - (CN8 - basic-Latin at create, any at login) +# 25 - (Tournament - basic-Latin at create, any at login) +# 26 - (Test Server - any language) +# 27 - (Tournament - basic-Latin at create, any at login) +# 28 - (QA Server - any language) +# 29 - (CN9 - basic-Latin at create, any at login) + +RealmZone = 1 + +# +# World.RealmAvailability +# Description: If enabled, players will enter the realm normally. +# Character creation will still be possible even when realm is disabled. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +World.RealmAvailability = 1 + +# +# StrictPlayerNames +# Description: Limit player name to language specific symbol set. Prevents character +# creation and forces rename request if not allowed symbols are used +# Default: 0 - (Disable, Limited server timezone dependent client check) +# 1 - (Enabled, Strictly basic Latin characters) +# 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. +# 3 - (Enabled, Basic Latin characters + server timezone specific) + +StrictPlayerNames = 0 + +# +# StrictCharterNames +# Description: Limit guild/arena team charter names to language specific symbol set. +# Prevents charter creation if not allowed symbols are used. +# Default: 0 - (Disable, Limited server timezone dependent client check) +# 1 - (Enabled, Strictly basic Latin characters) +# 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. +# 3 - (Enabled, Basic Latin characters + server timezone specific) + +StrictCharterNames = 0 + +# +# StrictPetNames +# Description: Limit pet names to language specific symbol set. +# Prevents pet naming if not allowed symbols are used. +# Default: 0 - (Disable, Limited server timezone dependent client check) +# 1 - (Enabled, Strictly basic Latin characters) +# 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. +# 3 - (Enabled, Basic Latin characters + server timezone specific) + +StrictPetNames = 0 + +# +# DBC.Locale +# Description: DBC language settings. +# Default: 255 - (Auto Detect) +# 0 - (English) +# 1 - (Korean) +# 2 - (French) +# 3 - (German) +# 4 - (Chinese) +# 5 - (Taiwanese) +# 6 - (Spanish) +# 7 - (Spanish Mexico) +# 8 - (Russian) + +DBC.Locale = 255 + +# +# DeclinedNames +# Description: Allow Russian clients to set and use declined names. +# Default: 0 - (Disabled, Except when the Russian RealmZone is set) +# 1 - (Enabled) + +DeclinedNames = 0 + +# +# Expansion +# Description: Allow server to use content from expansions. Checks for expansion-related +# map files, client compatibility and class/race character creation. +# Default: 2 - (Expansion 2) +# 1 - (Expansion 1) +# 0 - (Disabled, Ignore and disable expansion content (maps, races, classes) + +Expansion = 2 + +# +# MinPlayerName +# Description: Minimal player name length. +# Range: 1-12 +# Default: 2 + +MinPlayerName = 2 + +# +# MinCharterName +# Description: Minimal charter name length. +# Range: 1-24 +# Default: 2 + +MinCharterName = 2 + +# +# MinPetName +# Description: Minimal pet name length. +# Range: 1-12 +# Default: 2 + +MinPetName = 2 + +# +# Guild.CharterCost +# ArenaTeam.CharterCost.2v2 +# ArenaTeam.CharterCost.3v3 +# ArenaTeam.CharterCost.5v5 +# Description: Amount of money (in Copper) the petitions costs. +# Default: 1000 - (10 Silver) +# 800000 - (80 Gold) +# 1200000 - (120 Gold) +# 2000000 - (200 Gold) + +Guild.CharterCost = 1000 +ArenaTeam.CharterCost.2v2 = 800000 +ArenaTeam.CharterCost.3v3 = 1200000 +ArenaTeam.CharterCost.5v5 = 2000000 + +# +# MaxWhoListReturns +# Description: Set the max number of players returned in the /who list and interface. +# Default: 49 - (stable) + +MaxWhoListReturns = 49 + +# +# CharacterCreating.Disabled +# Description: Disable character creation for players based on faction. +# Default: 0 - (Enabled, All factions are allowed) +# 1 - (Disabled, Alliance) +# 2 - (Disabled, Horde) +# 3 - (Disabled, Both factions) + +CharacterCreating.Disabled = 0 + +# +# CharacterCreating.Disabled.RaceMask +# Description: Mask of races which cannot be created by players. +# Example: 1536 - (1024 + 512, Blood Elf and Draenei races are disabled) +# Default: 0 - (Enabled, All races are allowed) +# 1 - (Disabled, Human) +# 2 - (Disabled, Orc) +# 4 - (Disabled, Dwarf) +# 8 - (Disabled, Night Elf) +# 16 - (Disabled, Undead) +# 32 - (Disabled, Tauren) +# 64 - (Disabled, Gnome) +# 128 - (Disabled, Troll) +# 512 - (Disabled, Blood Elf) +# 1024 - (Disabled, Draenei) + +CharacterCreating.Disabled.RaceMask = 0 + +# +# CharacterCreating.Disabled.ClassMask +# Description: Mask of classes which cannot be created by players. +# Example: 288 - (32 + 256, Death Knight and Warlock classes are disabled) +# Default: 0 - (Enabled, All classes are allowed) +# 1 - (Disabled, Warrior) +# 2 - (Disabled, Paladin) +# 4 - (Disabled, Hunter) +# 8 - (Disabled, Rogue) +# 16 - (Disabled, Priest) +# 32 - (Disabled, Death Knight) +# 64 - (Disabled, Shaman) +# 128 - (Disabled, Mage) +# 256 - (Disabled, Warlock) +# 1024 - (Disabled, Druid) + +CharacterCreating.Disabled.ClassMask = 0 + +# +# CharactersPerAccount +# Description: Limit number of characters per account on all realms on this realmlist. +# Important: Number must be >= CharactersPerRealm +# Default: 50 + +CharactersPerAccount = 50 +# +# CharactersPerRealm +# Description: Limit number of characters per account on this realm. +# Range: 1-10 +# Default: 10 - (Client limitation) + +CharactersPerRealm = 10 + +# +# HeroicCharactersPerRealm +# Description: Limit number of heroic class characters per account on this realm. +# Range: 1-10 +# Default: 1 + +HeroicCharactersPerRealm = 1 + +# +# CharacterCreating.MinLevelForHeroicCharacter +# Description: Limit creating heroic characters only for account with another +# character of specific level (ignored for GM accounts) +# Default: 55 - (Enabled, Requires at least another level 55 character) +# 0 - (Disabled) +# 1 - (Enabled, Requires at least another level 1 character) + +CharacterCreating.MinLevelForHeroicCharacter = 55 + +# +# SkipCinematics +# Description: Disable cinematic intro at first login after character creation. +# Prevents buggy intros in case of custom start location coordinates. +# Default: 0 - (Show intro for each new character) +# 1 - (Show intro only for first character of selected race) +# 2 - (Disable intro for all classes) + +SkipCinematics = 0 + +# +# MaxPlayerLevel +# Description: Maximum level that can be reached by players. +# Important: Levels beyond 100 are not recommended at all. +# Range: 1-255 +# Default: 80 + +MaxPlayerLevel = 80 + +# +# MinDualSpecLevel +# Description: Level requirement for Dual Talent Specialization +# Default: 40 + +MinDualSpecLevel = 40 + +# +# StartPlayerLevel +# Description: Starting level for characters after creation. +# Range: 1-MaxPlayerLevel +# Default: 1 + +StartPlayerLevel = 1 + +# +# StartHeroicPlayerLevel +# Description: Staring level for heroic class characters after creation. +# Range: 1-MaxPlayerLevel +# Default: 55 + +StartHeroicPlayerLevel = 55 + +# +# StartPlayerMoney +# Description: Amount of money (in Copper) that a character has after creation. +# Default: 0 +# 100 - (1 Silver) + +StartPlayerMoney = 0 + +# +# MaxHonorPoints +# Description: Maximum honor points a character can have. +# Default: 75000 + +MaxHonorPoints = 75000 + +# +# MaxHonorPointsMoneyPerPoint +# Description: Convert excess honor points into money if players got more points than allowed after changing the honor cap. +# Honor points will be converted into copper according to the value set in this config. +# Default: 0 - Disabled + +MaxHonorPointsMoneyPerPoint = 0 + +# +# StartHonorPoints +# Description: Amount of honor points that characters have after creation. +# Default: 0 + +StartHonorPoints = 0 + +# +# MaxArenaPoints +# Description: Maximum arena points a character can have. +# Default: 10000 + +MaxArenaPoints = 10000 + +# +# StartArenaPoints +# Description: Amount of arena points that characters has after creation. +# Default: 0 + +StartArenaPoints = 0 + +# +# RecruitAFriend.MaxLevel +# Description: Highest level up to which a character can benefit from the Recruit-A-Friend +# experience multiplier. +# Default: 60 + +RecruitAFriend.MaxLevel = 60 + +# +# RecruitAFriend.MaxDifference +# Description: Highest level difference between linked Recruiter and Friend benefit from +# the Recruit-A-Friend experience multiplier. +# Default: 4 + +RecruitAFriend.MaxDifference = 4 + +# +# InstantLogout +# Description: Required security level for instantly logging out everywhere. +# Does not work while in combat, dueling or falling. +# Default: 1 - (Enabled, Mods/GMs/Admins) +# 0 - (Enabled, Everyone) +# 2 - (Enabled, GMs/Admins) +# 3 - (Enabled, Admins) +# 4 - (Disabled) + +InstantLogout = 1 + +# +# PreventAFKLogout +# Description: Prevent players AFK from being logged out +# Default: 0 - (Disabled) +# 1 - (Enabled, prevent players AFK from being logged out in Sanctuary zones) +# 2 - (Enabled, prevent players AFK from being logged out in all zones) + +PreventAFKLogout = 0 + +# +# DisableWaterBreath +# Description: Required security level for water breathing. +# Default: 4 - (Disabled) +# 0 - (Enabled, Everyone) +# 1 - (Enabled, Mods/GMs/Admins) +# 2 - (Enabled, GMs/Admins) +# 3 - (Enabled, Admins) + +DisableWaterBreath = 4 + +# +# AllFlightPaths +# Description: Character knows all flight paths (of both factions) after creation. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllFlightPaths = 0 + +# +# InstantFlightPaths +# Description: Flight paths will take players to their destination instantly instead +# of making them wait while flying. +# Default: 0 - (Disabled) +# 1 - (Enabled) +# 2 - (Enabled, but the player can toggle instant flight off or on at each flight master) + +InstantFlightPaths = 0 + +# +# AlwaysMaxSkillForLevel +# Description: Players will automatically gain max skill level when logging in or leveling +# up. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AlwaysMaxSkillForLevel = 0 + +# +# ActivateWeather +# Description: Activate the weather system. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +ActivateWeather = 1 + +# +# CastUnstuck +# Description: Allow casting the Unstuck spell using .start or unstuck button in client +# help options. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +CastUnstuck = 1 + +# +# Instance.IgnoreLevel +# Description: Ignore level requirement when entering instances. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Instance.IgnoreLevel = 0 + +# +# Instance.IgnoreRaid +# Description: Ignore raid group requirement when entering instances. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Instance.IgnoreRaid = 0 + +# +# Instance.GMSummonPlayer +# Description: Allow GM to summon players or only other GM accounts inside instances. +# Default: 0 - (Disabled, Only GM accounts can be summoned by GM) +# 1 - (Enabled, GM and Player accounts can be summoned by GM) + +Instance.GMSummonPlayer = 0 + +# +# Instance.ResetTimeHour +# Description: Hour of the day when the global instance reset occurs. +# Range: 0-23 +# Default: 4 - (04:00 AM) + +Instance.ResetTimeHour = 4 + +# +# Instance.UnloadDelay +# Description: Time (in milliseconds) before instance maps are unloaded from memory if no +# characters are inside. +# Default: 1800000 - (Enabled, 30 minutes) +# 0 - (Disabled, Instance maps are kept in memory until the instance +# resets) + +Instance.UnloadDelay = 1800000 + +# +# Quests.EnableQuestTracker +# Description: Store data in the database about quest completion and abandonment to help finding bugged quests. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Quests.EnableQuestTracker = 0 + +# +# Quests.LowLevelHideDiff +# Description: Level difference between player and quest level at which quests are +# considered low-level and are not shown via exclamation mark (!) at quest +# givers. +# Default: 4 - (Enabled, Hide quests that have 4 levels less than the character) +# -1 - (Disabled, Show all available quest marks) + +Quests.LowLevelHideDiff = 4 + +# +# Quests.HighLevelHideDiff +# Description: Level difference between player and quest level at which quests are +# considered high-level and are not shown via exclamation mark (!) at quest +# givers. +# Default: 7 - (Enabled, Hide quests that have 7 levels more than the character) +# -1 - (Disabled, Show all available quest marks) + +Quests.HighLevelHideDiff = 7 + +# +# Quests.IgnoreRaid +# Description: Allow non-raid quests to be completed while in a raid group. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Quests.IgnoreRaid = 0 + +# +# Quests.IgnoreAutoAccept +# Description: Ignore auto accept flag. Clients will have to manually accept all quests. +# Default: 0 - (Disabled, DB values determine if quest is marked auto accept or not.) +# 1 - (Enabled, clients will not be told to automatically accept any quest.) + +Quests.IgnoreAutoAccept = 0 + +# +# Quests.IgnoreAutoComplete +# Description: Ignore auto complete flag. Clients will have to manually complete all quests. +# Default: 0 - (Disabled, DB values determine if quest is marked auto complete or not.) +# 1 - (Enabled, clients will not be told to automatically complete any quest.) + +Quests.IgnoreAutoComplete = 0 + +# +# Calendar.DeleteOldEventsHour +# Description: Hour of the day when the daily deletion of old calendar events occurs. +# Range: 0-23 +# Default: 6 - (06:00 AM) + +Calendar.DeleteOldEventsHour = 6 + +# +# Guild.EventLogRecordsCount +# Description: Number of log entries for guild events that are stored per guild. Old entries +# will be overwritten if the number of log entries exceed the configured value. +# High numbers prevent this behavior but may have performance impacts. +# Default: 100 + +Guild.EventLogRecordsCount = 100 + +# +# Guild.ResetHour +# Description: Hour of the day when the daily cap resets occur. +# Range: 0-23 +# Default: 6 - (06:00 AM) + +Guild.ResetHour = 6 + +# +# Guild.BankEventLogRecordsCount +# Description: Number of log entries for guild bank events that are stored per guild. Old +# entries will be overwritten if the number of log entries exceed the +# configured value. High numbers prevent this behavior but may have performance +# impacts. +# Default: 25 - (Minimum) + +Guild.BankEventLogRecordsCount = 25 + +# +# MaxPrimaryTradeSkill +# Description: Maximum number of primary professions a character can learn. +# Range: 0-11 +# Default: 2 + +MaxPrimaryTradeSkill = 2 + +# +# MinPetitionSigns +# Description: Number of required signatures on charters to create a guild. +# Range: 0-9 +# Default: 9 + +MinPetitionSigns = 9 + +# +# MaxGroupXPDistance +# Description: Max distance to creature for group member to get experience at creature +# death. +# Default: 74 + +MaxGroupXPDistance = 74 + +# +# MaxRecruitAFriendBonusDistance +# Description: Max distance between character and and group to gain the Recruit-A-Friend +# XP multiplier. +# Default: 100 + +MaxRecruitAFriendBonusDistance = 100 + +# +# MailDeliveryDelay +# Description: Time (in seconds) mail delivery is delayed when sending items. +# Default: 3600 - (1 hour) + +MailDeliveryDelay = 3600 + +# +# SkillChance.Prospecting +# Description: Allow skill increase from prospecting. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +SkillChance.Prospecting = 0 + +# +# SkillChance.Milling +# Description: Allow skill increase from milling. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +SkillChance.Milling = 0 + +# +# OffhandCheckAtSpellUnlearn +# Description: Unlearning certain spells can change offhand weapon restrictions +# for equip slots. +# Default: 1 - (Recheck offhand slot weapon at unlearning a spell) +# 0 - (Recheck offhand slot weapon only at zone update) + +OffhandCheckAtSpellUnlearn = 1 + +# +# ClientCacheVersion +# Description: Client cache version for client cache data reset. Use any value different +# from DB and not recently been used to trigger client side cache reset. +# Default: 0 - (Use DB value from world DB db_version.cache_id field) + +ClientCacheVersion = 0 + +# +# Event.Announce +# Description: Announce events. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Event.Announce = 0 + +# +# BeepAtStart +# Description: Beep when the world server finished starting (Unix/Linux systems). +# Default: 1 - (Enabled) +# 0 - (Disabled) + +BeepAtStart = 1 + +# +# FlashAtStart +# Description: Flashes in taskbar when the world server finished starting. (Works on Windows only) +# Default: 1 - (Enabled) +# 0 - (Disabled) + +FlashAtStart = 1 + +# +# Motd +# Description: Message of the Day, displayed at login. +# Use '@' for a newline and be sure to escape special characters. +# Example: "Welcome to John\'s Server@" +# Default: "Welcome to an AzerothCore server." + +Motd = "Welcome to an AzerothCore server." + +# PLEASE NOTE: another (hardcoded) text will appear below the MOTD: +# +# "This server runs on AzerothCore www.azerothcore.org" +# +# All the AzerothCore contributors, as well as its father projects (MaNGOS, TrinityCore, etc..), +# have worked for free to provide you this software. Please do not remove the credits. +# +# Changing or removing such hardcoded text is considered a violation of our license +# and it's not allowed. We reserve the right to take legal action in case of violations. +# Furthermore, any kind of support will be always denied. +# +# All AzerothCore contributors and its father projects are publicly listed in +# our official repository. Credits to open source contributions should always be shown. +# + +# +# Server.LoginInfo +# Description: Display core version (.server info) on login. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Server.LoginInfo = 0 + +# +# Command.LookupMaxResults +# Description: Number of results being displayed using a .lookup command. +# Default: 0 - (Unlimited) + +Command.LookupMaxResults = 0 + +# +# AllowTickets +# Description: Allow/disallow sending new tickets. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +AllowTickets = 1 + +# DeletedCharacterTicketTrace +# Description: Keep trace of tickets opened by deleted characters +# gm_ticket.playerGuid will be 0, old GUID and character name +# will be included in gm_ticket.comment +# Default: 0 - (Disabled) +# 1 - (Enabled) + +DeletedCharacterTicketTrace = 0 + +# +# DungeonFinder.OptionsMask +# Description: Dungeon and raid finder system. +# Value is a bitmask consisting of: +# LFG_OPTION_ENABLE_DUNGEON_FINDER = 1, Enable the dungeon finder browser +# LFG_OPTION_ENABLE_RAID_BROWSER = 2, Enable the raid browser +# LFG_OPTION_ENABLE_SEASONAL_BOSSES = 4, Enable seasonal bosses + +# Default: 5 + +DungeonFinder.OptionsMask = 5 + +# +# AccountInstancesPerHour +# Description: Controls the max amount of different instances player can enter within hour +# Default: 5 + +AccountInstancesPerHour = 5 + +# +# BirthdayTime +# Description: Set to date of project's birth in UNIX time. By default Thu Oct 2, 2008 +# Default: 1222964635 +# +# + +BirthdayTime = 1222964635 + +# +# IsContinentTransport.Enabled +# Description: Controls the continent transport (ships, zeppelins etc..) +# Default: 1 - (Enabled) +# +# + +IsContinentTransport.Enabled = 1 + +# +# IsPreloadedContinentTransport.Enabled +# Description: Should we preload the transport? +# (Not recommended on low-end servers as it consumes 100% more ram) +# and it's not really necessary to be enabled. +# +# Default: 0 - (Disabled) +# +# + +IsPreloadedContinentTransport.Enabled = 0 + +# +################################################################################################### + +################################################################################################### +# CRYPTOGRAPHY +# +# TOTPMasterSecret +# Description: The key used by authserver to decrypt TOTP secrets from database storage. +# You only need to set this here if you plan to use the in-game 2FA +# management commands (.account 2fa), otherwise this can be left blank. +# +# The server will auto-detect if this does not match your authserver setting, +# in which case any commands reliant on the secret will be disabled. +# +# Default: <blank> +# + +TOTPMasterSecret = + +# +################################################################################################### + +################################################################################################### +# UPDATE SETTINGS +# +# Updates.EnableDatabases +# Description: A mask that describes which databases shall be updated. +# +# Following flags are available +# DATABASE_LOGIN = 1, // Auth database +# DATABASE_CHARACTER = 2, // Character database +# DATABASE_WORLD = 4, // World database +# +# Default: 7 - (All enabled) +# 4 - (Enable world only) +# 0 - (All disabled) + +Updates.EnableDatabases = 7 + +# +# Updates.AutoSetup +# Description: Auto populate empty databases. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.AutoSetup = 1 + +# +# Updates.Redundancy +# Description: Perform data redundancy checks through hashing +# to detect changes on sql updates and reapply it. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.Redundancy = 1 + +# +# Updates.ArchivedRedundancy +# Description: Check hashes of archived updates (slows down startup). +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Updates.ArchivedRedundancy = 0 + +# +# Updates.AllowRehash +# Description: Inserts the current file hash in the database if it is left empty. +# Useful if you want to mark a file as applied but you don't know its hash. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Updates.AllowRehash = 1 + +# +# Updates.CleanDeadRefMaxCount +# Description: Cleans dead/ orphaned references that occur if an update was removed or renamed and edited in one step. +# It only starts the clean up if the count of the missing updates is below or equal the Updates.CleanDeadRefMaxCount value. +# This way prevents erasing of the update history due to wrong source directory state (maybe wrong branch or bad revision). +# Disable this if you want to know if the database is in a possible "dirty state". +# Default: 3 - (Enabled) +# 0 - (Disabled) +# -1 - (Enabled - unlimited) + +Updates.CleanDeadRefMaxCount = 3 + +# +################################################################################################### + +################################################################################################### +# WARDEN SETTINGS +# +# Warden.Enabled +# Description: Enable Warden anti-cheat system. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Warden.Enabled = 1 + +# +# Warden.NumMemChecks +# Description: Number of Warden memory checks that are sent to the client each cycle. +# Default: 3 - (Enabled) +# 0 - (Disabled) + +Warden.NumMemChecks = 3 + +# +# Warden.NumLuaChecks +# Description: Number of Warden LUA checks that are sent to the client each cycle. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Warden.NumLuaChecks = 1 + +# +# Warden.NumOtherChecks +# Description: Number of Warden checks other than memory checks that are added to request +# each checking cycle. +# Default: 7 - (Enabled) +# 0 - (Disabled) + +Warden.NumOtherChecks = 7 + +# +# Warden.ClientResponseDelay +# Description: Time (in seconds) before client is getting disconnecting for not responding. +# Default: 600 - (10 Minutes) +# 0 - (Disabled, client won't be kicked) + +Warden.ClientResponseDelay = 600 + +# +# Warden.ClientCheckHoldOff +# Description: Time (in seconds) to wait before sending the next check request to the client. +# A low number increases traffic and load on client and server side. +# Default: 30 - (30 Seconds) +# 0 - (Send check as soon as possible) + +Warden.ClientCheckHoldOff = 30 + +# +# Warden.ClientCheckFailAction +# Description: Default action being taken if a client check failed. Actions can be +# overwritten for each single check via warden_action table in characters +# database. +# Default: 0 - (Disabled, Logging only) +# 1 - (Kick) +# 2 - (Ban) + +Warden.ClientCheckFailAction = 0 + +# +# Warden.BanDuration +# Description: Time (in seconds) an account will be banned if ClientCheckFailAction is set +# to ban. +# Default: 86400 - (24 hours) +# 0 - (Permanent ban) + +Warden.BanDuration = 259200 + +# +################################################################################################### + +################################################################################################### +# PLAYER INTERACTION +# +# AllowTwoSide.Accounts +# Description: Allow creating characters of both factions on the same account. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +AllowTwoSide.Accounts = 1 + +# +# AllowTwoSide.Interaction.Calendar +# Description: Allow calendar invites between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Calendar = 0 + +# +# AllowTwoSide.Interaction.Chat +# Description: Allow say chat between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Chat = 0 + +# +# AllowTwoSide.Interaction.Emote +# Description: Allow emote messages between factions (e.g. "/e looks into the sky") +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Emote = 0 + +# +# AllowTwoSide.Interaction.Channel +# Description: Allow channel chat between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Channel = 0 + +# +# AllowTwoSide.Interaction.Group +# Description: Allow group joining between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Group = 0 + +# +# AllowTwoSide.Interaction.Guild +# Description: Allow guild joining between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Guild = 0 + +# +# AllowTwoSide.Interaction.Auction +# Description: Allow auctions between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Auction = 0 + +# +# AllowTwoSide.Interaction.Mail +# Description: Allow sending mails between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Interaction.Mail = 0 + +# +# AllowTwoSide.WhoList +# Description: Show characters from both factions in the /who list. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.WhoList = 0 + +# +# AllowTwoSide.AddFriend +# Description: Allow adding friends from other faction the friends list. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.AddFriend = 0 + +# +# AllowTwoSide.Trade +# Description: Allow trading between factions. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AllowTwoSide.Trade = 0 + +# +# TalentsInspecting +# Description: Allow inspecting characters from the opposing faction. +# Doesn't affect characters in gamemaster mode. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +TalentsInspecting = 1 + +# +################################################################################################### + +################################################################################################### +# CREATURE SETTINGS +# +# ThreatRadius +# Description: Distance for creatures to evade after being pulled away from the combat +# starting point. If ThreatRadius is less than creature aggro radius then aggro +# radius will be used. +# Default: 60 + +ThreatRadius = 60 + +# +# Rate.Creature.Aggro +# Description: Aggro radius percentage. +# Default: 1 - (Enabled, 100%) +# 1.5 - (Enabled, 150%) +# 0 - (Disabled, 0%) + +Rate.Creature.Aggro = 1 + +# +# CreatureFamilyFleeAssistanceRadius +# Description: Distance for fleeing creatures seeking assistance from other creatures. +# Default: 30 - (Enabled) +# 0 - (Disabled) + +CreatureFamilyFleeAssistanceRadius = 30 + +# +# CreatureFamilyAssistanceRadius +# Description: Distance for creatures calling for assistance from other creatures without +# moving. +# Default: 10 - (Enabled) +# 0 - (Disabled) + +CreatureFamilyAssistanceRadius = 10 + +# +# CreatureFamilyAssistanceDelay +# Description: Time (in milliseconds) before creature assistance call. +# Default: 2000 - (2 Seconds) + +CreatureFamilyAssistanceDelay = 2000 + +# +# CreatureFamilyAssistancePeriod +# Description: Time (in milliseconds) before next creature assistance call. +# Default: 3000 - (3 Seconds) +# 0 - (Disabled) + +CreatureFamilyAssistancePeriod = 3000 + +# +# CreatureFamilyFleeDelay +# Description: Time (in milliseconds) during which creature can flee if no assistance was +# found. +# Default: 7000 (7 Seconds) + +CreatureFamilyFleeDelay = 7000 + +# +# WorldBossLevelDiff +# Description: World boss level difference. +# Default: 3 + +WorldBossLevelDiff = 3 + +# +# Corpse.Decay.NORMAL +# Corpse.Decay.RARE +# Corpse.Decay.ELITE +# Corpse.Decay.RAREELITE +# Corpse.Decay.WORLDBOSS +# Description: Time (in seconds) until creature corpse will decay if not looted or skinned. +# Default: 60 - (1 Minute, Corpse.Decay.NORMAL) +# 300 - (5 Minutes, Corpse.Decay.RARE) +# 300 - (5 Minutes, Corpse.Decay.ELITE) +# 300 - (5 Minutes, Corpse.Decay.RAREELITE) +# 3600 - (1 Hour, Corpse.Decay.WORLDBOSS) + +Corpse.Decay.NORMAL = 60 +Corpse.Decay.RARE = 300 +Corpse.Decay.ELITE = 300 +Corpse.Decay.RAREELITE = 300 +Corpse.Decay.WORLDBOSS = 3600 + +# +# Rate.Corpse.Decay.Looted +# Description: Multiplier for Corpse.Decay.* to configure how long creature corpses stay +# after they have been looted. +# Default: 0.5 + +Rate.Corpse.Decay.Looted = 0.5 + +# +# Rate.Creature.Normal.Damage +# Rate.Creature.Elite.Elite.Damage +# Rate.Creature.Elite.RARE.Damage +# Rate.Creature.Elite.RAREELITE.Damage +# Rate.Creature.Elite.WORLDBOSS.Damage +# Description: Multiplier for creature melee damage. +# Default: 1 - (Rate.Creature.Normal.Damage) +# 1 - (Rate.Creature.Elite.Elite.Damage) +# 1 - (Rate.Creature.Elite.RARE.Damage) +# 1 - (Rate.Creature.Elite.RAREELITE.Damage) +# 1 - (Rate.Creature.Elite.WORLDBOSS.Damage) +# + +Rate.Creature.Normal.Damage = 1 +Rate.Creature.Elite.Elite.Damage = 1 +Rate.Creature.Elite.RARE.Damage = 1 +Rate.Creature.Elite.RAREELITE.Damage = 1 +Rate.Creature.Elite.WORLDBOSS.Damage = 1 + +# +# Rate.Creature.Normal.SpellDamage +# Rate.Creature.Elite.Elite.SpellDamage +# Rate.Creature.Elite.RARE.SpellDamage +# Rate.Creature.Elite.RAREELITE.SpellDamage +# Rate.Creature.Elite.WORLDBOSS.SpellDamage +# Description: Multiplier for creature spell damage. +# Default: 1 - (Rate.Creature.Normal.SpellDamage) +# 1 - (Rate.Creature.Elite.Elite.SpellDamage) +# 1 - (Rate.Creature.Elite.RARE.SpellDamage) +# 1 - (Rate.Creature.Elite.RAREELITE.SpellDamage) +# 1 - (Rate.Creature.Elite.WORLDBOSS.SpellDamage) + +Rate.Creature.Normal.SpellDamage = 1 +Rate.Creature.Elite.Elite.SpellDamage = 1 +Rate.Creature.Elite.RARE.SpellDamage = 1 +Rate.Creature.Elite.RAREELITE.SpellDamage = 1 +Rate.Creature.Elite.WORLDBOSS.SpellDamage = 1 + +# +# Rate.Creature.Normal.HP +# Rate.Creature.Elite.Elite.HP +# Rate.Creature.Elite.RARE.HP +# Rate.Creature.Elite.RAREELITE.HP +# Rate.Creature.Elite.WORLDBOSS.HP +# Description: Multiplier for creature health. +# Default: 1 - (Rate.Creature.Normal.HP) +# 1 - (Rate.Creature.Elite.Elite.HP) +# 1 - (Rate.Creature.Elite.RARE.HP) +# 1 - (Rate.Creature.Elite.RAREELITE.HP) +# 1 - (Rate.Creature.Elite.WORLDBOSS.HP) + +Rate.Creature.Normal.HP = 1 +Rate.Creature.Elite.Elite.HP = 1 +Rate.Creature.Elite.RARE.HP = 1 +Rate.Creature.Elite.RAREELITE.HP = 1 +Rate.Creature.Elite.WORLDBOSS.HP = 1 + +# +# ListenRange.Say +# Description: Distance in which players can read say messages from creatures or +# gameobjects. +# Default: 40 + +ListenRange.Say = 40 + +# +# ListenRange.TextEmote +# Description: Distance in which players can read emotes from creatures or gameobjects. +# Default: 40 + +ListenRange.TextEmote = 40 + +# +# ListenRange.Yell +# Description: Distance in which players can read yell messages from creatures or +# gameobjects. +# Default: 300 + +ListenRange.Yell = 300 + +# +# Creature.MovingStopTimeForPlayer +# Description: Time (in milliseconds) during which creature will not move after +# interaction with player. +# Default: 180000 + +Creature.MovingStopTimeForPlayer = 180000 + +# WaypointMovementStopTimeForPlayer +# Description: Specifies the time (in seconds) that a creature with waypoint +# movement will wait after a player interacts with it. +# default: 120 + +WaypointMovementStopTimeForPlayer = 120 + +# NpcEvadeIfTargetIsUnreachable +# Description: Specifies the time (in seconds) that a creature whom target +# is unreachable to end up in evade mode. +# Default: 5 + +NpcEvadeIfTargetIsUnreachable = 5 + +# NpcRegenHPIfTargetIsUnreachable +# Description: Regenerates HP for Creatures in Raids if they cannot reach the target. +# Keep disabled if you are experiencing mmaps/pathing issues. +# +# Default: 1 - (Enabled) +# 0 - (Disabled) + +NpcRegenHPIfTargetIsUnreachable = 1 + +# NpcRegenHPTimeIfTargetIsUnreachable +# Description: Specifies the time (in seconds) that a creature whom target +# is unreachable in raid to end up regenerate health. +# Default: 10 + +NpcRegenHPTimeIfTargetIsUnreachable = 10 + +# Creatures.CustomIDs +# Description: The list of custom creatures with gossip dialogues hardcoded in core, +# divided by "," without spaces. +# It is implied that you do not use for these NPC dialogs data from "gossip_menu" table. +# Server will skip these IDs during the definitions validation process. +# Example: Creatures.CustomIDs = "190010,55005,999991,25462,98888,601014" - Npcs for Transmog, Guild-zone, 1v1-arena, Skip Dk, +# Racial Trait Swap, NPC - All Mounts Modules +# Default: "" + +Creatures.CustomIDs = "190010,55005,999991,25462,98888,601014,34567,34568" + +# +################################################################################################### + +################################################################################################### +# CHAT SETTINGS +# +# ChatFakeMessagePreventing +# Description: Additional protection from creating fake chat messages using spaces. +# Collapses multiple subsequent whitespaces into a single whitespace. +# Not applied to the addon language, but may break old addons that use +# "normal" chat messages for sending data to other clients. +# Default: 1 - (Enabled, Blizzlike) +# 0 - (Disabled) +# + +ChatFakeMessagePreventing = 1 + +# +# ChatStrictLinkChecking.Severity +# Description: Check chat messages for in-game links to spells, items, quests, etc. +# -1 - (Only verify validity of link data, but permit use of custom colors) +# Default: 0 - (Only verify that link data and color are valid without checking text) +# 1 - (Additionally verifies that the link text matches the provided data) +# +# Note: If this is set to '1', you must additionally provide .dbc files for all +# client locales that are in use on your server. +# If any files are missing, messages with links from clients using those +# locales will likely be blocked by the server. +# + +ChatStrictLinkChecking.Severity = 0 + +# +# ChatStrictLinkChecking.Kick +# Description: Defines what should be done if a message containing invalid control characters +# is received. +# Default: 0 - (Silently ignore message) +# 1 - (Ignore message and kick player) +# + +ChatStrictLinkChecking.Kick = 0 + +# +# ChatFlood.MessageCount +# Description: Chat flood protection, number of messages before player gets muted. +# Default: 10 - (Enabled) +# 0 - (Disabled) + +ChatFlood.MessageCount = 10 + +# +# ChatFlood.MessageDelay +# Description: Time (in seconds) between messages to be counted into ChatFlood.MessageCount. +# Default: 1 + +ChatFlood.MessageDelay = 1 + +# +# ChatFlood.MuteTime +# Description: Time (in seconds) characters get muted for violating ChatFlood.MessageCount. +# Default: 10 + +ChatFlood.MuteTime = 10 + +# +# Chat.MuteFirstLogin +# Description: Speaking is allowed after playing for Chat.MuteTimeFirstLogin minutes. You may use party and guild chat. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Chat.MuteFirstLogin = 0 + +# +# Chat.MuteTimeFirstLogin +# Description: The time after which the player will be able to speak. +# Default: 120 - (Minutes) + +Chat.MuteTimeFirstLogin = 120 + +# +# Channel.RestrictedLfg +# Description: Restrict LookupForGroup channel to characters registered in the LFG tool. +# Default: 1 - (Enabled, Allow join to channel only if registered in LFG) +# 0 - (Disabled, Allow join to channel in any time) + +Channel.RestrictedLfg = 1 + +# +# Channel.SilentlyGMJoin +# Description: Silently join GM characters to channels. If set to 1, channel kick and ban +# commands issued by a GM will not be broadcasted. +# Default: 0 - (Disabled, Join with announcement) +# 1 - (Enabled, Join without announcement) + +Channel.SilentlyGMJoin = 0 + +# Channel.ModerationGMLevel +# Min GM account security level required for executing moderator in-game commands in the channels +# This also bypasses password prompts on joining channels which require password +# 0 (in-game channel moderator privileges only) +# Default: 1 (enabled for moderators and above) + +Channel.ModerationGMLevel = 1 + +# +# ChatLevelReq.Channel +# Description: Level requirement for characters to be able to write in chat channels. +# Default: 1 + +ChatLevelReq.Channel = 1 + +# +# ChatLevelReq.Whisper +# Description: Level requirement for characters to be able to whisper other characters. +# Default: 1 + +ChatLevelReq.Whisper = 1 + +# +# ChatLevelReq.Say +# Description: Level requirement for characters to be able to use say/yell/emote. +# Default: 1 + +ChatLevelReq.Say = 1 + +# +# PartyLevelReq +# Description: Minimum level at which players can invite to group, even if they aren't on +# the invite friends list. (Players who are on that friend list can always +# invite despite having lower level) +# Default: 1 + +PartyLevelReq = 1 + +# +# AllowPlayerCommands +# Description: Allow players to use commands. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +AllowPlayerCommands = 1 + +# +# PreserveCustomChannels +# Description: Store custom chat channel settings like password, automatic ownership handout +# or ban list in the database. Needs to be enabled to save custom +# world/trade/etc. channels that have automatic ownership handout disabled. +# (.channel set ownership $channel off) +# Default: 0 - (Disabled, Blizzlike, Channel settings are lost if last person left) +# 1 - (Enabled) + +PreserveCustomChannels = 1 + +# +# PreserveCustomChannelDuration +# Description: Time (in days) that needs to pass before the customs chat channels get +# cleaned up from the database. Only channels with ownership handout enabled +# (default behavior) will be cleaned. +# Default: 14 - (Enabled, Clean channels that haven't been used for 14 days) +# 0 - (Disabled, Infinite channel storage) + +PreserveCustomChannelDuration = 14 + +# +################################################################################################### + +################################################################################################### +# GAME MASTER SETTINGS +# +# GM.LoginState +# Description: GM mode at login. +# Default: 2 - (Last save state) +# 0 - (Disable) +# 1 - (Enable) + +GM.LoginState = 2 + +# +# GM.Visible +# Description: GM visibility at login. +# Default: 2 - (Last save state) +# 0 - (Invisible) +# 1 - (Visible) + +GM.Visible = 2 + +# +# GM.Chat +# Description: GM chat mode at login. +# Default: 2 - (Last save state) +# 0 - (Disable) +# 1 - (Enable) + +GM.Chat = 2 + +# +# GM.WhisperingTo +# Description: Is GM accepting whispers from player by default or not. +# Default: 2 - (Last save state) +# 0 - (Disable) +# 1 - (Enable) + +GM.WhisperingTo = 2 + +# +# GM.InGMList.Level +# Description: Maximum GM level shown in GM list (if enabled) in non-GM state (.gm off). +# Default: 3 - (Anyone) +# 0 - (Only players) +# 1 - (Only moderators) +# 2 - (Only gamemasters) + +GM.InGMList.Level = 3 + +# +# GM.InWhoList.Level +# Description: Max GM level showed in who list (if visible). +# Default: 3 - (Anyone) +# 0 - (Only players) +# 1 - (Only moderators) +# 2 - (Only gamemasters) + +GM.InWhoList.Level = 3 + +# +# GM.StartLevel +# Description: GM character starting level. +# Default: 1 + +GM.StartLevel = 1 + +# +# GM.AllowInvite +# Description: Allow players to invite GM characters. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +GM.AllowInvite = 0 + +# +# GM.AllowFriend +# Description: Allow players to add GM characters to their friends list. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +GM.AllowFriend = 0 + +# +# GM.LowerSecurity +# Description: Allow lower security levels to use commands on higher security level +# characters. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +GM.LowerSecurity = 0 + +# +# GM.TicketSystem.ChanceOfGMSurvey +# Description: Chance of sending a GM survey after ticket completion. +# Default: 50 - (Enabled) +# 0 - (Disabled) + +GM.TicketSystem.ChanceOfGMSurvey = 50 + +# +################################################################################################### + +################################################################################################### +# VISIBILITY AND DISTANCES +# +# Visibility.GroupMode +# Description: Group visibility modes. Defines which groups can aways detect invisible +# characters of the same raid, group or faction. +# Default: 1 - (Raid) +# 0 - (Party) +# 2 - (Faction) + +Visibility.GroupMode = 1 + +# +# Visibility.Distance.Continents +# Visibility.Distance.Instances +# Visibility.Distance.BGArenas +# Description: Visibility distance to see other players or gameobjects. +# Visibility on continents on retail ~90 yards. In BG/Arenas ~180. +# For instances default ~120. +# Max limited by active player zone: ~ 333 +# Min limit is max aggro radius (45) * Rate.Creature.Aggro +# Default: 90 - (Visibility.Distance.Continents) +# 120 - (Visibility.Distance.Instances) +# 180 - (Visibility.Distance.BGArenas) + +Visibility.Distance.Continents = 90 +Visibility.Distance.Instances = 120 +Visibility.Distance.BGArenas = 180 + +# +# Visibility.Notify.Period.OnContinents +# Visibility.Notify.Period.InInstances +# Visibility.Notify.Period.InBGArenas +# Description: Time (in milliseconds) for visibility update period. Lower values may have +# performance impact. +# Default: 1000 - (Visibility.Notify.Period.OnContinents) +# 1000 - (Visibility.Notify.Period.InInstances) +# 1000 - (Visibility.Notify.Period.InBGArenas) + +Visibility.Notify.Period.OnContinents = 1000 +Visibility.Notify.Period.InInstances = 1000 +Visibility.Notify.Period.InBGArenas = 1000 + +# +################################################################################################### + +################################################################################################### +# SERVER RATES +# +# Rate.Health +# Rate.Mana +# Rate.Rage.Income +# Rate.Rage.Loss +# Rate.RunicPower.Income +# Rate.RunicPower.Loss +# Rate.Focus +# Rate.Energy +# Rate.Loyalty +# Description: Multiplier to configure health, mana, incoming rage, loss of rage, focus +# energy and loyalty increase or decrease. +# Default: 1 - (Rate.Health) +# 1 - (Rate.Mana) +# 1 - (Rate.Rage.Income) +# 1 - (Rate.Rage.Loss) +# 1 - (Rate.RunicPower.Income) +# 1 - (Rate.RunicPower.Loss) +# 1 - (Rate.Focus) +# 1 - (Rate.Energy) +# 1 - (Rate.Loyalty) + +Rate.Health = 1 +Rate.Mana = 1 +Rate.Rage.Income = 1 +Rate.Rage.Loss = 1 +Rate.RunicPower.Income = 1 +Rate.RunicPower.Loss = 1 +Rate.Focus = 1 +Rate.Energy = 1 +Rate.Loyalty = 1 + +# +# Rate.Skill.Discovery +# Description: Multiplier for skill discovery. +# Default: 1 + +Rate.Skill.Discovery = 1 + +# +# Rate.Drop.Item.Poor +# Rate.Drop.Item.Normal +# Rate.Drop.Item.Uncommon +# Rate.Drop.Item.Rare +# Rate.Drop.Item.Epic +# Rate.Drop.Item.Legendary +# Rate.Drop.Item.Artifact +# Rate.Drop.Item.Referenced +# Rate.Drop.Money +# Description: Drop rates for money and items based on quality. +# Default: 1 - (Rate.Drop.Item.Poor) +# 1 - (Rate.Drop.Item.Normal) +# 1 - (Rate.Drop.Item.Uncommon) +# 1 - (Rate.Drop.Item.Rare) +# 1 - (Rate.Drop.Item.Epic) +# 1 - (Rate.Drop.Item.Legendary) +# 1 - (Rate.Drop.Item.Artifact) +# 1 - (Rate.Drop.Item.Referenced) +# 1 - (Rate.Drop.Money) + +Rate.Drop.Item.Poor = 1 +Rate.Drop.Item.Normal = 1 +Rate.Drop.Item.Uncommon = 1 +Rate.Drop.Item.Rare = 1 +Rate.Drop.Item.Epic = 1 +Rate.Drop.Item.Legendary = 1 +Rate.Drop.Item.Artifact = 1 +Rate.Drop.Item.Referenced = 1 +Rate.Drop.Money = 1 + +# RewardBonusMoney +# Description: Allows to further tweak the amount of extra money rewarded by quests when the player +# is at MaxPlayerLevel (this amount is specified in quest_template.RewardBonusMoney). +# NOTE: the final amount will also affected by Rate.Drop.Money +# Default: 1 + +Rate.RewardBonusMoney = 1 + +# +# Rate.SellValue.Item.Poor +# Rate.SellValue.Item.Normal +# Rate.SellValue.Item.Uncommon +# Rate.SellValue.Item.Rare +# Rate.SellValue.Item.Epic +# Rate.SellValue.Item.Legendary +# Rate.SellValue.Item.Artifact +# Rate.SellValue.Item.Heirloom +# Description: Item Sale Value rates based on quality. +# Default: 1 - (Rate.SellValue.Item.Poor) +# 1 - (Rate.SellValue.Item.Normal) +# 1 - (Rate.SellValue.Item.Uncommon) +# 1 - (Rate.SellValue.Item.Rare) +# 1 - (Rate.SellValue.Item.Epic) +# 1 - (Rate.SellValue.Item.Legendary) +# 1 - (Rate.SellValue.Item.Artifact) +# 1 - (Rate.SellValue.Item.Heirloom) + +Rate.SellValue.Item.Poor = 1 +Rate.SellValue.Item.Normal = 1 +Rate.SellValue.Item.Uncommon = 1 +Rate.SellValue.Item.Rare = 1 +Rate.SellValue.Item.Epic = 1 +Rate.SellValue.Item.Legendary = 1 +Rate.SellValue.Item.Artifact = 1 +Rate.SellValue.Item.Heirloom = 1 + +# +# Rate.BuyValue.Item.Poor +# Rate.BuyValue.Item.Normal +# Rate.BuyValue.Item.Uncommon +# Rate.BuyValue.Item.Rare +# Rate.BuyValue.Item.Epic +# Rate.BuyValue.Item.Legendary +# Rate.BuyValue.Item.Artifact +# Rate.BuyValue.Item.Heirloom +# Description: Item Sale Value rates based on quality. +# Default: 1 - (Rate.BuyValue.Item.Poor) +# 1 - (Rate.BuyValue.Item.Normal) +# 1 - (Rate.BuyValue.Item.Uncommon) +# 1 - (Rate.BuyValue.Item.Rare) +# 1 - (Rate.BuyValue.Item.Epic) +# 1 - (Rate.BuyValue.Item.Legendary) +# 1 - (Rate.BuyValue.Item.Artifact) +# 1 - (Rate.BuyValue.Item.Heirloom) + +Rate.BuyValue.Item.Poor = 1 +Rate.BuyValue.Item.Normal = 1 +Rate.BuyValue.Item.Uncommon = 1 +Rate.BuyValue.Item.Rare = 1 +Rate.BuyValue.Item.Epic = 1 +Rate.BuyValue.Item.Legendary = 1 +Rate.BuyValue.Item.Artifact = 1 +Rate.BuyValue.Item.Heirloom = 1 + +# +# Rate.Drop.Item.ReferencedAmount +# Description: Multiplier for referenced loot amount. +# Default: 1 + +Rate.Drop.Item.ReferencedAmount = 1 + +# +# Rate.XP.Kill +# Rate.XP.Quest +# Rate.XP.Explore +# Rate.XP.Pet +# Description: Experience rates (outside battleground) +# Default: 1 - (Rate.XP.Kill) +# 1 - (Rate.XP.Quest) +# 1 - (Rate.XP.Quest.DF) - Dungeon Finder/LFG quests only. +# 1 - (Rate.XP.Explore) +# 1 - (Rate.XP.Pet) + +Rate.XP.Kill = 1 +Rate.XP.Quest = 1 +Rate.XP.Quest.DF = 1 +Rate.XP.Explore = 1 +Rate.XP.Pet = 1 + +# +# Rate.XP.BattlegroundKill... +# Description: Experience rate for honorable kills in battlegrounds. Not affected by Rate.XP.Kill. Defined for each battleground. +# Only works if Battleground.GiveXPForKills = 1 +# Default: 1 + +Rate.XP.BattlegroundKillAV = 1 +Rate.XP.BattlegroundKillWSG = 1 +Rate.XP.BattlegroundKillAB = 1 +Rate.XP.BattlegroundKillEOTS = 1 +Rate.XP.BattlegroundKillSOTA = 1 +Rate.XP.BattlegroundKillIC = 1 + +# +# Rate.RepairCost +# Description: Repair cost rate. +# Default: 1 + +Rate.RepairCost = 1 + +# +# Rate.Rest.InGame +# Rate.Rest.Offline.InTavernOrCity +# Rate.Rest.Offline.InWilderness +# Description: Resting points grow rates. +# Default: 1 - (Rate.Rest.InGame) +# 1 - (Rate.Rest.Offline.InTavernOrCity) +# 1 - (Rate.Rest.Offline.InWilderness) + +Rate.Rest.InGame = 1 +Rate.Rest.Offline.InTavernOrCity = 1 +Rate.Rest.Offline.InWilderness = 1 + +# +# Rate.Damage.Fall +# Description: Damage after fall rate. +# Default: 1 + +Rate.Damage.Fall = 1 + +# +# Rate.Auction.Time +# Rate.Auction.Deposit +# Rate.Auction.Cut +# Description: Auction rates (auction time, deposit get at auction start, +# auction cut from price at auction end) +# Default: 1 - (Rate.Auction.Time) +# 1 - (Rate.Auction.Deposit) +# 1 - (Rate.Auction.Cut) + +Rate.Auction.Time = 1 +Rate.Auction.Deposit = 1 +Rate.Auction.Cut = 1 + +# +# Rate.Honor +# Description: Honor gain rate. +# Default: 1 + +Rate.Honor = 1 + +# +# Rate.ArenaPoints +# Description: Arena points gain rate. +# Default: 1 + +Rate.ArenaPoints = 1 + +# +# Rate.Talent +# Description: Talent point rate. +# Default: 1 + +Rate.Talent = 1 + +# +# Rate.Reputation.Gain +# Description: Reputation gain rate. +# Default: 1 + +Rate.Reputation.Gain = 1 + +# +# Rate.Reputation.LowLevel.Kill +# Description: Reputation gain from killing low level (grey) creatures. +# Default: 1 + +Rate.Reputation.LowLevel.Kill = 1 + +# +# Rate.Reputation.LowLevel.Quest +# Description: Reputation gain rate. +# Default: 1 + +Rate.Reputation.LowLevel.Quest = 1 + +# +# Rate.Reputation.RecruitAFriendBonus +# Description: Reputation bonus rate for recruit-a-friend. +# Default: 0.1 + +Rate.Reputation.RecruitAFriendBonus = 0.1 + +# +# Rate.MoveSpeed +# Description: Movement speed rate. +# Default: 1 + +Rate.MoveSpeed = 1 + +# +# Rate.InstanceResetTime +# Description: Multiplier for the rate between global raid/heroic instance resets +# (dbc value). Higher value increases the time between resets, +# lower value lowers the time, you need clean instance_reset in +# characters db in order to let new values work. +# Default: 1 + +Rate.InstanceResetTime = 1 + +# +# Rate.Pet.LevelXP +# Description: Modifies the amount of experience required to level up a pet. +# The lower the rate the less experience is required. +# Default: 0.05 +# +Rate.Pet.LevelXP = 0.05 + +# +# SkillGain.Crafting +# SkillGain.Defense +# SkillGain.Gathering +# SkillGain.Weapon +# Description: Crafting/defense/gathering/weapon skills gain rate. +# Default: 1 - (SkillGain.Crafting) +# 1 - (SkillGain.Defense) +# 1 - (SkillGain.Gathering) +# 1 - (SkillGain.Weapon) + +SkillGain.Crafting = 1 +SkillGain.Defense = 1 +SkillGain.Gathering = 1 +SkillGain.Weapon = 1 + +# +# SkillChance.Orange +# SkillChance.Yellow +# SkillChance.Green +# SkillChance.Grey +# Description: Chance to increase skill based on recipe color. +# Default: 100 - (SkillChance.Orange) +# 75 - (SkillChance.Yellow) +# 25 - (SkillChance.Green) +# 0 - (SkillChance.Grey) + +SkillChance.Orange = 100 +SkillChance.Yellow = 75 +SkillChance.Green = 25 +SkillChance.Grey = 0 + +# +# SkillChance.MiningSteps +# SkillChance.SkinningSteps +# Description: Skinning and Mining chance decreases with skill level. +# Default: 0 - (Disabled) +# 75 - (In 2 times each 75 skill points) + +SkillChance.MiningSteps = 0 +SkillChance.SkinningSteps = 0 + +# +# DurabilityLoss.InPvP +# Description: Durability loss on death during PvP. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +DurabilityLoss.InPvP = 0 + +# +# DurabilityLoss.OnDeath +# Description: Durability loss percentage on death. +# Note: On 3.3.5 client always shows log message "Your items have lost 10% durability" +# Default: 10 + +DurabilityLoss.OnDeath = 10 + +# +# DurabilityLossChance.Damage +# Description: Chance to lose durability on one equipped item from damage. +# Default: 0.5 - (100/0.5 = 200, Each 200 damage one equipped item will use durability) + +DurabilityLossChance.Damage = 0.5 + +# +# DurabilityLossChance.Absorb +# Description: Chance to lose durability on one equipped armor item when absorbing damage. +# Default: 0.5 - (100/0.5 = 200, Each 200 absorbed damage one equipped item will lose +# durability) + +DurabilityLossChance.Absorb = 0.5 + +# +# DurabilityLossChance.Parry +# Description: Chance to lose durability on main weapon when parrying attacks. +# Default: 0.05 - (100/0.05 = 2000, Each 2000 parried damage the main weapon will lose +# durability) + +DurabilityLossChance.Parry = 0.05 + +# +# DurabilityLossChance.Block +# Description: Chance to lose durability on shield when blocking attacks. +# Default: 0.05 - (100/0.05 = 2000, Each 2000 blocked damage the shield will lose +# durability) + +DurabilityLossChance.Block = 0.05 + +# +# Death.SicknessLevel +# Description: Starting level for resurrection sickness. +# Example: 11 - (Level 1-10 characters will not be affected, +# Level 11-19 characters will be affected for 1 minute, +# Level 20-MaxPlayerLevel characters will be affected for 10 minutes) +# Default: 11 - (Enabled, See Example) +# MaxPlayerLevel+1 - (Disabled) +# -10 - (Enabled, Level 1+ characters have 10 minute duration) + +Death.SicknessLevel = 11 + +# +# Death.CorpseReclaimDelay.PvP +# Death.CorpseReclaimDelay.PvE +# Description: Increase corpse reclaim delay at PvP/PvE deaths. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Death.CorpseReclaimDelay.PvP = 1 +Death.CorpseReclaimDelay.PvE = 0 + +# +# Death.Bones.World +# Death.Bones.BattlegroundOrArena +# Description: Create bones instead of corpses at resurrection in normal zones, instances, +# battleground or arenas. +# Default: 1 - (Enabled, Death.Bones.World) +# 1 - (Enabled, Death.Bones.BattlegroundOrArena) +# 0 - (Disabled) + +Death.Bones.World = 1 +Death.Bones.BattlegroundOrArena = 1 + +# +# Die.Command.Mode +# Description: Do not trigger things like loot from .die command. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Die.Command.Mode = 1 + +# Rate.MissChanceMultiplier.Creature +# Rate.MissChanceMultiplier.Player +# Rate.MissChanceMultiplier.OnlyAffectsPlayer +# +# Description: When the target is 3 or more level higher than the player, +# the chance to hit is determined by the formula: 94 - (levelDiff - 2) * Rate.MissChanceMultiplier +# The higher the Rate.MissChanceMultiplier constant, the higher is the chance to miss. +# +# Note: this does not affect when the player is less than 3 levels different than the target, +# where this (linear) formula is used instead to calculate the hit chance: 96 - levelDiff. +# You can set Rate.MissChanceMultiplier.OnlyAffectsPlayer to 1 if you only want to affect the MissChance +# for player casters only. This way you won't be affecting creature missing chance. +# +# Example: if you want the chance to keep growing linearly, use 1. +# +# Default: Rate.MissChanceMultiplier.TargetCreature = 11 +# Rate.MissChanceMultiplier.TargetPlayer = 7 +# Rate.MissChanceMultiplier.OnlyAffectsPlayer = 0 +# + +Rate.MissChanceMultiplier.TargetCreature = 11 +Rate.MissChanceMultiplier.TargetPlayer = 7 +Rate.MissChanceMultiplier.OnlyAffectsPlayer = 0 + +# +################################################################################################### + +################################################################################################### +# STATS LIMITS +# +# Stats.Limits.Enable +# Description: Enable or disable stats system limitations +# Default: 0 - Disabled +# 1 - Enabled + +Stats.Limits.Enable = 0 + +# +# Stats.Limit.[STAT] +# Description: Set percentage limit for dodge, parry, block and crit rating +# Default: 95.0 (95%) + +Stats.Limits.Dodge = 95.0 +Stats.Limits.Parry = 95.0 +Stats.Limits.Block = 95.0 +Stats.Limits.Crit = 95.0 + +# +################################################################################################### + +################################################################################################### +# AUTO BROADCAST +# +# AutoBroadcast.On +# Description: Enable auto broadcast. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AutoBroadcast.On = 0 + +# +# AutoBroadcast.Center +# Description: Auto broadcasting display method. +# Default: 0 - (Announce) +# 1 - (Notify) +# 2 - (Both) + +AutoBroadcast.Center = 0 + +# +# AutoBroadcast.Timer +# Description: Timer (in milliseconds) for auto broadcasts. +# Default: 60000 - (60 seconds) + +AutoBroadcast.Timer = 60000 + +# +# AutoBroadcast.MinDisableLevel +# Description: Minimum level required to disable autobroadcast announcements if EnablePlayerSettings option is enabled. +# Default: 0 - (Not allowed to disable it) + +AutoBroadcast.MinDisableLevel = 0 + +# +################################################################################################### + +################################################################################################### +# BATTLEGROUND CONFIG +# +# Battleground.CastDeserter +# Description: Cast Deserter spell at players who leave battlegrounds in progress. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Battleground.CastDeserter = 1 + +# +# Battleground.QueueAnnouncer.Enable +# Description: Announce battleground queue status to chat. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.QueueAnnouncer.Enable = 0 + +# +# Battleground.QueueAnnouncer.Limit.MinLevel +# Description: Limit the QueueAnnouncer starting from a certain level. +# When limited, it announces only if there are at least MinPlayers queued (see below) +# At 80 it only limits RBG, at lower level only limits Warsong Gulch. +# Default: 0 - (Disabled, no limits) +# 10 - (Enabled for all, because BGs start at 10) +# 20 - (Enabled for 20 and higher) +# 80 - (Enabled only for 80) + +Battleground.QueueAnnouncer.Limit.MinLevel = 0 + +# +# Battleground.QueueAnnouncer.Limit.MinPlayers +# Description: When the Battleground.QueueAnnouncer.Limit.MinLevel limit is enabled (not 0) +# only show when at least MinPlayers are queued. +# Default: 3 - (Show only when 3 or more players are queued) + +Battleground.QueueAnnouncer.Limit.MinPlayers = 3 + +# +# Battleground.QueueAnnouncer.SpamProtection.Delay +# Description: Show announce if player rejoined in queue after sec +# Default: 30 +# + +Battleground.QueueAnnouncer.SpamProtection.Delay = 30 + +# +# Battleground.QueueAnnouncer.PlayerOnly +# Description: Battleground queue announcement type. +# Default: 0 - (System message, Anyone can see it) +# 1 - (Private, Only queued players can see it) + +Battleground.QueueAnnouncer.PlayerOnly = 0 + +# +# Battleground.QueueAnnouncer.Timed +# Description: Enabled battleground queue announcements based on timer +# Default: 0 - (Disabled) +# 1 - (Enabled - Set Arena.QueueAnnouncer.Timer) +# + +Battleground.QueueAnnouncer.Timed = 0 + +# +# Battleground.QueueAnnouncer.Timer +# Description: Set timer for queue announcements +# Default: 30000 (30 sec) +# + +Battleground.QueueAnnouncer.Timer = 30000 + +# +# Battleground.PrematureFinishTimer +# Description: Time (in milliseconds) before battleground will end prematurely if there are +# not enough players on one team. (Values defined in battleground template) +# Default: 300000 - (Enabled, 5 minutes) +# 0 - (Disabled, Not recommended) + +Battleground.PrematureFinishTimer = 300000 + +# +# Battleground.PremadeGroupWaitForMatch +# Description: Time (in milliseconds) a pre-made group has to wait for matching group of the +# other faction. +# Default: 1800000 - (Enabled, 30 minutes) +# 0 - (Disabled, Not recommended) + +Battleground.PremadeGroupWaitForMatch = 1800000 + +# +# Battleground.GiveXPForKills +# Description: Give experience for honorable kills in battlegrounds. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.GiveXPForKills = 0 + +# +# Battleground.Random.ResetHour +# Description: Hour of the day when the global instance resets occur. +# Range: 0-23 +# Default: 6 - (06:00 AM) + +Battleground.Random.ResetHour = 6 + +# Battleground.StoreStatistics.Enable +# Description: Store Battleground scores in the database. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.StoreStatistics.Enable = 1 + +# Battleground.TrackDeserters.Enable +# Description: Track deserters of Battlegrounds. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.TrackDeserters.Enable = 1 + +# +# Battleground.InvitationType +# Description: Set Battleground invitation type. +# Default: 0 - (Normal, Invite as much players to battlegrounds as queued, +# Don't bother with balance) +# 1 - (Experimental, Don't allow to invite much more players +# of one faction) +# 2 - (Experimental, Try to have even teams) + +Battleground.InvitationType = 0 + +# +# Battleground.ReportAFK.Timer +# Description: After a few minutes that battle started you can report the player. +# Default: 4 + +Battleground.ReportAFK.Timer = 4 + +# +# Battleground.ReportAFK +# Description: Number of reports needed to kick someone AFK from Battleground. +# Range: 1-9 +# Default: 3 + +Battleground.ReportAFK = 3 + +# Battleground.DisableQuestShareInBG +# Description: Disables the ability to share quests while in a Battleground. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.DisableQuestShareInBG = 0 + +# +# Battleground.DisableReadyCheckInBG +# Description: Disables the ability to send a Ready Check survey while in a Battleground. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.DisableReadyCheckInBG = 0 + +# +# Battleground.RewardWinnerHonorFirst +# Battleground.RewardWinnerArenaFirst +# Battleground.RewardWinnerHonorLast +# Battleground.RewardWinnerArenaLast +# Battleground.RewardLoserHonorFirst +# Battleground.RewardLoserHonorLast +# Description: Random Battlegrounds / call to the arms rewards +# Default: 30 - Battleground.RewardWinnerHonorFirst +# 25 - Battleground.RewardWinnerArenaFirst +# 15 - Battleground.RewardWinnerHonorLast +# 0 - Battleground.RewardWinnerArenaLast +# 5 - Battleground.RewardLoserHonorFirst +# 5 - Battleground.RewardLoserHonorLast +# + +Battleground.RewardWinnerHonorFirst = 30 +Battleground.RewardWinnerArenaFirst = 25 +Battleground.RewardWinnerHonorLast = 15 +Battleground.RewardWinnerArenaLast = 0 +Battleground.RewardLoserHonorFirst = 5 +Battleground.RewardLoserHonorLast = 5 + +# +# Battleground.PlayerRespawn +# Description: Battleground player resurrection interval (in seconds). +# Default: 30 + +Battleground.PlayerRespawn = 30 + +# +# Battleground.RestorationBuffRespawn +# Description: Battleground restoration buff respawn time (in seconds). +# Default: 20 (Recommended 10+) + +Battleground.RestorationBuffRespawn = 20 + +# +# Battleground.BerserkingBuffRespawn +# Description: Battleground berserking buff respawn time (in seconds). +# Default: 120 (Recommended 10+) + +Battleground.BerserkingBuffRespawn = 120 + +# +# Battleground.SpeedBuffRespawn +# Description: Battleground speed buff respawn time (in seconds). +# Default: 150 (Recommended 10+) + +Battleground.SpeedBuffRespawn = 150 + +# +################################################################################################### + +################################################################################################### +# BATTLEFIELD CONFIG +# +# Wintergrasp.Enable +# Description: Enable the Wintergrasp battlefield. +# Default: 0 - (Disabled) +# 1 - (Enabled, Experimental as of still being in development) + +Wintergrasp.Enable = 1 + +# +# Wintergrasp.PlayerMax +# Description: Maximum number of players allowed in Wintergrasp. +# Default: 100 + +Wintergrasp.PlayerMax = 120 + +# +# Wintergrasp.PlayerMin +# Description: Minimum number of players required for Wintergrasp. +# Default: 0 + +Wintergrasp.PlayerMin = 0 + +# +# Wintergrasp.PlayerMinLvl +# Description: Required character level for the Wintergrasp battle. +# Default: 77 + +Wintergrasp.PlayerMinLvl = 77 + +# +# Wintergrasp.BattleTimer +# Description: Time (in minutes) for the Wintergrasp battle to last. +# Default: 30 + +Wintergrasp.BattleTimer = 30 + +# +# Wintergrasp.NoBattleTimer +# Description: Time (in minutes) between Wintergrasp battles. +# Default: 150 + +Wintergrasp.NoBattleTimer = 150 + +# +# Wintergrasp.CrashRestartTimer +# Description: Time (in minutes) to delay the restart of Wintergrasp if the world server +# crashed during a running battle. +# Default: 10 + +Wintergrasp.CrashRestartTimer = 10 + +# +################################################################################################### + +################################################################################################### +# ARENA CONFIG +# +# Arena.MaxRatingDifference +# Description: Maximum rating difference between two teams in rated matches. +# Default: 150 - (Enabled) +# 0 - (Disabled) + +Arena.MaxRatingDifference = 150 + +# +# Arena.RatingDiscardTimer +# Description: Time (in milliseconds) after which rating differences are ignored when +# setting up matches. +# Default: 600000 - (Enabled, 10 minutes) +# 0 - (Disabled) + +Arena.RatingDiscardTimer = 600000 + +# +# Arena.PreviousOpponentsDiscardTimer +# Description: Time (in milliseconds) after which the previous opponents will be ignored. +# Default: 120000 - (Enabled, 2 minutes - Blizzlike) +# 0 - (Disabled) + +Arena.PreviousOpponentsDiscardTimer = 120000 + +# +# Arena.AutoDistributePoints +# Description: Automatically distribute arena points. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Arena.AutoDistributePoints = 0 + +# +# Arena.AutoDistributeInterval +# Description: Time (in days) how often arena points should be distributed if automatic +# distribution is enabled. +# Default: 7 - (Weekly) + +Arena.AutoDistributeInterval = 7 + +# +# Arena.GamesRequired +# Description: Number of arena matches teams must participate in to be eligible for arena point distribution. +# Default: 10 + +Arena.GamesRequired = 10 + +# +# Arena.QueueAnnouncer.Enable +# Description: Announce arena queue status to chat. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Arena.QueueAnnouncer.Enable = 0 + +# +# Battleground.QueueAnnouncer.PlayerOnly +# Description: Battleground queue announcement type. +# Default: 0 - (System message, Anyone can see it) +# 1 - (Private, Only queued players can see it) +# + +Arena.QueueAnnouncer.PlayerOnly = 0 + +# +# Arena.ArenaSeason.ID +# Description: Current arena season id shown in clients. +# Default: 8 + +Arena.ArenaSeason.ID = 8 + +# +# Arena.ArenaSeason.InProgress +# Description: State of current arena season. +# Default: 1 - (Active) +# 0 - (Finished) + +Arena.ArenaSeason.InProgress = 1 + +# +# Arena.ArenaStartRating +# Description: Start rating for new arena teams. +# Default: 0 + +Arena.ArenaStartRating = 0 + +# +# Arena.ArenaStartPersonalRating +# Description: Start personal rating when joining a team. +# Default: 0 + +Arena.ArenaStartPersonalRating = 0 + +# +# Arena.ArenaStartMatchmakerRating +# Description: Start matchmaker rating for players. +# Default: 1500 + +Arena.ArenaStartMatchmakerRating = 1500 + +# +# Arena.ArenaWinRatingModifier1 +# Description: Modifier of rating addition when winner team rating is less than 1300 +# be aware that from 1000 to 1300 it gradually decreases automatically down to the half of it +# (increasing this value will give more rating) +# Default: 48 + +Arena.ArenaWinRatingModifier1 = 48 + +# +# Arena.ArenaWinRatingModifier2 +# Description: Modifier of rating addition when winner team rating is equal or more than 1300 +# (increasing this value will give more rating) +# Default: 24 + +Arena.ArenaWinRatingModifier2 = 24 + +# +# Arena.ArenaLoseRatingModifier +# Description: Modifier of rating subtraction for loser team +# (increasing this value will subtract more rating) +# Default: 24 + +Arena.ArenaLoseRatingModifier = 24 + +# +# Arena.ArenaMatchmakerRatingModifier +# Description: Modifier of matchmaker rating +# Default: 24 + +Arena.ArenaMatchmakerRatingModifier = 24 + +# +################################################################################################### + +################################################################################################### +# NETWORK CONFIG +# +# Network.Threads +# Description: Number of threads for network. +# Default: 1 - (Recommended 1 thread per 1000 connections) + +Network.Threads = 1 + +# +# Network.OutKBuff +# Description: Amount of memory (in bytes) used for the output kernel buffer (see SO_SNDBUF +# socket option, TCP manual). +# Default: -1 - (Use system default setting) + +Network.OutKBuff = -1 + +# +# Network.OutUBuff +# Description: Amount of memory (in bytes) reserved in the user space per connection for +# output buffering. +# Default: 65536 + +Network.OutUBuff = 65536 + +# +# Network.TcpNoDelay: +# Description: TCP Nagle algorithm setting. +# Default: 0 - (Enabled, Less traffic, More latency) +# 1 - (Disabled, More traffic, Less latency, TCP_NO_DELAY) + +Network.TcpNodelay = 1 + +# +################################################################################################### + +################################################################################################### +# CONSOLE AND REMOTE ACCESS +# +# Console.Enable +# Description: Enable console. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Console.Enable = 1 + +# +# Ra.Enable +# Description: Enable remote console (telnet). +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Ra.Enable = 0 + +# +# Ra.IP +# Description: Bind remote access to IP/hostname. +# Default: "0.0.0.0" - (Bind to all IPs on the system) + +Ra.IP = "0.0.0.0" + +# +# Ra.Port +# Description: TCP port to reach the remote console. +# Default: 3443 + +Ra.Port = 3443 + +# +# Ra.MinLevel +# Description: Required security level to use the remote console. +# Default: 3 + +Ra.MinLevel = 3 + +# +# SOAP.Enable +# Description: Enable soap service +# Default: 0 - (Disabled) +# 1 - (Enabled) + +SOAP.Enabled = 0 + +# +# SOAP.IP +# Description: Bind SOAP service to IP/hostname +# Default: "127.0.0.1" - (Bind to localhost) + +SOAP.IP = "127.0.0.1" + +# +# SOAP.Port +# Description: TCP port to reach the SOAP service. +# Default: 7878 + +SOAP.Port = 7878 + +# +################################################################################################### + +################################################################################################### +# CHARACTER DELETE OPTIONS +# +# CharDelete.Method +# Description: Character deletion behavior. +# Default: 0 - (Completely remove character from the database) +# 1 - (Unlink the character from account and free up the name, Appears as +# deleted ingame) + +CharDelete.Method = 0 + +# +# CharDelete.MinLevel +# Description: Required level to use the unlinking method if enabled. +# Default: 0 - (Same method for every level) +# 1+ - (Only characters with the specified level will use the unlinking method) + +CharDelete.MinLevel = 0 + +# +# CharDelete.KeepDays +# Description: Time (in days) before unlinked characters will be removed from the database. +# Default: 30 - (Enabled) +# 0 - (Disabled, Don't delete any characters) + +CharDelete.KeepDays = 30 + +# +################################################################################################### + +################################################################################################### +# ITEM DELETE OPTIONS +# +# ItemDelete.Method +# Description: Item deletion behavior. +# Default: 0 - (Completely remove item from the database) +# 1 - (Save Item to database) + +ItemDelete.Method = 0 + +# +# ItemDelete.Vendor +# Description: Saving items into database when the player sells items to vendor +# Default: 0 (disabled) +# 1 (enabled) +# + +ItemDelete.Vendor = 0 + +# +# ItemDelete.Quality +# Description: Saving items into database that have quality greater or equal to ItemDelete.Quality +# +# ID | Color | Quality +# 0 | Grey | Poor +# 1 | White | Common +# 2 | Green | Uncommon +# 3 | Blue | Rare +# 4 | Purple| Epic +# 5 | Orange| Legendary +# 6 | Red | Artifact +# 7 | Gold | Bind to Account +# +# Default: 3 +# + +ItemDelete.Quality = 3 + +# +# ItemDelete.ItemLevel +# Description: Saving items into database that are Item Levels greater or equal to ItemDelete.ItemLevel +# Default: 80 +# + +ItemDelete.ItemLevel = 80 + +# +################################################################################################### + +################################################################################################### +# CUSTOM SERVER OPTIONS +# +# PlayerStart.AllReputation +# Description: Players will start with most of the high level reputations that are needed +# for items, mounts etc. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PlayerStart.AllReputation = 0 + +# +# PlayerStart.CustomSpells +# Description: If enabled, players will start with custom spells defined in +# playercreateinfo_spell_custom table. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PlayerStart.CustomSpells = 0 + +# +# PlayerStart.MapsExplored +# Description: Characters start with all maps explored. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PlayerStart.MapsExplored = 0 + +# +# HonorPointsAfterDuel +# Description: Amount of honor points the duel winner will get after a duel. +# Default: 0 - (Disabled) +# 1+ - (Enabled) + +HonorPointsAfterDuel = 0 + +# +# AlwaysMaxWeaponSkill +# Description: Players will automatically gain max weapon/defense skill when logging in, +# or leveling. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +AlwaysMaxWeaponSkill = 0 + +# +# PvPToken.Enable +# Description: Character will receive a token after defeating another character that yields +# honor. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +PvPToken.Enable = 0 + +# +# PvPToken.MapAllowType +# Description: Define where characters can receive tokens. +# Default: 4 - (All maps) +# 3 - (Battlegrounds) +# 2 - (FFA areas only like Gurubashi arena) +# 1 - (Battlegrounds and FFA areas) + +PvPToken.MapAllowType = 4 + +# +# PvPToken.ItemID +# Description: Item characters will receive after defeating another character if PvP Token +# system is enabled. +# Default: 29434 - (Badge of justice) + +PvPToken.ItemID = 29434 + +# +# PvPToken.ItemCount +# Description: Number of tokens a character will receive. +# Default: 1 + +PvPToken.ItemCount = 1 + +# +# NoResetTalentsCost +# Description: Resetting talents doesn't cost anything. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +NoResetTalentsCost = 0 + +# +# ToggleXP.Cost +# Description: Cost of locking/unlocking XP +# Default: 100000 - (10 Gold) +# + +ToggleXP.Cost = 100000 + +# +# Guild.AllowMultipleGuildMaster +# Description: Allow more than one guild master. Additional Guild Masters must be set using +# the ".guild rank" command. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Guild.AllowMultipleGuildMaster = 0 + +# +# Guild.BankInitialTabs +# Description: Changes the amounts of available tabs of the guild bank on guild creation +# Default: 0 (no tabs given for free) +# 1-6 (amount of tabs of the guild bank at guild creation) + +Guild.BankInitialTabs = 0 + +# +# Guild.BankTabCost0-5 +# Description: Changes the price of the guild tabs. Note that the client will still show the default values. +# Default: 1000000 - (100 Gold) +# 2500000 - (250 Gold) +# 5000000 - (500 Gold) +# 10000000 - (1000 Gold) +# 25000000 - (2500 Gold) +# 50000000 - (5000 Gold) + +Guild.BankTabCost0 = 1000000 +Guild.BankTabCost1 = 2500000 +Guild.BankTabCost2 = 5000000 +Guild.BankTabCost3 = 10000000 +Guild.BankTabCost4 = 25000000 +Guild.BankTabCost5 = 50000000 + +# +# ShowKickInWorld +# Description: Determines whether a message is broadcast to the entire server when a +# player gets kicked +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ShowKickInWorld = 0 + +# +# ShowMuteInWorld +# Description: Determines whether a message is broadcast to the entire server when a +# player gets muted. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ShowMuteInWorld = 0 + +# +# ShowBanInWorld +# Description: Determines whether a message is broadcast to the entire server when a +# player gets banned. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +ShowBanInWorld = 0 + +# +# RecordUpdateTimeDiffInterval +# Description: Time (in milliseconds) update time diff is written to the log file. +# Update diff can be used as a performance indicator. Diff < 300: good +# performance. Diff > 600 bad performance, may be caused by high CPU usage. +# Default: 300000 - (Enabled, 5 minutes) +# 0 - (Disabled) + +RecordUpdateTimeDiffInterval = 300000 + +# +# MinRecordUpdateTimeDiff +# Description: Only record update time diff which is greater than this value. +# Default: 100 + +MinRecordUpdateTimeDiff = 100 + +# +# PlayerStart.String +# Description: String to be displayed at first login of newly created characters. +# Default: "" - (Disabled) + +PlayerStart.String = "" + +# +# LevelReq.Trade +# Description: Level requirement for characters to be able to trade. +# Default: 1 + +LevelReq.Trade = 1 + +# +# LevelReq.Ticket +# Description: Level requirement for characters to be able to write tickets. +# Default: 1 + +LevelReq.Ticket = 1 + +# +# LevelReq.Auction +# Description: Level requirement for characters to be able to use the auction house. +# Default: 1 + +LevelReq.Auction = 1 + +# +# LevelReq.Mail +# Description: Level requirement for characters to be able to send and receive mails. +# Default: 1 + +LevelReq.Mail = 1 + +# +# PlayerDump.DisallowPaths +# Description: Disallow using paths in PlayerDump output files +# Default: 1 + +PlayerDump.DisallowPaths = 1 + +# +# PlayerDump.DisallowOverwrite +# Description: Disallow overwriting existing files with PlayerDump +# Default: 1 + +PlayerDump.DisallowOverwrite = 1 + +# +# DisconnectToleranceInterval +# Description: Allows to skip queue after being disconnected for a given number of seconds. +# Default: 0 + +DisconnectToleranceInterval = 0 + +# +# MonsterSight +# Description: The maximum distance in yards that a "monster" creature can see +# regardless of level difference (through CreatureAI::IsVisible). +# Increases CONFIG_SIGHT_MONSTER to 50 yards. Used to be 20 yards. +# Default: 50.000000 + +MonsterSight = 50.000000 + +# +# StrictChannelNames +# Description: Limit channel names to language specific symbol set. +# Prevents charter creation if not allowed symbols are used. +# Default: 0 - (Disable, Limited server timezone dependent client check) +# 1 - (Enabled, Strictly basic Latin characters) +# 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. +# 3 - (Enabled, Basic Latin characters + server timezone specific) + +StrictChannelNames = 0 + +# +# Instance.SharedNormalHeroicId +# Description: Forces ICC and RS Normal and Heroic to share lockouts. ToC is uneffected and Normal and Heroic will be separate. +# Default: 1 - Enable +# 0 - Disable +# + +Instance.SharedNormalHeroicId = 1 + +# +# Instance.ResetTimeRelativeTimestamp +# Description: Needed for displaying valid instance reset times in ingame calendar. +# This timestamp should be set to a date in the past (midnight) on which +# both 3-day and 7-day raids were reset. +# Default: 1135814400 - (Thu, 29 Dec 2005 00:00:00 GMT - meaning that 7-day raid reset falls on Thursdays, +# while 3-day reset falls on "Thu 29 Dec 2005", "Sun 01 Jan 2006", "Wed 04 Jan 2006", and so on) + +Instance.ResetTimeRelativeTimestamp = 1135814400 + +# +# TeleportTimeoutNear +# Description: No description +# Default: 25 +# + +TeleportTimeoutNear = 25 + +# +# TeleportTimeoutFar +# Description: No description +# Default: 45 +# + +TeleportTimeoutFar = 45 + +# +# MaxAllowedMMRDrop +# Description: Some players continuously lose arena matches to lower their MMR and then fight with weaker opponents. +# This setting prevents lowering MMR too much from max achieved MMR. +# Eg. if max achieved MMR for a character was 2400, with default setting (MaxAllowedMMRDrop = 500) the character can't get below 1900 MMR no matter what. +# Default: 500 + +MaxAllowedMMRDrop = 500 + +# +# EnableLoginAfterDC +# Description: After not logging out properly (clicking Logout and waiting 20 seconds), +# characters stay in game world for a full minute, even if the client connection was closed. +# Such behaviour prevents for example exploiting boss encounters by alt+f4 +# and skipping crucial boss abilities, or escaping opponents in PvP. +# This setting is used to allow/disallow players to log back into characters that are left in world. +# Default: 1 - (by clicking "Enter World" player will log back into a character that is already in world) +# 0 - (by clicking "Enter World" player will get an error message when trying to log into a character +# that is left in world, and has to wait a minute for the character to be removed from world) + +EnableLoginAfterDC = 1 + +# +# DontCacheRandomMovementPaths +# Description: Random movement paths (calculated using MoveMaps) can be cached to save cpu time, +# but this may use up considerable amount of memory and can be prevented by setting this option to 1. +# Recommended setting for populated servers is having enough RAM and setting this to 0. +# Default: 0 - (cache paths, uses more memory) +# 1 - (don't cache, uses more cpu) + +DontCacheRandomMovementPaths = 0 + +# +# MoveMaps.Enable +# Description: Enable/Disable pathfinding using mmaps - recommended. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +MoveMaps.Enable = 1 + +# +# Minigob.Manabonk.Enable +# Description: Enable/ Disable Minigob Manabonk +# Default: 1 + +Minigob.Manabonk.Enable = 1 + +# +# Allow.IP.Based.Action.Logging +# Description: Logs actions, e.g. account login and logout to name a few, based on IP of current session. +# Default: 0 - (Disabled) +# 1 - (Enabled) +# + +Allow.IP.Based.Action.Logging = 0 + +# +# Calculate.Creature.Zone.Area.Data +# Description: Calculate at loading creature zoneId / areaId and save in creature table (WARNING: SLOW WORLD SERVER STARTUP) +# Default: 0 - (Do not show) +# + +Calculate.Creature.Zone.Area.Data = 0 + +# +# Calculate.Gameoject.Zone.Area.Data +# Description: Calculate at loading gameobject zoneId / areaId and save in gameobject table (WARNING: SLOW WORLD SERVER STARTUP) +# Default: 0 - (Do not show) +# + +Calculate.Gameoject.Zone.Area.Data = 0 + +# +# LFG.Location.All +# +# Includes satellite to search for work elsewhere LFG +# Default: 0 - Disable +# 1 - Enable +# + +LFG.Location.All = 0 + +# +# DungeonAccessRequirements.PrintMode +# +# Description: Select the preferred format to display information to the player who cannot enter a portal dungeon because when has not met the access requirements: +# Default: 1 - (Display only one requirement at a time (BlizzLike, like in the LFG interface)) +# 0 - (Display no extra information, only "Requirements not met") +# 2 - (Display detailed requirements, all at once, with clickable links) +# + +DungeonAccessRequirements.PrintMode = 1 + +# +# DungeonAccessRequirements.PortalAvgIlevelCheck +# +# Description: Enable average item level requirement when entering a dungeon/raid's portal (= deny the entry if player has too low average ilevel, like in LFG). +# Default: 0 - (Disabled -> Blizzlike) +# 1 - (Enabled) + +DungeonAccessRequirements.PortalAvgIlevelCheck = 0 + +# +# DungeonAccessRequirements.LFGLevelDBCOverride +# +# Description: If enabled, use `min_level` and `max_level` values from table `dungeon_access_requirements` to list or to hide a dungeon from the LFG window. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +DungeonAccessRequirements.LFGLevelDBCOverride = 0 + +# +# DungeonAccessRequirements.OptionalStringID +# +# Description: Display an extra message from acore_strings in the chat after printing the dungeon access requirements. +# To enable it set the ID of your desired string from the table acore_strings +# Default: 0 - (Disabled) +# 1+ - (Enabled) + +DungeonAccessRequirements.OptionalStringID = 0 + +# +# ICC Buff +# Description: Specify ICC buff +# (It is necessary to restart the server after changing the values!) +# Default: ICC.Buff.Horde = 73822 +# ICC.Buff.Alliance = 73828 +# +# Spell IDs for the auras: +# 73816 - 5% buff Horde +# 73818 - 10% buff Horde +# 73819 - 15% buff Horde +# 73820 - 20% buff Horde +# 73821 - 25% buff Horde +# 73822 - 30% buff Horde +# 73762 - 5% buff Alliance +# 73824 - 10% buff Alliance +# 73825 - 15% buff Alliance +# 73826 - 20% buff Alliance +# 73827 - 25% buff Alliance +# 73828 - 30% buff Alliance + +ICC.Buff.Horde = 73822 +ICC.Buff.Alliance = 73828 + +# +# Item.SetItemTradeable +# Description: Enabled/Disabled trading BoP items among raid members. +# Default: 1 - (Set BoP items tradeable timer to 2 hours) +# 0 - (Disable trading BoP items among raid members) + +Item.SetItemTradeable = 1 + +# +# FFAPvPTimer +# Description: Specify time offset when player unset FFAPvP flag when leaving FFAPvP area. (e.g. Gurubashi Arena) +# Default: 30 sec + +FFAPvPTimer = 30 + +# +# LootNeedBeforeGreedILvlRestriction +# Description: Specify level restriction for items below player's subclass in Need Before Greed loot mode in DF groups +# Default: 70 +# 0 - Disabled + +LootNeedBeforeGreedILvlRestriction = 70 + +# +# LFG.MaxKickCount +# Description: Specify the maximum number of kicks allowed in LFG groups (max 3 kicks) +# Default: 2 +# 0 - Disabled (kicks are never allowed) + +LFG.MaxKickCount = 2 + +# +# LFG.KickPreventionTimer +# Description: Specify for how long players are prevented from being kicked after just joining LFG groups +# Default: 900 secs (15 minutes) +# 0 - Disabled + +LFG.KickPreventionTimer = 900 + +# +# EnablePlayerSettings +# Description: Enables the usage of character specific settings. +# Default: 0 - Disabled +# 1 - Enabled + +EnablePlayerSettings = 0 + +# +# JoinBGAndLFG.Enable +# Description: Allow queueing for BG and LFG at the same time. +# Default: 0 - Disabled +# 1 - Enabled + +JoinBGAndLFG.Enable = 0 + +# +# LeaveGroupOnLogout.Enabled +# Description: Should the player leave their group when they log out? +# (It does not affect raids or dungeon finder groups) +# +# Default: 1 - (Enabled) + +LeaveGroupOnLogout.Enabled = 1 + +# +# ChangeFaction.MaxMoney +# Description: Maximum amount of gold allowed on the character to perform a faction change. +# Default: 0 - Disabled +# > 0 - Enabled (money in copper) +# Example: If set to 10000, the maximum amount of money allowed on the character would be 1 gold. + +ChangeFaction.MaxMoney = 0 + +# +################################################################################################### + +################################################################################################### +# LOGGING SYSTEM SETTINGS +# +# Appender config values: Given an appender "name" +# Appender.name +# Description: Defines 'where to log'. +# Format: Type,LogLevel,Flags,optional1,optional2,optional3 +# +# Type +# 0 - (None) +# 1 - (Console) +# 2 - (File) +# 3 - (DB) +# +# LogLevel +# 0 - (Disabled) +# 1 - (Fatal) +# 2 - (Error) +# 3 - (Warning) +# 4 - (Info) +# 5 - (Debug) +# 6 - (Trace) +# +# Flags: +# 0 - None +# 1 - Prefix Timestamp to the text +# 2 - Prefix Log Level to the text +# 4 - Prefix Log Filter type to the text +# 8 - Append timestamp to the log file name. Format: YYYY-MM-DD_HH-MM-SS +# (Only used with Type = 2) +# 16 - Make a backup of existing file before overwrite +# (Only used with Mode = w) +# +# Colors (read as optional1 if Type = Console) +# Format: "fatal error warn info debug trace" +# 0 - BLACK +# 1 - RED +# 2 - GREEN +# 3 - BROWN +# 4 - BLUE +# 5 - MAGENTA +# 6 - CYAN +# 7 - GREY +# 8 - YELLOW +# 9 - LRED +# 10 - LGREEN +# 11 - LBLUE +# 12 - LMAGENTA +# 13 - LCYAN +# 14 - WHITE +# Example: "1 9 3 6 5 8" +# +# File: Name of the file (read as optional1 if Type = File) +# Allows to use one "%s" to create dynamic files +# +# Mode: Mode to open the file (read as optional2 if Type = File) +# a - (Append) +# w - (Overwrite) +# +# MaxFileSize: Maximum file size of the log file before creating a new log file +# (read as optional3 if Type = File) +# Size is measured in bytes expressed in a 64-bit unsigned integer. +# Maximum value is 4294967295 (4 GB). Leave blank for no limit. +# NOTE: Does not work with dynamic filenames. +# Example: 536870912 (512 MB) +# + +Appender.Console=1,4,0,"1 9 3 6 5 8" +Appender.Server=2,5,0,Server.log,w +Appender.GM=2,5,15,gm_%s.log +Appender.DBErrors=2,5,0,DBErrors.log +# Appender.DB=3,5,0 + +# Logger config values: Given a logger "name" +# Logger.name +# Description: Defines 'What to log' +# Format: LogLevel,AppenderList +# +# LogLevel +# 0 - (Disabled) +# 1 - (Fatal) +# 2 - (Error) +# 3 - (Warning) +# 4 - (Info) +# 5 - (Debug) +# 6 - (Trace) +# +# AppenderList: List of appenders linked to logger +# (Using spaces as separator). +# + +Logger.root=2,Console Server +Logger.commands.gm=4,Console GM +Logger.diff=3,Console Server +Logger.mmaps=4,Server +Logger.scripts.hotswap=4,Console Server +Logger.server=4,Console Server +Logger.sql.sql=2,Console DBErrors +Logger.sql=4,Console Server +Logger.time.update=4,Console Server +Logger.module=4,Console Server + +#Logger.achievement=4,Console Server +#Logger.addon=4,Console Server +#Logger.ahbot=4,Console Server +#Logger.auctionHouse=4,Console Server +#Logger.bg.arena=4,Console Server +#Logger.bg.battlefield=4,Console Server +#Logger.bg.battleground=4,Console Server +#Logger.bg.reportpvpafk=4,Console Server +#Logger.calendar=4,Console Server +#Logger.chat.log=4,Console Server +#Logger.chat.log.addon=4,Console Server +#Logger.chat.system=4,Console Server +#Logger.cheat=4,Console Server +#Logger.commands.ra=4,Console Server +#Logger.condition=4,Console Server +#Logger.dbc=4,Console Server +#Logger.disable=4,Console Server +#Logger.entities.dyobject=4,Console Server +#Logger.entities.faction=4,Console Server +#Logger.entities.gameobject=4,Console Server +#Logger.entities.object=4,Console Server +#Logger.entities.pet=4,Console Server +#Logger.entities.player.character=4,Console Server +#Logger.entities.player.dump=4,Console Server +#Logger.entities.player.items=4,Console Server +#Logger.entities.player.loading=4,Console Server +#Logger.entities.player.skills=4,Console Server +#Logger.entities.player=4,Console Server +#Logger.entities.transport=4,Console Server +#Logger.entities.unit.ai=4,Console Server +#Logger.entities.unit=4,Console Server +#Logger.entities.vehicle=4,Console Server +#Logger.gameevent=4,Console Server +#Logger.group=4,Console Server +#Logger.guild=4,Console Server +#Logger.instance.save=4,Console Server +#Logger.instance.script=4,Console Server +#Logger.lfg=4,Console Server +#Logger.loot=4,Console Server +#Logger.mail=4,Console Server +#Logger.maps.script=4,Console Server +#Logger.maps=4,Console Server +#Logger.misc=4,Console Server +#Logger.mmaps.tiles=4,Console Server +#Logger.movement.flightpath=4,Console Server +#Logger.movement.motionmaster=4,Console Server +#Logger.movement.splinechain=4,Console Server +#Logger.movement=4,Console Server +#Logger.network.kick=4,Console Server +#Logger.network.opcode=4,Console Server +#Logger.network.soap=4,Console Server +#Logger.network=4,Console Server +#Logger.outdoorpvp=4,Console Server +#Logger.pool=4,Console Server +#Logger.rbac=4,Console Server +#Logger.reputation=4,Console Server +#Logger.scripts.ai.escortai=4,Console Server +#Logger.scripts.ai.followerai=4,Console Server +#Logger.scripts.ai.petai=4,Console Server +#Logger.scripts.ai.sai=4,Console Server +#Logger.scripts.ai=4,Console Server +#Logger.scripts.cos=4,Console Server +#Logger.scripts=4,Console Server +#Logger.server.authserver=4,Console Server +#Logger.spells.aura.effect.nospell=4,Console Server +#Logger.spells.aura.effect=4,Console Server +#Logger.spells.effect.nospell=4,Console Server +#Logger.spells.effect=4,Console Server +#Logger.spells.scripts=4,Console Server +#Logger.spells=4,Console Server +#Logger.sql.dev=4,Console Server +#Logger.sql.driver=4,Console Server +#Logger.vehicles=4,Console Server +#Logger.warden=4,Console Server +#Logger.weather=4,Console Server + +# +# Log.Async.Enable +# Description: Enables asynchronous message logging. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Log.Async.Enable = 0 + +# +################################################################################################### + +################################################################################################### +# PACKET SPOOF PROTECTION SETTINGS +# +# These settings determine which action to take when harmful packet spoofing is detected. +# +# PacketSpoof.Policy +# Description: Determines the course of action when packet spoofing is detected. +# Values: 0 - Log only +# 1 - Log + kick +# 2 - Log + kick + ban + +PacketSpoof.Policy = 1 + +# +# PacketSpoof.BanMode +# Description: If PacketSpoof.Policy equals 2, this will determine the ban mode. +# Values: 0 - Ban Account +# 1 - Ban IP +# Note: Banning by character not supported for logical reasons. +# + +PacketSpoof.BanMode = 0 + +# +# PacketSpoof.BanDuration +# Description: Duration of the ban in seconds. Only valid if PacketSpoof.Policy is set to 2. +# Set to 0 for permanent ban. +# Default: 86400 seconds (1 day) +# + +PacketSpoof.BanDuration = 86400 + +# +################################################################################################### + +################################################################################################### +# DEBUG +# +# Debug.Battleground +# Description: Enable or disable Battleground 1v0 mode. (If enabled, the in-game command is disabled.) +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Debug.Battleground = 0 + +# +# Debug.Arena +# Description: Enable or disable Arena 1v1 mode. (If enabled, the in-game command is disabled.) +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Debug.Arena = 0 + +# +################################################################################################### + +################################################################################################### +# METRIC SETTINGS +# +# These settings control the statistics sent to the metric database (currently InfluxDB) +# +# Metric.Enable +# Description: Enables statistics sent to the metric database. +# Default: 0 - (Disabled) +# 1 - (Enabled) +# + +Metric.Enable = 0 + +# +# Metric.Interval +# Description: Interval between every batch of data sent in seconds +# Default: 10 seconds +# + +Metric.Interval = 10 + +# +# Metric.ConnectionInfo +# Description: Connection settings for metric database (currently InfluxDB). +# Example: "hostname;port;database" +# Default: "127.0.0.1;8086;worldserver" +# + +Metric.ConnectionInfo = "127.0.0.1;8086;worldserver" + +# +# Metric.OverallStatusInterval +# Description: Interval between every gathering of overall worldserver status data in seconds +# Default: 1 second +# + +Metric.OverallStatusInterval = 1 + +# +# Metric threshold values: Given a metric "name" +# Metric.Threshold.name +# Description: Skips sending statistics with a value lower than the config value. +# If the threshold is commented out, the metric will be ignored. +# Only metrics logged with METRIC_DETAILED_TIMER in the sources are affected. +# Disabled by default. Requires WITH_DETAILED_METRICS CMake flag. +# +# Format: Value as integer +# + +#Metric.Threshold.world_update_sessions_time = 100 +#Metric.Threshold.worldsession_update_opcode_time = 50 + +# +################################################################################################### diff --git a/src/server/apps/worldserver/worldserver.ico b/src/server/apps/worldserver/worldserver.ico Binary files differnew file mode 100644 index 0000000000..01a874d9fc --- /dev/null +++ b/src/server/apps/worldserver/worldserver.ico diff --git a/src/server/apps/worldserver/worldserver.rc b/src/server/apps/worldserver/worldserver.rc new file mode 100644 index 0000000000..8a2790fd84 --- /dev/null +++ b/src/server/apps/worldserver/worldserver.rc @@ -0,0 +1,93 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) 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 Affero 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 <http://www.gnu.org/licenses/>. + */ + +#include "resource.h" +#include "revision.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "windows.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APPICON ICON "worldserver.ico" + +///////////////////////////////////////////////////////////////////////////// +// Neutre (Par défaut système) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUSD) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION + +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + +#ifndef _DEBUG + FILEFLAGS 0 +#else + #define VER_PRERELEASE VS_FF_PRERELEASE + #define VER_PRIVATEBUILD VS_FF_PRIVATEBUILD + #define VER_DEBUG 0 + FILEFLAGS (VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG) +#endif + +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP + +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080004b0" + BEGIN + VALUE "CompanyName", VER_COMPANYNAME_STR + VALUE "FileDescription", "World Server Daemon" + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", "worldserver" + VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR + VALUE "OriginalFilename", "worldserver.exe" + VALUE "ProductName", "World Server" + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x800, 1200 + END +END +#endif |