/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2019 The ManaPlus Developers * * This file is part of The ManaPlus Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "progs/manaplus/client.h" #include "chatlogger.h" #include "configmanager.h" #include "dirs.h" #include "eventsmanager.h" #include "game.h" #include "graphicsmanager.h" #include "main.h" #include "party.h" #include "pincodemanager.h" #include "settings.h" #include "soundmanager.h" #include "spellmanager.h" #include "being/localclan.h" #include "being/localplayer.h" #include "being/playerinfo.h" #include "being/playerrelations.h" #include "const/net/net.h" #include "enums/being/attributesstrings.h" #include "fs/virtfs/fs.h" #include "fs/virtfs/tools.h" #include "gui/dialogsmanager.h" #include "gui/gui.h" #include "gui/skin.h" #include "gui/popupmanager.h" #include "gui/windowmanager.h" #include "gui/shortcut/dropshortcut.h" #include "gui/shortcut/emoteshortcut.h" #include "gui/shortcut/itemshortcut.h" #include "gui/shortcut/spellshortcut.h" #include "gui/windows/changeemaildialog.h" #include "gui/windows/changepassworddialog.h" #include "gui/windows/charselectdialog.h" #include "gui/windows/connectiondialog.h" #include "gui/windows/equipmentwindow.h" #include "gui/windows/logindialog.h" #include "gui/windows/npcdialog.h" #include "gui/windows/okdialog.h" #include "gui/windows/registerdialog.h" #include "gui/windows/serverdialog.h" #include "gui/windows/setupwindow.h" #include "gui/windows/updaterwindow.h" #include "gui/windows/quitdialog.h" #include "gui/windows/worldselectdialog.h" #include "gui/widgets/button.h" #include "gui/widgets/createwidget.h" #include "gui/widgets/desktop.h" #include "gui/widgets/windowcontainer.h" #include "input/inputmanager.h" #include "input/joystick.h" #include "input/keyboardconfig.h" #include "input/touch/touchmanager.h" #include "net/charserverhandler.h" #include "net/chathandler.h" #include "net/download.h" #include "net/gamehandler.h" #include "net/generalhandler.h" #include "net/guildhandler.h" #include "net/inventoryhandler.h" #include "net/ipc.h" #include "net/loginhandler.h" #include "net/net.h" #include "net/updatetypeoperators.h" #include "net/useragent.h" #include "net/packetlimiter.h" #include "net/partyhandler.h" #ifdef TMWA_SUPPORT #include "net/tmwa/guildmanager.h" #endif // TMWA_SUPPORT #include "particle/particleengine.h" #include "resources/dbmanager.h" #include "resources/imagehelper.h" #include "resources/dye/dyepalette.h" #include "resources/resourcemanager/resourcemanager.h" #include "resources/sprite/spritereference.h" #include "utils/checkutils.h" #include "utils/cpu.h" #include "utils/delete2.h" #include "utils/dumplibs.h" #include "utils/dumpsizes.h" #include "utils/env.h" #include "utils/fuzzer.h" #include "utils/gettext.h" #include "utils/gettexthelper.h" #include "utils/mrand.h" #ifdef ANDROID #include "fs/paths.h" #endif // ANDROID #include "utils/perfstat.h" #include "utils/sdlcheckutils.h" #include "utils/sdlhelper.h" #include "utils/timer.h" #include "utils/translation/translationmanager.h" #include "listeners/assertlistener.h" #include "listeners/errorlistener.h" #ifdef USE_OPENGL #include "test/testlauncher.h" #include "test/testmain.h" #else // USE_OPENGL #include "configuration.h" #endif // USE_OPENGL #ifdef WIN32 PRAGMA48(GCC diagnostic push) PRAGMA48(GCC diagnostic ignored "-Wshadow") #include PRAGMA48(GCC diagnostic pop) #include "fs/specialfolder.h" #undef ERROR #endif // WIN32 #ifdef ANDROID #ifndef USE_SDL2 PRAGMA48(GCC diagnostic push) PRAGMA48(GCC diagnostic ignored "-Wshadow") #include PRAGMA48(GCC diagnostic pop) #include #endif // USE_SDL2 #endif // ANDROID #include #ifdef USE_MUMBLE #include "mumblemanager.h" #endif // USE_MUMBLE PRAGMA48(GCC diagnostic push) PRAGMA48(GCC diagnostic ignored "-Wshadow") #ifdef USE_SDL2 #include #else // USE_SDL2 #include #endif // USE_SDL2 PRAGMA48(GCC diagnostic pop) #include "debug.h" std::string errorMessage; LoginData loginData; Client *client = nullptr; extern FPSmanager fpsManager; extern int evolPacketOffset; volatile bool runCounters; bool isSafeMode = false; int serverVersion = 0; int packetVersion = 0; int packetVersionMain = 0; int packetVersionRe = 0; int packetVersionZero = 0; int packetsType = 0; int itemIdLen = 2; bool packets_main = true; bool packets_re = false; bool packets_zero = false; unsigned int tmwServerVersion = 0; time_t start_time; unsigned int mLastHost = 0; unsigned long mSearchHash = 0; int textures_count = 0; volatile bool isTerminate = false; namespace { class AccountListener final : public ActionListener { public: AccountListener() { } A_DELETE_COPY(AccountListener) void action(const ActionEvent &event A_UNUSED) override final { client->setState(State::CHAR_SELECT); } } accountListener; class LoginListener final : public ActionListener { public: LoginListener() { } A_DELETE_COPY(LoginListener) void action(const ActionEvent &event A_UNUSED) override final { client->setState(State::PRE_LOGIN); } } loginListener; } // namespace Client::Client() : ActionListener(), mCurrentServer(), mGame(nullptr), mCurrentDialog(nullptr), mQuitDialog(nullptr), mSetupButton(nullptr), mVideoButton(nullptr), mHelpButton(nullptr), mAboutButton(nullptr), mThemesButton(nullptr), mPerfomanceButton(nullptr), #ifdef ANDROID mCloseButton(nullptr), #endif // ANDROID mState(State::CHOOSE_SERVER), mOldState(State::START), mSkin(nullptr), mButtonPadding(1), mButtonSpacing(3), mPing(0), mConfigAutoSaved(false) { WindowManager::init(); } void Client::testsInit() { if (!settings.options.test.empty() && settings.options.test != "99") { gameInit(); } else { initRand(); logger = new Logger; SDL::initLogger(); Dirs::initLocalDataDir(); Dirs::initTempDir(); Dirs::initConfigDir(); GettextHelper::initLang(); } } void Client::gameInit() { logger = new Logger; SDL::initLogger(); initRand(); assertListener = new AssertListener; // Load branding information if (!settings.options.brandingPath.empty()) { branding.init(settings.options.brandingPath, UseVirtFs_false, SkipError_false); } setBrandingDefaults(branding); Dirs::initRootDir(); Dirs::initHomeDir(); // Configure logger if (!settings.options.logFileName.empty()) { settings.logFileName = settings.options.logFileName; } else { settings.logFileName = pathJoin(settings.localDataDir, "manaplus.log"); } logger->log("Log file: " + settings.logFileName); logger->setLogFile(settings.logFileName); #ifdef USE_FUZZER Fuzzer::init(); #endif // USE_FUZZER if (settings.options.ipc == true) IPC::start(); if (settings.options.test.empty()) ConfigManager::backupConfig("config.xml"); ConfigManager::initConfiguration(); SDL::setLogLevel(config.getIntValue("sdlLogLevel")); settings.init(); Net::loadIgnorePackets(); setPathsDefaults(paths); initFeatures(); initPaths(); logger->log("init 4"); logger->setDebugLog(config.getBoolValue("debugLog")); logger->setReportUnimplemented(config.getBoolValue("unimplimentedLog")); config.incValue("runcount"); #ifndef ANDROID if (settings.options.test.empty()) ConfigManager::storeSafeParameters(); #endif // ANDROID if (!VirtFs::setWriteDir(settings.localDataDir)) { logger->error(strprintf("%s couldn't be set as home directory! " "Exiting.", settings.localDataDir.c_str())); } GettextHelper::initLang(); chatLogger = new ChatLogger; if (settings.options.chatLogDir.empty()) { chatLogger->setBaseLogDir(settings.localDataDir + std::string("/logs/")); } else { chatLogger->setBaseLogDir(settings.options.chatLogDir); } // Log the client version logger->log1(FULL_VERSION); logger->log("Start configPath: " + config.getConfigPath()); Dirs::initScreenshotDir(); updateEnv(); SDL::allowScreenSaver(config.getBoolValue("allowscreensaver")); dumpLibs(); dumpSizes(); // Initialize SDL logger->log1("Initializing SDL..."); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) { logger->safeError(strprintf("Could not initialize SDL: %s", SDL_GetError())); } #ifndef __SWITCH__ atexit(SDL_Quit); #endif PacketLimiter::initPacketLimiter(); #ifndef USE_SDL2 SDL_EnableUNICODE(1); #endif // USE_SDL2 WindowManager::applyKeyRepeat(); eventsManager.init(); eventsManager.enableEvents(); #ifdef WIN32 Dirs::mountDataDir(); #endif // WIN32 #ifndef USE_SDL2 WindowManager::setIcon(); #endif // USE_SDL2 ConfigManager::checkConfigVersion(); logVars(); Cpu::detect(); DyePalette::initFunctions(); #if defined(USE_OPENGL) #if !defined(ANDROID) && !defined(__APPLE__) #if !defined(__native_client__) && !defined(__SWITCH__) && !defined(UNITTESTS) if (!settings.options.safeMode && settings.options.renderer < 0 && settings.options.test.empty() && !settings.options.validate && !config.getBoolValue("videodetected")) { graphicsManager.detectVideoSettings(); } #endif // !defined(__native_client__) && !defined(__SWITCH__) && // !defined(UNITTESTS) #endif // !defined(ANDROID) && !defined(__APPLE__) && #endif // defined(USE_OPENGL) initGraphics(); UserAgent::update(); touchManager.init(); #ifndef WIN32 Dirs::extractDataDir(); Dirs::mountDataDir(); #endif // WIN32 Dirs::updateDataPath(); // Add the main data directories to our VirtFs search path if (!settings.options.dataPath.empty()) { VirtFs::mountDir(settings.options.dataPath, Append_false); } // Add the local data directory to VirtFs search path VirtFs::mountDir(settings.localDataDir, Append_false); TranslationManager::loadCurrentLang(); TranslationManager::loadDictionaryLang(); #ifdef ENABLE_CUSTOMNLS TranslationManager::loadGettextLang(); #endif // ENABLE_CUSTOMNLS #ifdef USE_SDL2 WindowManager::setIcon(); #endif // USE_SDL2 WindowManager::initTitle(); mainGraphics->postInit(); theme = new Theme; Theme::selectSkin(); ActorSprite::load(); touchManager.init(); // Initialize the item and emote shortcuts. for (size_t f = 0; f < SHORTCUT_TABS; f ++) itemShortcut[f] = new ItemShortcut(f); emoteShortcut = new EmoteShortcut; dropShortcut = new DropShortcut; gui = new Gui; gui->postInit(mainGraphics); dialogsManager = new DialogsManager; popupManager = new PopupManager; initSoundManager(); // Initialize keyboard keyboard.init(); inputManager.init(); // Initialise player relations playerRelations.init(); Joystick::init(); WindowManager::createWindows(); keyboard.update(); if (joystick != nullptr) joystick->update(); // Initialize default server mCurrentServer.hostname = settings.options.serverName; mCurrentServer.port = settings.options.serverPort; if (!settings.options.serverType.empty()) { mCurrentServer.type = ServerInfo::parseType( settings.options.serverType); } loginData.username = settings.options.username; loginData.password = settings.options.password; LoginDialog::savedPassword = settings.options.password; loginData.remember = (serverConfig.getValue("remember", 1) != 0); loginData.registerLogin = false; if (mCurrentServer.hostname.empty()) { mCurrentServer.hostname = branding.getValue("defaultServer", ""); settings.options.serverName = mCurrentServer.hostname; } if (mCurrentServer.port == 0) { mCurrentServer.port = CAST_U16(branding.getValue( "defaultPort", CAST_S32(DEFAULT_PORT))); mCurrentServer.type = ServerInfo::parseType( branding.getValue("defaultServerType", "tmwathena")); } chatLogger->setServerName(mCurrentServer.hostname); if (loginData.username.empty() && loginData.remember) loginData.username = serverConfig.getValue("username", ""); if (mState != State::ERROR) mState = State::CHOOSE_SERVER; startTimers(); const int fpsLimit = config.getIntValue("fpslimit"); settings.limitFps = fpsLimit > 0; SDL_initFramerate(&fpsManager); WindowManager::setFramerate(fpsLimit); initConfigListeners(); settings.guiAlpha = config.getFloatValue("guialpha"); optionChanged("fpslimit"); start_time = time(nullptr); PlayerInfo::init(); #ifdef ANDROID #ifndef USE_SDL2 WindowManager::updateScreenKeyboard(SDL_GetScreenKeyboardHeight(nullptr)); #endif // USE_SDL2 #endif // ANDROID #ifdef USE_MUMBLE if (!mumbleManager) mumbleManager = new MumbleManager; #endif // USE_MUMBLE mSkin = theme->load("windowmenu.xml", "", true, Theme::getThemePath()); if (mSkin != nullptr) { mButtonPadding = mSkin->getPadding(); mButtonSpacing = mSkin->getOption("spacing", 3); } if (settings.options.error) inputManager.executeAction(InputAction::ERROR); if (settings.options.validate == true) runValidate(); } Client::~Client() { if (!settings.options.testMode) gameClear(); else testsClear(); CHECKLISTENERS } void Client::initConfigListeners() { config.addListener("fpslimit", this); config.addListener("guialpha", this); config.addListener("gamma", this); config.addListener("enableGamma", this); config.addListener("particleEmitterSkip", this); config.addListener("vsync", this); config.addListener("repeateDelay", this); config.addListener("repeateInterval", this); config.addListener("logInput", this); } void Client::initSoundManager() { // Initialize sound engine try { if (config.getBoolValue("sound")) soundManager.init(); soundManager.setSfxVolume(config.getIntValue("sfxVolume")); soundManager.setMusicVolume(config.getIntValue("musicVolume")); } catch (const char *const err) { mState = State::ERROR; errorMessage = err; logger->log("Warning: %s", err); } soundManager.playMusic(branding.getValue( "loginMusic", "keprohm.ogg"), SkipError_true); } void Client::initGraphics() { #ifndef USE_SDL2 WindowManager::applyVSync(); #endif // USE_SDL2 runCounters = config.getBoolValue("packetcounters"); graphicsManager.initGraphics(); #ifdef USE_SDL2 WindowManager::applyVSync(); #endif // USE_SDL2 imageHelper->postInit(); setConfigDefaults2(config); WindowManager::applyGrabMode(); WindowManager::applyGamma(); mainGraphics->beginDraw(); } void Client::testsClear() { if (!settings.options.test.empty()) gameClear(); else BeingInfo::clear(); } void Client::gameClear() { if (logger != nullptr) logger->log1("Quitting1"); isTerminate = true; config.removeListeners(this); delete2(assertListener) IPC::stop(); eventsManager.shutdown(); WindowManager::deleteWindows(); if (windowContainer != nullptr) windowContainer->slowLogic(); stopTimers(); DbManager::unloadDb(); if (loginHandler != nullptr) loginHandler->clearWorlds(); if (chatHandler != nullptr) chatHandler->clear(); if (charServerHandler != nullptr) charServerHandler->clear(); delete2(ipc) #ifdef USE_MUMBLE delete2(mumbleManager) #endif // USE_MUMBLE PlayerInfo::deinit(); // Before config.write() since it writes the shortcuts to the config for (unsigned f = 0; f < SHORTCUT_TABS; f ++) delete2(itemShortcut[f]) delete2(emoteShortcut) delete2(dropShortcut) playerRelations.store(); if (logger != nullptr) logger->log1("Quitting2"); delete2(mCurrentDialog) delete2(popupManager) delete2(dialogsManager) delete2(gui) if (inventoryHandler != nullptr) inventoryHandler->clear(); if (logger != nullptr) logger->log1("Quitting3"); touchManager.clear(); GraphicsManager::deleteRenderers(); if (logger != nullptr) logger->log1("Quitting4"); XML::cleanupXML(); if (logger != nullptr) logger->log1("Quitting5"); BeingInfo::clear(); // Shutdown sound soundManager.close(); if (logger != nullptr) logger->log1("Quitting6"); ActorSprite::unload(); ResourceManager::deleteInstance(); soundManager.shutdown(); if (logger != nullptr) logger->log1("Quitting8"); WindowManager::deleteIcon(); if (logger != nullptr) logger->log1("Quitting9"); delete2(joystick) keyboard.deinit(); if (logger != nullptr) logger->log1("Quitting10"); touchManager.shutdown(); #ifdef DEBUG_CONFIG config.enableKeyLogging(); #endif // DEBUG_CONFIG config.removeOldKeys(); config.write(); serverConfig.write(); config.clear(); serverConfig.clear(); if (logger != nullptr) logger->log1("Quitting11"); #ifdef USE_PROFILER Perfomance::clear(); #endif // USE_PROFILER #ifdef DEBUG_OPENGL_LEAKS if (logger) logger->log("textures left: %d", textures_count); #endif // DEBUG_OPENGL_LEAKS Graphics::cleanUp(); if (logger != nullptr) logger->log1("Quitting12"); delete2(chatLogger) TranslationManager::close(); } int Client::testsExec() { #ifdef USE_OPENGL if (settings.options.test.empty()) { TestMain test; return test.exec(); } else { TestLauncher launcher(settings.options.test); return launcher.exec(); } #else // USE_OPENGL return 0; #endif // USE_OPENGL } #define ADDBUTTON(var, object) var = object; \ x -= var->getWidth() + mButtonSpacing; \ var->setPosition(x, mButtonPadding); \ top->add(var); void Client::stateConnectGame1() { if ((gameHandler != nullptr) && (loginHandler != nullptr) && gameHandler->isConnected()) { loginHandler->disconnect(); } } void Client::stateConnectServer1() { if (mOldState == State::CHOOSE_SERVER) { settings.serverName = mCurrentServer.hostname; ConfigManager::initServerConfig(mCurrentServer.hostname); PacketLimiter::initPacketLimiter(); initTradeFilter(); Dirs::initUsersDir(); playerRelations.init(); // Initialize the item and emote shortcuts. for (unsigned f = 0; f < SHORTCUT_TABS; f ++) { delete itemShortcut[f]; itemShortcut[f] = new ItemShortcut(f); } delete emoteShortcut; emoteShortcut = new EmoteShortcut; // Initialize the drop shortcuts. delete dropShortcut; dropShortcut = new DropShortcut; initFeatures(); PlayerInfo::loadData(); loginData.registerUrl = mCurrentServer.registerUrl; loginData.packetVersion = mCurrentServer.packetVersion; if (!mCurrentServer.onlineListUrl.empty()) settings.onlineListUrl = mCurrentServer.onlineListUrl; else settings.onlineListUrl = settings.serverName; settings.persistentIp = mCurrentServer.persistentIp; settings.supportUrl = mCurrentServer.supportUrl; settings.updateMirrors = mCurrentServer.updateMirrors; settings.enableRemoteCommands = (serverConfig.getValue( "enableRemoteCommands", 1) != 0); if (settings.options.username.empty()) { if (loginData.remember) loginData.username = serverConfig.getValue("username", ""); else loginData.username.clear(); } else { loginData.username = settings.options.username; } #ifdef SAVE_PASSWORD LoginDialog::savedPassword = loginData.remember ? serverConfig.getValue("password", "") : ""; loginData.password = LoginDialog::savedPassword; settings.options.password = LoginDialog::savedPassword; #endif settings.login = loginData.username; WindowManager::updateTitle(); loginData.remember = (serverConfig.getValue("remember", 1) != 0); Net::connectToServer(mCurrentServer); #ifdef USE_MUMBLE if (mumbleManager) mumbleManager->setServer(mCurrentServer.hostname); #endif // USE_MUMBLE #ifdef TMWA_SUPPORT GuildManager::init(); #endif // TMWA_SUPPORT if (!mConfigAutoSaved) { mConfigAutoSaved = true; config.write(); } } else if (loginHandler != nullptr && loginHandler->isConnected()) { mState = State::PRE_LOGIN; } } void Client::stateWorldSelect1() { if (mOldState == State::UPDATE && (loginHandler != nullptr)) { if (loginHandler->getWorlds().size() < 2) mState = State::PRE_LOGIN; } } void Client::stateGame1() { if (gui == nullptr) return; BasicContainer2 *const top = static_cast( gui->getTop()); if (top == nullptr) return; CREATEWIDGETV(desktop, Desktop, nullptr); top->add(desktop); int x = top->getWidth() - mButtonPadding; ADDBUTTON(mSetupButton, new Button(desktop, // TRANSLATORS: setup tab quick button _("Setup"), "Setup", BUTTON_SKIN, this)) ADDBUTTON(mPerfomanceButton, new Button(desktop, // TRANSLATORS: perfoamance tab quick button _("Performance"), "Perfomance", BUTTON_SKIN, this)) ADDBUTTON(mVideoButton, new Button(desktop, // TRANSLATORS: video tab quick button _("Video"), "Video", BUTTON_SKIN, this)) ADDBUTTON(mThemesButton, new Button(desktop, // TRANSLATORS: theme tab quick button _("Theme"), "Themes", BUTTON_SKIN, this)) ADDBUTTON(mAboutButton, new Button(desktop, // TRANSLATORS: theme tab quick button _("About"), "about", BUTTON_SKIN, this)) ADDBUTTON(mHelpButton, new Button(desktop, // TRANSLATORS: theme tab quick button _("Help"), "help", BUTTON_SKIN, this)) #ifdef ANDROID ADDBUTTON(mCloseButton, new Button(desktop, // TRANSLATORS: close quick button _("Close"), "close", BUTTON_SKIN, this)) #endif // ANDROID desktop->setSize(mainGraphics->getWidth(), mainGraphics->getHeight()); } void Client::stateSwitchLogin1() { if (mOldState == State::GAME && (gameHandler != nullptr)) { gameHandler->disconnect(); } } int Client::gameExec() { int lastTickTime = tick_time; Perf::init(); while (mState != State::EXIT) { PROFILER_START(); PERF_STAT(0); if (eventsManager.handleEvents()) continue; PERF_STAT(1); BLOCK_START("Client::gameExec 3") if (generalHandler != nullptr) generalHandler->flushNetwork(); BLOCK_END("Client::gameExec 3") PERF_STAT(2); BLOCK_START("Client::gameExec 4") if (gui != nullptr) gui->logic(); PERF_STAT(3); cur_time = time(nullptr); int k = 0; while (lastTickTime != tick_time && k < 40) { if (mGame != nullptr) mGame->logic(); else if (gui != nullptr) gui->handleInput(); ++lastTickTime; k ++; } PERF_STAT(4); soundManager.logic(); PERF_STAT(5); logic_count = logic_count + k; if (gui != nullptr) gui->slowLogic(); PERF_STAT(6); if (mGame != nullptr) mGame->slowLogic(); PERF_STAT(7); slowLogic(); PERF_STAT(8); BLOCK_END("Client::gameExec 4") // This is done because at some point tick_time will wrap. lastTickTime = tick_time; BLOCK_START("Client::gameExec 6") if (mState == State::CONNECT_GAME) { stateConnectGame1(); } else if (mState == State::CONNECT_SERVER) { stateConnectServer1(); } else if (mState == State::WORLD_SELECT) { stateWorldSelect1(); } else if (mOldState == State::START || (mOldState == State::GAME && mState != State::GAME)) { stateGame1(); } else if (mState == State::SWITCH_LOGIN) { stateSwitchLogin1(); } BLOCK_END("Client::gameExec 6") PERF_STAT(9); if (mState != mOldState) { BLOCK_START("Client::gameExec 7") PlayerInfo::stateChange(mState); if (mOldState == State::GAME) { delete2(mGame) assertListener = new AssertListener; Game::clearInstance(); ResourceManager::cleanOrphans(false); Party::clearParties(); Guild::clearGuilds(); NpcDialog::clearDialogs(); if (guildHandler != nullptr) guildHandler->clear(); if (partyHandler != nullptr) partyHandler->clear(); if (chatLogger != nullptr) chatLogger->clear(); if (!settings.options.dataPath.empty()) UpdaterWindow::unloadMods(settings.options.dataPath); else UpdaterWindow::unloadMods(settings.oldUpdates); if (!settings.options.skipUpdate) UpdaterWindow::unloadMods(settings.oldUpdates + "/fix/"); } else if (mOldState == State::CHAR_SELECT) { if (mState != State::CHANGEPASSWORD && charServerHandler != nullptr) { charServerHandler->clear(); } } mOldState = mState; // Get rid of the dialog of the previous state delete2(mCurrentDialog) // State has changed, while the quitDialog was active, it might // not be correct anymore if (mQuitDialog != nullptr) { mQuitDialog->scheduleDelete(); mQuitDialog = nullptr; } BLOCK_END("Client::gameExec 7") BLOCK_START("Client::gameExec 8") switch (mState) { case State::CHOOSE_SERVER: { BLOCK_START("Client::gameExec STATE_CHOOSE_SERVER") logger->log1("State: CHOOSE SERVER"); unloadData(); pincodeManager.closeDialogs(); // Allow changing this using a server choice dialog // We show the dialog box only if the command-line // options weren't set. if (settings.options.serverName.empty() && settings.options.serverPort == 0 && !branding.getValue("onlineServerList", "a").empty()) { // Don't allow an alpha opacity // lower than the default value theme->setMinimumOpacity(0.8F); CREATEWIDGETV(mCurrentDialog, ServerDialog, &mCurrentServer, settings.configDir); } else { mState = State::CONNECT_SERVER; // Reset options so that cancelling or connect // timeout will show the server dialog. settings.options.serverName.clear(); settings.options.serverPort = 0; } BLOCK_END("Client::gameExec STATE_CHOOSE_SERVER") break; } case State::CONNECT_SERVER: BLOCK_START("Client::gameExec State::CONNECT_SERVER") logger->log1("State: CONNECT SERVER"); loginData.updateHosts.clear(); CREATEWIDGETV(mCurrentDialog, ConnectionDialog, // TRANSLATORS: connection dialog header _("Connecting to server"), State::SWITCH_SERVER); TranslationManager::loadCurrentLang(); TranslationManager::loadDictionaryLang(); pincodeManager.init(); BLOCK_END("Client::gameExec State::CONNECT_SERVER") break; case State::PRE_LOGIN: logger->log1("State: PRE_LOGIN"); break; case State::LOGIN: BLOCK_START("Client::gameExec State::LOGIN") logger->log1("State: LOGIN"); // Don't allow an alpha opacity // lower than the default value theme->setMinimumOpacity(0.8F); if (packetVersion == 0) { packetVersion = loginData.packetVersion; if (packetVersion != 0) { loginHandler->updatePacketVersion(); logger->log("Preconfigured packet version: %d", packetVersion); } } loginData.updateType = static_cast( serverConfig.getValue("updateType", 0)); mSearchHash = Net::Download::adlerBuffer( const_cast(mCurrentServer.hostname.c_str()), CAST_S32(mCurrentServer.hostname.size())); if (settings.options.username.empty() || settings.options.password.empty()) { CREATEWIDGETV(mCurrentDialog, LoginDialog, loginData, &mCurrentServer, &settings.options.updateHost); } else { mState = State::LOGIN_ATTEMPT; // Clear the password so that when login fails, the // dialog will show up next time. settings.options.password.clear(); } BLOCK_END("Client::gameExec State::LOGIN") break; case State::LOGIN_ATTEMPT: BLOCK_START("Client::gameExec State::LOGIN_ATTEMPT") logger->log1("State: LOGIN ATTEMPT"); CREATEWIDGETV(mCurrentDialog, ConnectionDialog, // TRANSLATORS: connection dialog header _("Logging in"), State::SWITCH_SERVER); if (loginHandler != nullptr) loginHandler->loginOrRegister(&loginData); BLOCK_END("Client::gameExec State::LOGIN_ATTEMPT") break; case State::WORLD_SELECT: BLOCK_START("Client::gameExec State::WORLD_SELECT") logger->log1("State: WORLD SELECT"); { TranslationManager::loadCurrentLang(); TranslationManager::loadDictionaryLang(); if (loginHandler == nullptr) { BLOCK_END("Client::gameExec State::WORLD_SELECT") break; } Worlds worlds = loginHandler->getWorlds(); if (worlds.empty()) { // Trust that the netcode knows what it's doing mState = State::UPDATE; } else if (worlds.size() == 1) { loginHandler->chooseServer( 0, mCurrentServer.persistentIp); mState = State::UPDATE; } else { CREATEWIDGETV(mCurrentDialog, WorldSelectDialog, worlds); if (settings.options.chooseDefault) { static_cast(mCurrentDialog) ->action(ActionEvent(nullptr, "ok")); } } } BLOCK_END("Client::gameExec State::WORLD_SELECT") break; case State::WORLD_SELECT_ATTEMPT: BLOCK_START("Client::gameExec State::WORLD_SELECT_ATTEMPT") logger->log1("State: WORLD SELECT ATTEMPT"); CREATEWIDGETV(mCurrentDialog, ConnectionDialog, // TRANSLATORS: connection dialog header _("Entering game world"), State::WORLD_SELECT); BLOCK_END("Client::gameExec State::WORLD_SELECT_ATTEMPT") break; case State::UPDATE: BLOCK_START("Client::gameExec State::UPDATE") logger->log1("State: UPDATE"); // Determine which source to use for the update host if (!settings.options.updateHost.empty()) settings.updateHost = settings.options.updateHost; else settings.updateHost = loginData.updateHost; Dirs::initUpdatesDir(); if (!settings.oldUpdates.empty()) UpdaterWindow::unloadUpdates(settings.oldUpdates); if (settings.options.skipUpdate) { mState = State::LOAD_DATA; settings.oldUpdates.clear(); UpdaterWindow::loadDirMods(settings.options.dataPath); } else if ((loginData.updateType & UpdateType::Skip) != 0) { settings.oldUpdates = pathJoin(settings.localDataDir, settings.updatesDir); UpdaterWindow::loadLocalUpdates(settings.oldUpdates); mState = State::LOAD_DATA; } else { settings.oldUpdates = pathJoin(settings.localDataDir, settings.updatesDir); CREATEWIDGETV(mCurrentDialog, UpdaterWindow, settings.updateHost, settings.oldUpdates, settings.options.dataPath.empty(), loginData.updateType); } BLOCK_END("Client::gameExec State::UPDATE") break; case State::LOAD_DATA: { BLOCK_START("Client::gameExec State::LOAD_DATA") logger->log1("State: LOAD DATA"); loadData(); mState = State::GET_CHARACTERS; BLOCK_END("Client::gameExec State::LOAD_DATA") break; } case State::GET_CHARACTERS: BLOCK_START("Client::gameExec State::GET_CHARACTERS") logger->log1("State: GET CHARACTERS"); CREATEWIDGETV(mCurrentDialog, ConnectionDialog, // TRANSLATORS: connection dialog header _("Requesting characters"), State::SWITCH_SERVER); if (charServerHandler != nullptr) charServerHandler->requestCharacters(); BLOCK_END("Client::gameExec State::GET_CHARACTERS") break; case State::CHAR_SELECT: BLOCK_START("Client::gameExec State::CHAR_SELECT") logger->log1("State: CHAR SELECT"); // Don't allow an alpha opacity // lower than the default value theme->setMinimumOpacity(0.8F); settings.login = loginData.username; WindowManager::updateTitle(); CREATEWIDGETV(mCurrentDialog, CharSelectDialog, loginData); pincodeManager.updateState(); if (!(static_cast(mCurrentDialog)) ->selectByName(settings.options.character, CharSelectDialog::Choose)) { (static_cast(mCurrentDialog)) ->selectByName( serverConfig.getValue("lastCharacter", ""), settings.options.chooseDefault ? CharSelectDialog::Choose : CharSelectDialog::Focus); } // Choosing character on the command line should work only // once, clear it so that 'switch character' works. settings.options.character.clear(); BLOCK_END("Client::gameExec State::CHAR_SELECT") break; case State::CONNECT_GAME: BLOCK_START("Client::gameExec State::CONNECT_GAME") logger->log1("State: CONNECT GAME"); CREATEWIDGETV(mCurrentDialog, ConnectionDialog, // TRANSLATORS: connection dialog header _("Connecting to the game server"), State::CHOOSE_SERVER); if (gameHandler != nullptr) gameHandler->connect(); BLOCK_END("Client::gameExec State::CONNECT_GAME") break; case State::CHANGE_MAP: BLOCK_START("Client::gameExec State::CHANGE_MAP") logger->log1("State: CHANGE_MAP"); CREATEWIDGETV(mCurrentDialog, ConnectionDialog, // TRANSLATORS: connection dialog header _("Changing game servers"), State::SWITCH_CHARACTER); if (gameHandler != nullptr) gameHandler->connect(); BLOCK_END("Client::gameExec State::CHANGE_MAP") break; case State::GAME: BLOCK_START("Client::gameExec State::GAME") if (localPlayer != nullptr) { logger->log("Memorizing selected character %s", localPlayer->getName().c_str()); serverConfig.setValue("lastCharacter", localPlayer->getName()); #ifdef USE_MUMBLE if (mumbleManager) mumbleManager->setPlayer(localPlayer->getName()); #endif // USE_MUMBLE Perf::init(); } // Fade out logon-music here too to give the desired effect // of "flowing" into the game. soundManager.fadeOutMusic(1000); // Allow any alpha opacity theme->setMinimumOpacity(-1.0F); if (chatLogger != nullptr) chatLogger->setServerName(settings.serverName); #ifdef ANDROID delete2(mCloseButton) #endif // ANDROID delete2(mSetupButton) delete2(mVideoButton) delete2(mThemesButton) delete2(mAboutButton) delete2(mHelpButton) delete2(mPerfomanceButton) delete2(desktop) mCurrentDialog = nullptr; logger->log1("State: GAME"); if (generalHandler != nullptr) generalHandler->reloadPartially(); mGame = new Game; BLOCK_END("Client::gameExec State::GAME") break; case State::LOGIN_ERROR: BLOCK_START("Client::gameExec State::LOGIN_ERROR") logger->log1("State: LOGIN ERROR"); CREATEWIDGETV(mCurrentDialog, OkDialog, // TRANSLATORS: error dialog header _("Error"), errorMessage, // TRANSLATORS: ok dialog button _("Close"), DialogType::ERROR, Modal_true, ShowCenter_true, nullptr, 260); mCurrentDialog->addActionListener(&loginListener); mCurrentDialog = nullptr; // OkDialog deletes itself BLOCK_END("Client::gameExec State::LOGIN_ERROR") break; case State::ACCOUNTCHANGE_ERROR: BLOCK_START("Client::gameExec State::ACCOUNTCHANGE_ERROR") logger->log1("State: ACCOUNT CHANGE ERROR"); CREATEWIDGETV(mCurrentDialog, OkDialog, // TRANSLATORS: error dialog header _("Error"), errorMessage, // TRANSLATORS: ok dialog button _("Close"), DialogType::ERROR, Modal_true, ShowCenter_true, nullptr, 260); mCurrentDialog->addActionListener(&accountListener); mCurrentDialog = nullptr; // OkDialog deletes itself BLOCK_END("Client::gameExec State::ACCOUNTCHANGE_ERROR") break; case State::REGISTER_PREP: BLOCK_START("Client::gameExec State::REGISTER_PREP") logger->log1("State: REGISTER_PREP"); CREATEWIDGETV(mCurrentDialog, ConnectionDialog, // TRANSLATORS: connection dialog header _("Requesting registration details"), State::LOGIN); loginHandler->getRegistrationDetails(); BLOCK_END("Client::gameExec State::REGISTER_PREP") break; case State::REGISTER: logger->log1("State: REGISTER"); CREATEWIDGETV(mCurrentDialog, RegisterDialog, loginData); break; case State::REGISTER_ATTEMPT: BLOCK_START("Client::gameExec State::REGISTER_ATTEMPT") logger->log("Username is %s", loginData.username.c_str()); if (loginHandler != nullptr) loginHandler->registerAccount(&loginData); BLOCK_END("Client::gameExec State::REGISTER_ATTEMPT") break; case State::CHANGEPASSWORD: BLOCK_START("Client::gameExec State::CHANGEPASSWORD") logger->log1("State: CHANGE PASSWORD"); CREATEWIDGETV(mCurrentDialog, ChangePasswordDialog, loginData); mCurrentDialog->setVisible(Visible_true); BLOCK_END("Client::gameExec State::CHANGEPASSWORD") break; case State::CHANGEPASSWORD_ATTEMPT: BLOCK_START("Client::gameExec " "State::CHANGEPASSWORD_ATTEMPT") logger->log1("State: CHANGE PASSWORD ATTEMPT"); if (loginHandler != nullptr) { loginHandler->changePassword(loginData.password, loginData.newPassword); } BLOCK_END("Client::gameExec State::CHANGEPASSWORD_ATTEMPT") break; case State::CHANGEPASSWORD_SUCCESS: BLOCK_START("Client::gameExec " "State::CHANGEPASSWORD_SUCCESS") logger->log1("State: CHANGE PASSWORD SUCCESS"); CREATEWIDGETV(mCurrentDialog, OkDialog, // TRANSLATORS: password change message header _("Password Change"), // TRANSLATORS: password change message text _("Password changed successfully!"), // TRANSLATORS: ok dialog button _("OK"), DialogType::ERROR, Modal_true, ShowCenter_true, nullptr, 260); mCurrentDialog->addActionListener(&accountListener); mCurrentDialog = nullptr; // OkDialog deletes itself loginData.password = loginData.newPassword; loginData.newPassword.clear(); BLOCK_END("Client::gameExec State::CHANGEPASSWORD_SUCCESS") break; case State::CHANGEEMAIL: logger->log1("State: CHANGE EMAIL"); CREATEWIDGETV(mCurrentDialog, ChangeEmailDialog, loginData); mCurrentDialog->setVisible(Visible_true); break; case State::CHANGEEMAIL_ATTEMPT: logger->log1("State: CHANGE EMAIL ATTEMPT"); if (loginHandler != nullptr) loginHandler->changeEmail(loginData.email); break; case State::CHANGEEMAIL_SUCCESS: logger->log1("State: CHANGE EMAIL SUCCESS"); CREATEWIDGETV(mCurrentDialog, OkDialog, // TRANSLATORS: email change message header _("Email Change"), // TRANSLATORS: email change message text _("Email changed successfully!"), // TRANSLATORS: ok dialog button _("OK"), DialogType::ERROR, Modal_true, ShowCenter_true, nullptr, 260); mCurrentDialog->addActionListener(&accountListener); mCurrentDialog = nullptr; // OkDialog deletes itself break; case State::SWITCH_SERVER: BLOCK_START("Client::gameExec State::SWITCH_SERVER") logger->log1("State: SWITCH SERVER"); if (loginHandler != nullptr) loginHandler->disconnect(); if (gameHandler != nullptr) { gameHandler->disconnect(); gameHandler->clear(); } settings.serverName.clear(); settings.login.clear(); WindowManager::updateTitle(); serverConfig.write(); serverConfig.unload(); if (setupWindow != nullptr) setupWindow->externalUnload(); mState = State::CHOOSE_SERVER; BLOCK_END("Client::gameExec State::SWITCH_SERVER") break; case State::SWITCH_LOGIN: BLOCK_START("Client::gameExec State::SWITCH_LOGIN") logger->log1("State: SWITCH LOGIN"); if (loginHandler != nullptr) { loginHandler->logout(); loginHandler->disconnect(); } if (gameHandler != nullptr) gameHandler->disconnect(); if (loginHandler != nullptr) loginHandler->connect(); settings.login.clear(); WindowManager::updateTitle(); mState = State::LOGIN; BLOCK_END("Client::gameExec State::SWITCH_LOGIN") break; case State::SWITCH_CHARACTER: BLOCK_START("Client::gameExec State::SWITCH_CHARACTER") logger->log1("State: SWITCH CHARACTER"); // Done with game if (gameHandler != nullptr) gameHandler->disconnect(); settings.login.clear(); WindowManager::updateTitle(); mState = State::GET_CHARACTERS; BLOCK_END("Client::gameExec State::SWITCH_CHARACTER") break; case State::LOGOUT_ATTEMPT: logger->log1("State: LOGOUT ATTEMPT"); break; case State::WAIT: logger->log1("State: WAIT"); break; case State::EXIT: BLOCK_START("Client::gameExec State::EXIT") logger->log1("State: EXIT"); Net::unload(); BLOCK_END("Client::gameExec State::EXIT") break; case State::FORCE_QUIT: BLOCK_START("Client::gameExec State::FORCE_QUIT") logger->log1("State: FORCE QUIT"); if (generalHandler != nullptr) generalHandler->unload(); mState = State::EXIT; BLOCK_END("Client::gameExec State::FORCE_QUIT") break; case State::ERROR: BLOCK_START("Client::gameExec State::ERROR") config.write(); #ifdef SAVE_PASSWORD if (errorMessage == "Wrong password.") { serverConfig.setValue("password", ""); serverConfig.write(); } #endif if (mOldState == State::GAME) serverConfig.write(); logger->log1("State: ERROR"); logger->log("Error: %s\n", errorMessage.c_str()); pincodeManager.closeDialogs(); mCurrentDialog = DialogsManager::openErrorDialog( // TRANSLATORS: error message header _("Error"), errorMessage, Modal_true); mCurrentDialog->addActionListener(&errorListener); mCurrentDialog = nullptr; // OkDialog deletes itself gameHandler->disconnect(); BLOCK_END("Client::gameExec State::ERROR") break; case State::AUTORECONNECT_SERVER: // ++++++ break; case State::START: default: mState = State::FORCE_QUIT; break; } BLOCK_END("Client::gameExec 8") } PERF_STAT(10); // Update the screen when application is visible, delay otherwise. if (!WindowManager::getIsMinimized()) { frame_count = frame_count + 1; if (gui != nullptr) gui->draw(); mainGraphics->updateScreen(); } else { SDL_Delay(100); } PERF_STAT(11); BLOCK_START("~Client::SDL_framerateDelay") if (settings.limitFps) SDL_framerateDelay(&fpsManager); BLOCK_END("~Client::SDL_framerateDelay") PERF_STAT(12); PERF_NEXTFRAME(); PROFILER_END(); } return settings.exitcode; } void Client::optionChanged(const std::string &name) { if (name == "fpslimit") { const int fpsLimit = config.getIntValue("fpslimit"); settings.limitFps = fpsLimit > 0; WindowManager::setFramerate(fpsLimit); } else if (name == "guialpha" || name == "enableGuiOpacity") { const float alpha = config.getFloatValue("guialpha"); settings.guiAlpha = alpha; ImageHelper::setEnableAlpha(alpha != 1.0F && config.getBoolValue("enableGuiOpacity")); } else if (name == "gamma" || name == "enableGamma") { WindowManager::applyGamma(); } else if (name == "particleEmitterSkip") { ParticleEngine::emitterSkip = config.getIntValue("particleEmitterSkip") + 1; } else if (name == "vsync") { WindowManager::applyVSync(); } else if (name == "repeateInterval" || name == "repeateDelay") { WindowManager::applyKeyRepeat(); } } void Client::action(const ActionEvent &event) { std::string tab; const std::string &eventId = event.getId(); if (eventId == "close") { setState(State::FORCE_QUIT); return; } if (eventId == "Setup") { tab.clear(); } else if (eventId == "help") { inputManager.executeAction(InputAction::WINDOW_HELP); return; } else if (eventId == "about") { inputManager.executeAction(InputAction::WINDOW_ABOUT); return; } else if (eventId == "Video") { tab = "Video"; } else if (eventId == "Themes") { tab = "Theme"; } else if (eventId == "Perfomance") { tab = "Perfomance"; } else { return; } if (setupWindow != nullptr) { setupWindow->setVisible(fromBool( !setupWindow->isWindowVisible(), Visible)); if (setupWindow->isWindowVisible()) { if (!tab.empty()) setupWindow->activateTab(tab); setupWindow->requestMoveToTop(); } } } void Client::initFeatures() { features.init(paths.getStringValue("featuresFile"), UseVirtFs_true, SkipError_true); setFeaturesDefaults(features); settings.fixDeadAnimation = features.getBoolValue("fixDeadAnimation"); } void Client::initPaths() { settings.gmCommandSymbol = paths.getStringValue("gmCommandSymbol"); settings.gmCharCommandSymbol = paths.getStringValue("gmCharCommandSymbol"); settings.linkCommandSymbol = paths.getStringValue("linkCommandSymbol"); if (settings.linkCommandSymbol.empty()) settings.linkCommandSymbol = "="; settings.overweightPercent = paths.getIntValue("overweightPercent"); settings.fixedInventorySize = paths.getIntValue("fixedInventorySize"); settings.playerNameOffset = paths.getIntValue( "playerNameOffset"); settings.playerBadgeAtRightOffset = paths.getIntValue( "playerBadgeAtRightOffset"); settings.unknownSkillsAutoTab = paths.getBoolValue("unknownSkillsAutoTab"); settings.enableNewMailSystem = paths.getBoolValue("enableNewMailSystem"); } void Client::initTradeFilter() { const std::string tradeListName = settings.serverConfigDir + "/tradefilter.txt"; std::ofstream tradeFile; struct stat statbuf; if ((stat(tradeListName.c_str(), &statbuf) != 0) || !S_ISREG(statbuf.st_mode)) { tradeFile.open(tradeListName.c_str(), std::ios::out); if (tradeFile.is_open()) { tradeFile << ": sell" << std::endl; tradeFile << ": buy" << std::endl; tradeFile << ": trade" << std::endl; tradeFile << "i sell" << std::endl; tradeFile << "i buy" << std::endl; tradeFile << "i trade" << std::endl; tradeFile << "i trading" << std::endl; tradeFile << "i am buy" << std::endl; tradeFile << "i am sell" << std::endl; tradeFile << "i am trade" << std::endl; tradeFile << "i am trading" << std::endl; tradeFile << "i'm buy" << std::endl; tradeFile << "i'm sell" << std::endl; tradeFile << "i'm trade" << std::endl; tradeFile << "i'm trading" << std::endl; } else { reportAlways("Error opening file for writing: %s", tradeListName.c_str()) } tradeFile.close(); } } bool Client::isTmw() { const std::string &name = settings.serverName; if (name == "server.themanaworld.org" || name == "themanaworld.org" || name == "167.114.129.72") { return true; } return false; } void Client::moveButtons(const int width) { if (mSetupButton != nullptr) { int x = width - mSetupButton->getWidth() - mButtonPadding; mSetupButton->setPosition(x, mButtonPadding); #ifndef WIN32 x -= mPerfomanceButton->getWidth() + mButtonSpacing; mPerfomanceButton->setPosition(x, mButtonPadding); x -= mVideoButton->getWidth() + mButtonSpacing; mVideoButton->setPosition(x, mButtonPadding); x -= mThemesButton->getWidth() + mButtonSpacing; mThemesButton->setPosition(x, mButtonPadding); x -= mAboutButton->getWidth() + mButtonSpacing; mAboutButton->setPosition(x, mButtonPadding); x -= mHelpButton->getWidth() + mButtonSpacing; mHelpButton->setPosition(x, mButtonPadding); #ifdef ANDROID x -= mCloseButton->getWidth() + mButtonSpacing; mCloseButton->setPosition(x, mButtonPadding); #endif // ANDROID #endif // WIN32 } } void Client::windowRemoved(const Window *const window) { if (mCurrentDialog == window) mCurrentDialog = nullptr; } void Client::focusWindow() { if (mCurrentDialog != nullptr) { mCurrentDialog->requestFocus(); } } void Client::updatePinState() { if (mCurrentDialog == nullptr || mState != State::CHAR_SELECT) { return; } CharSelectDialog *const dialog = dynamic_cast(mCurrentDialog); if (dialog != nullptr) pincodeManager.updateState(); } void Client::logVars() { #ifdef ANDROID logger->log("APPDIR: %s", getenv("APPDIR")); logger->log("DATADIR2: %s", getSdStoragePath().c_str()); #endif // ANDROID } void Client::slowLogic() { if ((gameHandler == nullptr) || !gameHandler->mustPing()) { return; } if (get_elapsed_time1(mPing) > 1500) { mPing = tick_time; if (mState == State::UPDATE || mState == State::LOGIN || mState == State::LOGIN_ATTEMPT || mState == State::REGISTER || mState == State::REGISTER_ATTEMPT) { if (loginHandler != nullptr) loginHandler->ping(); if (generalHandler != nullptr) generalHandler->flushSend(); } else if (mState == State::CHAR_SELECT) { if (charServerHandler != nullptr) charServerHandler->ping(); if (generalHandler != nullptr) generalHandler->flushSend(); } } } void Client::loadData() { // If another data path has been set, // we don't load any other files... if (settings.options.dataPath.empty()) { // Add customdata directory VirtFs::searchAndAddArchives( "customdata/", "zip", Append_false); } if (!settings.options.skipUpdate) { VirtFs::searchAndAddArchives( settings.updatesDir + "/local/", "zip", Append_false); VirtFs::mountDir(pathJoin( settings.localDataDir, settings.updatesDir, "local/"), Append_false); } logger->log("Init paths"); paths.init("paths.xml", UseVirtFs_true, SkipError_false); setPathsDefaults(paths); initPaths(); if (SpriteReference::Empty == nullptr) { SpriteReference::Empty = new SpriteReference( paths.getStringValue("spriteErrorFile"), 0); } if (BeingInfo::unknown == nullptr) BeingInfo::unknown = new BeingInfo; initFeatures(); TranslationManager::loadCurrentLang(); TranslationManager::loadDictionaryLang(); PlayerInfo::stateChange(mState); AttributesEnum::init(); DbManager::loadDb(); delete spellManager; spellManager = new SpellManager; delete spellShortcut; spellShortcut = new SpellShortcut; EquipmentWindow::prepareSlotNames(); ActorSprite::load(); if (desktop != nullptr) desktop->reloadWallpaper(); } void Client::unloadData() { DbManager::unloadDb(); mCurrentServer.supportUrl.clear(); settings.supportUrl.clear(); if (settings.options.dataPath.empty()) { // Add customdata directory VirtFs::searchAndRemoveArchives( "customdata/", "zip"); } if (!settings.oldUpdates.empty()) { UpdaterWindow::unloadUpdates(settings.oldUpdates); settings.oldUpdates.clear(); } if (!settings.options.skipUpdate) { VirtFs::searchAndRemoveArchives( pathJoin(settings.updatesDir, "local/"), "zip"); VirtFs::unmountDirSilent(pathJoin( settings.localDataDir, settings.updatesDir, "local/")); } ResourceManager::clearCache(); loginData.clearUpdateHost(); localClan.clear(); serverVersion = 0; packetVersion = 0; packetVersionMain = 0; packetVersionRe = 0; packetVersionZero = 0; tmwServerVersion = 0; evolPacketOffset = 0; } void Client::runValidate() { loadData(); WindowManager::createValidateWindows(); WindowManager::deleteValidateWindows(); unloadData(); delete2(client) VirtFs::deinit(); exit(0); }