/* * The ManaVerse Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2020 The ManaPlus Developers * Copyright (C) 2020-2025 The ManaVerse Developers * * This file is part of The ManaVerse 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 "dirs.h" #include "client.h" #include "configuration.h" #include "logger.h" #include "main.h" #include "settings.h" #include "fs/mkdir.h" #include "fs/paths.h" #include "fs/virtfs/fs.h" #include "utils/base64.h" #include "utils/stringutils.h" #if defined(__native_client__) || (defined(ANDROID) && defined(USE_SDL2)) #include "fs/files.h" #endif // defined(__native_client__) || (defined(ANDROID) && defined(USE_SDL2)) #include "utils/cast.h" #include "utils/gettext.h" #include "utils/performance.h" #ifdef ANDROID #ifdef USE_SDL2 #include "main.h" #include "render/graphics.h" #endif // USE_SDL2 #endif // ANDROID #ifdef __APPLE__ #include #endif // __APPLE__ #ifdef _WIN32 PRAGMA48(GCC diagnostic push) PRAGMA48(GCC diagnostic ignored "-Wshadow") #include PRAGMA48(GCC diagnostic pop) #include "fs/specialfolder.h" #undef ERROR #endif // _WIN32 #include #include #include "debug.h" #if defined __native_client__ #define _nacl_dir std::string("/persistent/manaplus") #endif // defined __native_client__ // Normalize paths to use forward slashes for cross-platform compatibility static std::string normalizePath(const std::string &path) { std::string normalized = path; replaceAll(normalized, "\\", "/"); return normalized; } #ifdef _WIN32 // Convert paths to use backslashes for Windows-specific functions static std::string toWindowsPath(const std::string &path) { std::string winPath = path; replaceAll(winPath, "/", "\\"); return winPath; } #endif #ifdef ANDROID #ifdef USE_SDL2 int loadingProgressCounter = 1; static void updateProgress(int cnt) { const int progress = cnt + loadingProgressCounter; const int h = mainGraphics->mHeight; mainGraphics->setColor(Color(255, 255, 255, 255)); const int maxSize = mainGraphics->mWidth - 100; const int width = maxSize * progress / 50; mainGraphics->fillRectangle(Rect(50, h - 100, width, 50)); mainGraphics->updateScreen(); } void Dirs::setProgress() { loadingProgressCounter++; updateProgress(loadingProgressCounter); } static void resetProgress() { loadingProgressCounter = 0; updateProgress(loadingProgressCounter); } void extractAssets() { if (!getenv("APPDIR")) { logger->log("error: APPDIR is not set!"); return; } const std::string fileName = normalizePath(pathJoin(getenv("APPDIR"), "data.zip")); logger->log("Extracting asset into: " + fileName); uint8_t *buf = new uint8_t[1000000]; FILE *const file = fopen(fileName.c_str(), "w"); for (int f = 0; f < 100; f ++) { std::string part = strprintf("manaplus-data.zip%u%u", CAST_U32(f / 10), CAST_U32(f % 10)); logger->log("testing asset: " + part); SDL_RWops *const rw = SDL_RWFromFile(part.c_str(), "r"); if (rw) { const int size = SDL_RWsize(rw); int size2 = SDL_RWread(rw, buf, 1, size); logger->log("asset size: %d", size2); fwrite(buf, 1, size2, file); SDL_RWclose(rw); Dirs::setProgress(); } else { break; } } fclose(file); const std::string fileName2 = normalizePath(pathJoin(getenv("APPDIR"), "locale.zip")); FILE *const file2 = fopen(fileName2.c_str(), "w"); SDL_RWops *const rw = SDL_RWFromFile("manaplus-locale.zip", "r"); if (rw) { const int size = SDL_RWsize(rw); int size2 = SDL_RWread(rw, buf, 1, size); fwrite(buf, 1, size2, file2); SDL_RWclose(rw); Dirs::setProgress(); } fclose(file2); delete [] buf; } #endif // USE_SDL2 #endif // ANDROID void Dirs::updateDataPath() { if (settings.options.dataPath.empty() && !branding.getStringValue("dataPath").empty()) { std::string dataPath = branding.getStringValue("dataPath"); if (isRealPath(dataPath)) { settings.options.dataPath = normalizePath(dataPath); } else { settings.options.dataPath = normalizePath( pathJoin(branding.getDirectory(), dataPath)); } settings.options.skipUpdate = true; } } void Dirs::extractDataDir() { #if defined(ANDROID) && defined(USE_SDL2) Files::setCopyCallBack(&updateProgress); resetProgress(); extractAssets(); const std::string zipName = normalizePath(pathJoin(getenv("APPDIR"), "data.zip")); const std::string dirName = normalizePath(pathJoin(getenv("APPDIR"), "data")); VirtFs::mountZip2(zipName, "data", Append_false); VirtFs::mountZip2(zipName, "data/perserver/default", Append_false); Files::extractLocale(); #endif // defined(ANDROID) && defined(USE_SDL2) } void Dirs::mountDataDir() { VirtFs::mountDirSilent(normalizePath(PKG_DATADIR "data/perserver/default"), Append_false); VirtFs::mountDirSilent(normalizePath("data/perserver/default"), Append_false); #if defined __APPLE__ CFBundleRef mainBundle = CFBundleGetMainBundle(); CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle); char path[PATH_MAX]; if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, reinterpret_cast(path), PATH_MAX)) { fprintf(stderr, "Can't find Resources directory\n"); } CFRelease(resourcesURL); std::string path2 = normalizePath(pathJoin(path, "data")); VirtFs::mountDir(normalizePath(pathJoin(path2, "perserver/default")), Append_false); VirtFs::mountDir(normalizePath(path2), Append_false); #endif // defined __APPLE__ VirtFs::mountDirSilent(normalizePath(PKG_DATADIR "data"), Append_false); setPackageDir(normalizePath(PKG_DATADIR "data")); VirtFs::mountDirSilent(normalizePath("data"), Append_false); #ifdef ANDROID #ifdef USE_SDL2 if (getenv("APPDIR")) { const std::string appDir = normalizePath(getenv("APPDIR")); VirtFs::mountDir(normalizePath(appDir + "/data"), Append_false); VirtFs::mountDir(normalizePath(appDir + "/data/perserver/default"), Append_false); } #endif // USE_SDL2 #endif // ANDROID #if defined __native_client__ VirtFs::mountZip(normalizePath("/http/data.zip"), Append_false); VirtFs::mountZip2(normalizePath("/http/data.zip"), "perserver/default", Append_false); #endif // defined __native_client__ #ifndef _WIN32 // Add branding/data to VirtFS search path if (!settings.options.brandingPath.empty()) { std::string path = normalizePath(settings.options.brandingPath); // Strip blah.manaplus from the path const int loc = CAST_S32(path.find_last_of('/')); if (loc > 0) { VirtFs::mountDir(normalizePath(path.substr(0, loc + 1).append("data")), Append_false); } } #endif // _WIN32 } void Dirs::initRootDir() { settings.rootDir = normalizePath(VirtFs::getBaseDir()); const std::string portableName = normalizePath(settings.rootDir + "portable.xml"); struct stat statbuf; if (stat(portableName.c_str(), &statbuf) == 0 && S_ISREG(statbuf.st_mode)) { std::string dir; Configuration portable; portable.init(portableName, UseVirtFs_false, SkipError_false); if (settings.options.brandingPath.empty()) { branding.init(portableName, UseVirtFs_false, SkipError_false); setBrandingDefaults(branding); } logger->log("Portable file: %s", portableName.c_str()); if (settings.options.localDataDir.empty()) { dir = portable.getValue("dataDir", ""); if (!dir.empty()) { settings.options.localDataDir = normalizePath(settings.rootDir + dir); logger->log("Portable data dir: %s", settings.options.localDataDir.c_str()); } } if (settings.options.configDir.empty()) { dir = portable.getValue("configDir", ""); if (!dir.empty()) { settings.options.configDir = normalizePath(settings.rootDir + dir); logger->log("Portable config dir: %s", settings.options.configDir.c_str()); } } if (settings.options.screenshotDir.empty()) { dir = portable.getValue("screenshotDir", ""); if (!dir.empty()) { settings.options.screenshotDir = normalizePath(settings.rootDir + dir); logger->log("Portable screenshot dir: %s", settings.options.screenshotDir.c_str()); } } } } void Dirs::initHomeDir() { initLocalDataDir(); initTempDir(); initConfigDir(); } void Dirs::initLocalDataDir() { settings.localDataDir = normalizePath(settings.options.localDataDir); if (settings.localDataDir.empty()) { #ifdef __APPLE__ settings.localDataDir = normalizePath(pathJoin(VirtFs::getUserDir(), "Library/Application Support", branding.getValue("appName", "ManaPlus"))); #elif defined __HAIKU__ settings.localDataDir = normalizePath(pathJoin(VirtFs::getUserDir(), "config/cache/Mana")); #elif defined _WIN32 settings.localDataDir = normalizePath(getSpecialFolderLocation(CSIDL_LOCAL_APPDATA)); if (settings.localDataDir.empty()) settings.localDataDir = normalizePath(VirtFs::getUserDir()); settings.localDataDir = normalizePath(pathJoin(settings.localDataDir, "Mana")); #elif defined __ANDROID__ settings.localDataDir = normalizePath(pathJoin(getSdStoragePath(), branding.getValue("appShort", "ManaPlus"), "local")); #elif defined __native_client__ settings.localDataDir = normalizePath(pathJoin(_nacl_dir, "local")); #elif defined __SWITCH__ settings.localDataDir = normalizePath(pathJoin(VirtFs::getUserDir(), "local")); #else settings.localDataDir = normalizePath(pathJoin(VirtFs::getUserDir(), ".local/share/mana")); #endif } if (mkdir_r(settings.localDataDir.c_str()) != 0) { // TRANSLATORS: directory creation error logger->error(strprintf(_("%s doesn't exist and can't be created! " "Exiting."), settings.localDataDir.c_str())); } #ifdef USE_PROFILER Performance::init(normalizePath(pathJoin(settings.localDataDir, "profiler.log"))); #endif } void Dirs::initTempDir() { settings.tempDir = normalizePath(pathJoin(settings.localDataDir, "temp")); if (mkdir_r(settings.tempDir.c_str()) != 0) { // TRANSLATORS: directory creation error logger->error(strprintf(_("%s doesn't exist and can't be created! " "Exiting."), settings.tempDir.c_str())); } } void Dirs::initConfigDir() { settings.configDir = normalizePath(settings.options.configDir); if (settings.configDir.empty()) { #ifdef __APPLE__ settings.configDir = normalizePath(pathJoin(settings.localDataDir, branding.getValue("appShort", "mana"))); #elif defined __HAIKU__ settings.configDir = normalizePath(pathJoin(VirtFs::getUserDir(), "config/settings/Mana", branding.getValue("appName", "ManaPlus"))); #elif defined _WIN32 settings.configDir = normalizePath(getSpecialFolderLocation(CSIDL_APPDATA)); if (settings.configDir.empty()) { settings.configDir = normalizePath(settings.localDataDir); } else { settings.configDir = normalizePath(pathJoin(settings.configDir, "mana", branding.getValue("appShort", "mana"))); } #elif defined __ANDROID__ settings.configDir = normalizePath(pathJoin(getSdStoragePath(), branding.getValue("appShort", "ManaPlus"), "config")); #elif defined __native_client__ settings.configDir = normalizePath(pathJoin(_nacl_dir, "config")); #elif defined __SWITCH__ settings.configDir = normalizePath(pathJoin(VirtFs::getUserDir(), "config")); #else settings.configDir = normalizePath(pathJoin(VirtFs::getUserDir(), ".config/mana", branding.getValue("appShort", "mana"))); #endif logger->log("Generating config dir: " + settings.configDir); } if (mkdir_r(settings.configDir.c_str()) != 0) { // TRANSLATORS: directory creation error logger->error(strprintf(_("%s doesn't exist and can't be created! " "Exiting."), settings.configDir.c_str())); } } void Dirs::initUpdatesDir() { std::stringstream updates; if (settings.updateHost.empty()) settings.updateHost = config.getStringValue("updatehost"); if (!checkPath(settings.updateHost)) return; if (settings.updateHost.length() < 2) { if (settings.updatesDir.empty()) settings.updatesDir = normalizePath(pathJoin("updates", settings.serverName)); return; } const size_t sz = settings.updateHost.size(); if (settings.updateHost.at(sz - 1) == '/') settings.updateHost.resize(sz - 1); const size_t pos = settings.updateHost.find("://"); if (pos != std::string::npos) { if (pos + 3 < settings.updateHost.length() && !settings.updateHost.empty()) { updates << "updates/" << settings.updateHost.substr(pos + 3); settings.updatesDir = normalizePath(updates.str()); } else { // TRANSLATORS: update server initialisation error logger->log("Error: Invalid update host: %s", settings.updateHost.c_str()); errorMessage = strprintf(_("Invalid update host: %s."), settings.updateHost.c_str()); client->setState(State::ERROR); } } else { logger->log1("Warning: no protocol was specified for the update host"); updates << "updates/" << settings.updateHost; settings.updatesDir = normalizePath(updates.str()); } #ifdef _WIN32 if (settings.updatesDir.find(":") != std::string::npos) replaceAll(settings.updatesDir, ":", "_"); #endif const std::string updateDir = normalizePath("/" + settings.updatesDir); if (!VirtFs::isDirectory(updateDir)) { if (!VirtFs::mkdir(updateDir)) { #ifdef _WIN32 std::string newDir = toWindowsPath(normalizePath(pathJoin(settings.localDataDir, settings.updatesDir))); if (!CreateDirectory(newDir.c_str(), nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) { // TRANSLATORS: update server initialisation error logger->log("Error: %s can't be made, but doesn't exist!", newDir.c_str()); errorMessage = _("Error creating updates directory!"); client->setState(State::ERROR); } #else // TRANSLATORS: update server initialisation error logger->log("Error: %s/%s can't be made, but doesn't exist!", settings.localDataDir.c_str(), settings.updatesDir.c_str()); errorMessage = _("Error creating updates directory!"); client->setState(State::ERROR); #endif } } const std::string updateLocal = normalizePath(pathJoin(updateDir, "local")); const std::string updateFix = normalizePath(pathJoin(updateDir, "fix")); if (!VirtFs::isDirectory(updateLocal)) VirtFs::mkdir(updateLocal); if (!VirtFs::isDirectory(updateFix)) VirtFs::mkdir(updateFix); } void Dirs::initScreenshotDir() { if (!settings.options.screenshotDir.empty()) { settings.screenshotDir = normalizePath(settings.options.screenshotDir); if (mkdir_r(settings.screenshotDir.c_str()) != 0) { // TRANSLATORS: directory creation error logger->log(strprintf( _("Error: %s doesn't exist and can't be created! " "Exiting."), settings.screenshotDir.c_str())); } } else if (settings.screenshotDir.empty()) { #ifdef __native_client__ settings.screenshotDir = normalizePath(pathJoin(_nacl_dir, "screenshots/")); #else settings.screenshotDir = normalizePath(decodeBase64String( config.getStringValue("screenshotDirectory3"))); if (settings.screenshotDir.empty()) { #ifdef __ANDROID__ settings.screenshotDir = normalizePath(getSdStoragePath() + std::string("/images")); if (mkdir_r(settings.screenshotDir.c_str())) { // TRANSLATORS: directory creation error logger->log(strprintf( _("Error: %s doesn't exist and can't be created! " "Exiting."), settings.screenshotDir.c_str())); } #else settings.screenshotDir = normalizePath(getPicturesDir()); #endif if (config.getBoolValue("useScreenshotDirectorySuffix")) { const std::string configScreenshotSuffix = branding.getValue("screenshots", "ManaPlus"); if (!configScreenshotSuffix.empty()) { settings.screenshotDir = normalizePath(pathJoin(settings.screenshotDir, configScreenshotSuffix)); } } config.setValue("screenshotDirectory3", encodeBase64String(settings.screenshotDir)); } #endif } // TRANSLATORS: directory creation error logger->log("screenshotDirectory: " + settings.screenshotDir); } void Dirs::initUsersDir() { settings.usersDir = normalizePath(settings.serverConfigDir + "/users/"); if (mkdir_r(settings.usersDir.c_str()) != 0) { // TRANSLATORS: directory creation error logger->error(strprintf(_("%s doesn't exist and can't be created!"), settings.usersDir.c_str())); } settings.npcsDir = normalizePath(settings.serverConfigDir + "/npcs/"); if (mkdir_r(settings.npcsDir.c_str()) != 0) { // TRANSLATORS: directory creation error logger->error(strprintf(_("%s doesn't exist and can't be created!"), settings.npcsDir.c_str())); } settings.usersIdDir = normalizePath(settings.serverConfigDir + "/usersid/"); if (mkdir_r(settings.usersIdDir.c_str()) != 0) { logger->error(strprintf(_("%s doesn't exist and can't be created!"), settings.usersIdDir.c_str())); } }