/* Minetest Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* Random portability stuff See comments in porting.h */ #include "porting.h" #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) #include #include extern char **environ; #elif defined(_WIN32) #include #include #include #include #include #include #endif #if !defined(_WIN32) #include #include #if !defined(__ANDROID__) #include #endif #endif #if defined(__hpux) #define _PSTAT64 #include #endif #if defined(__ANDROID__) #include "porting_android.h" #endif #if defined(__APPLE__) // For _NSGetEnviron() // Related: https://gitlab.haskell.org/ghc/ghc/issues/2458 #include #endif #if defined(__HAIKU__) #include #endif #include "config.h" #include "debug.h" #include "filesys.h" #include "log.h" #include "util/string.h" #include #include #include #if !defined(SERVER) && defined(_WIN32) // On Windows export some driver-specific variables to encourage Minetest to be // executed on the discrete GPU in case of systems with two. Portability is fun. extern "C" { __declspec(dllexport) DWORD NvOptimusEnablement = 1; __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; } #endif namespace porting { /* Signal handler (grabs Ctrl-C on POSIX systems) */ bool g_killed = false; bool *signal_handler_killstatus() { return &g_killed; } #if !defined(_WIN32) // POSIX #include void signal_handler(int sig) { if (!g_killed) { if (sig == SIGINT) { dstream << "INFO: signal_handler(): " << "Ctrl-C pressed, shutting down." << std::endl; } else if (sig == SIGTERM) { dstream << "INFO: signal_handler(): " << "got SIGTERM, shutting down." << std::endl; } // Comment out for less clutter when testing scripts /*dstream << "INFO: sigint_handler(): " << "Printing debug stacks" << std::endl; debug_stacks_print();*/ g_killed = true; } else { (void)signal(sig, SIG_DFL); } } void signal_handler_init(void) { (void)signal(SIGINT, signal_handler); (void)signal(SIGTERM, signal_handler); } #else // _WIN32 #include BOOL WINAPI event_handler(DWORD sig) { switch (sig) { case CTRL_C_EVENT: case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: if (!g_killed) { dstream << "INFO: event_handler(): " << "Ctrl+C, Close Event, Logoff Event or Shutdown Event," " shutting down." << std::endl; g_killed = true; } else { (void)signal(SIGINT, SIG_DFL); } break; case CTRL_BREAK_EVENT: break; } return TRUE; } void signal_handler_init(void) { SetConsoleCtrlHandler((PHANDLER_ROUTINE)event_handler, TRUE); } #endif /* Path mangler */ // Default to RUN_IN_PLACE style relative paths std::string path_share = ".."; std::string path_user = ".."; std::string path_locale = path_share + DIR_DELIM + "locale"; std::string path_cache = path_user + DIR_DELIM + "cache"; std::string getDataPath(const char *subpath) { return path_share + DIR_DELIM + subpath; } void pathRemoveFile(char *path, char delim) { // Remove filename and path delimiter int i; for(i = strlen(path)-1; i>=0; i--) { if(path[i] == delim) break; } path[i] = 0; } bool detectMSVCBuildDir(const std::string &path) { const char *ends[] = { "bin\\Release", "bin\\MinSizeRel", "bin\\RelWithDebInfo", "bin\\Debug", "bin\\Build", NULL }; return (!removeStringEnd(path, ends).empty()); } std::string get_sysinfo() { #ifdef _WIN32 std::ostringstream oss; LPSTR filePath = new char[MAX_PATH]; UINT blockSize; VS_FIXEDFILEINFO *fixedFileInfo; GetSystemDirectoryA(filePath, MAX_PATH); PathAppendA(filePath, "kernel32.dll"); DWORD dwVersionSize = GetFileVersionInfoSizeA(filePath, NULL); LPBYTE lpVersionInfo = new BYTE[dwVersionSize]; GetFileVersionInfoA(filePath, 0, dwVersionSize, lpVersionInfo); VerQueryValueA(lpVersionInfo, "\\", (LPVOID *)&fixedFileInfo, &blockSize); oss << "Windows/" << HIWORD(fixedFileInfo->dwProductVersionMS) << '.' // Major << LOWORD(fixedFileInfo->dwProductVersionMS) << '.' // Minor << HIWORD(fixedFileInfo->dwProductVersionLS) << ' '; // Build #ifdef _WIN64 oss << "x86_64"; #else BOOL is64 = FALSE; if (IsWow64Process(GetCurrentProcess(), &is64) && is64) oss << "x86_64"; // 32-bit app on 64-bit OS else oss << "x86"; #endif delete[] lpVersionInfo; delete[] filePath; return oss.str(); #else struct utsname osinfo; uname(&osinfo); return std::string(osinfo.sysname) + "/" + osinfo.release + " " + osinfo.machine; #endif } bool getCurrentWorkingDir(char *buf, size_t len) { #ifdef _WIN32 DWORD ret = GetCurrentDirectory(len, buf); return (ret != 0) && (ret <= len); #else return getcwd(buf, len); #endif } bool getExecPathFromProcfs(char *buf, size_t buflen) { #ifndef _WIN32 buflen--; ssize_t len; if ((len = readlink("/proc/self/exe", buf, buflen)) == -1 && (len = readlink("/proc/curproc/file", buf, buflen)) == -1 && (len = readlink("/proc/curproc/exe", buf, buflen)) == -1) return false; buf[len] = '\0'; return true; #else return false; #endif } //// Windows #if defined(_WIN32) bool getCurrentExecPath(char *buf, size_t len) { DWORD written = GetModuleFileNameA(NULL, buf, len); if (written == 0 || written == len) return false; return true; } //// Linux #elif defined(__linux__) bool getCurrentExecPath(char *buf, size_t len) { if (!getExecPathFromProcfs(buf, len)) return false; return true; } //// Mac OS X, Darwin #elif defined(__APPLE__) bool getCurrentExecPath(char *buf, size_t len) { uint32_t lenb = (uint32_t)len; if (_NSGetExecutablePath(buf, &lenb) == -1) return false; return true; } //// FreeBSD, NetBSD, DragonFlyBSD #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) bool getCurrentExecPath(char *buf, size_t len) { // Try getting path from procfs first, since valgrind // doesn't work with the latter if (getExecPathFromProcfs(buf, len)) return true; int mib[4]; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PATHNAME; mib[3] = -1; if (sysctl(mib, 4, buf, &len, NULL, 0) == -1) return false; return true; } #elif defined(__HAIKU__) bool getCurrentExecPath(char *buf, size_t len) { return find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, buf, len) == B_OK; } //// Solaris #elif defined(__sun) || defined(sun) bool getCurrentExecPath(char *buf, size_t len) { const char *exec = getexecname(); if (exec == NULL) return false; if (strlcpy(buf, exec, len) >= len) return false; return true; } // HP-UX #elif defined(__hpux) bool getCurrentExecPath(char *buf, size_t len) { struct pst_status psts; if (pstat_getproc(&psts, sizeof(psts), 0, getpid()) == -1) return false; if (pstat_getpathname(buf, len, &psts.pst_fid_text) == -1) return false; return true; } #else bool getCurrentExecPath(char *buf, size_t len) { return false; } #endif //// Non-Windows #if !defined(_WIN32) const char *getHomeOrFail() { const char *home = getenv("HOME"); // In rare cases the HOME environment variable may be unset FATAL_ERROR_IF(!home, "Required environment variable HOME is not set"); return home; } #endif //// Windows #if defined(_WIN32) bool setSystemPaths() { char buf[BUFSIZ]; // Find path of executable and set path_share relative to it FATAL_ERROR_IF(!getCurrentExecPath(buf, sizeof(buf)), "Failed to get current executable path"); pathRemoveFile(buf, '\\'); std::string exepath(buf); // Use ".\bin\.." path_share = exepath + "\\.."; if (detectMSVCBuildDir(exepath)) { // The msvc build dir schould normaly not be present if properly installed, // but its usefull for debugging. path_share += DIR_DELIM ".."; } // Use "C:\Users\\AppData\Roaming\" DWORD len = GetEnvironmentVariable("APPDATA", buf, sizeof(buf)); FATAL_ERROR_IF(len == 0 || len > sizeof(buf), "Failed to get APPDATA"); path_user = std::string(buf) + DIR_DELIM + PROJECT_NAME_C; return true; } //// Linux #elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) bool setSystemPaths() { char buf[BUFSIZ]; if (!getCurrentExecPath(buf, sizeof(buf))) { #ifdef __ANDROID__ errorstream << "Unable to read bindir "<< std::endl; #else FATAL_ERROR("Unable to read bindir"); #endif return false; } pathRemoveFile(buf, '/'); std::string bindir(buf); // Find share directory from these. // It is identified by containing the subdirectory "builtin". std::list trylist; std::string static_sharedir = STATIC_SHAREDIR; if (!static_sharedir.empty() && static_sharedir != ".") trylist.push_back(static_sharedir); trylist.push_back(bindir + DIR_DELIM ".." DIR_DELIM "share" DIR_DELIM + PROJECT_NAME); trylist.push_back(bindir + DIR_DELIM ".."); #ifdef __ANDROID__ trylist.push_back(path_user); #endif for (std::list::const_iterator i = trylist.begin(); i != trylist.end(); ++i) { const std::string &trypath = *i; if (!fs::PathExists(trypath) || !fs::PathExists(trypath + DIR_DELIM + "builtin")) { warningstream << "system-wide share not found at \"" << trypath << "\""<< std::endl; continue; } // Warn if was not the first alternative if (i != trylist.begin()) { warningstream << "system-wide share found at \"" << trypath << "\"" << std::endl; } path_share = trypath; break; } #ifndef __ANDROID__ path_user = std::string(getHomeOrFail()) + DIR_DELIM "." + PROJECT_NAME; #endif return true; } //// Mac OS X #elif defined(__APPLE__) bool setSystemPaths() { CFBundleRef main_bundle = CFBundleGetMainBundle(); CFURLRef resources_url = CFBundleCopyResourcesDirectoryURL(main_bundle); char path[PATH_MAX]; if (CFURLGetFileSystemRepresentation(resources_url, TRUE, (UInt8 *)path, PATH_MAX)) { path_share = std::string(path); } else { warningstream << "Could not determine bundle resource path" << std::endl; } CFRelease(resources_url); path_user = std::string(getHomeOrFail()) + "/Library/Application Support/" + PROJECT_NAME; return true; } #else bool setSystemPaths() { path_share = STATIC_SHAREDIR; path_user = std::string(getHomeOrFail()) + DIR_DELIM "." + lowercase(PROJECT_NAME); return true; } #endif void migrateCachePath() { const std::string local_cache_path = path_user + DIR_DELIM + "cache"; // Delete tmp folder if it exists (it only ever contained // a temporary ogg file, which is no longer used). if (fs::PathExists(local_cache_path + DIR_DELIM + "tmp")) fs::RecursiveDelete(local_cache_path + DIR_DELIM + "tmp"); // Bail if migration impossible if (path_cache == local_cache_path || !fs::PathExists(local_cache_path) || fs::PathExists(path_cache)) { return; } if (!fs::Rename(local_cache_path, path_cache)) { errorstream << "Failed to migrate local cache path " "to system path!" << std::endl; } } void initializePaths() { #if RUN_IN_PLACE char buf[BUFSIZ]; infostream << "Using relative paths (RUN_IN_PLACE)" << std::endl; bool success = getCurrentExecPath(buf, sizeof(buf)) || getExecPathFromProcfs(buf, sizeof(buf)); if (success) { pathRemoveFile(buf, DIR_DELIM_CHAR); std::string execpath(buf); path_share = execpath + DIR_DELIM ".."; path_user = execpath + DIR_DELIM ".."; if (detectMSVCBuildDir(execpath)) { path_share += DIR_DELIM ".."; path_user += DIR_DELIM ".."; } } else { errorstream << "Failed to get paths by executable location, " "trying cwd" << std::endl; if (!getCurrentWorkingDir(buf, sizeof(buf))) FATAL_ERROR("Ran out of methods to get paths"); size_t cwdlen = strlen(buf); if (cwdlen >= 1 && buf[cwdlen - 1] == DIR_DELIM_CHAR) { cwdlen--; buf[cwdlen] = '\0'; } if (cwdlen >= 4 && !strcmp(buf + cwdlen - 4, DIR_DELIM "bin")) pathRemoveFile(buf, DIR_DELIM_CHAR); std::string execpath(buf); path_share = execpath; path_user = execpath; } path_cache = path_user + DIR_DELIM + "cache"; #else infostream << "Using system-wide paths (NOT RUN_IN_PLACE)" << std::endl; if (!setSystemPaths()) errorstream << "Failed to get one or more system-wide path" << std::endl; # ifdef _WIN32 path_cache = path_user + DIR_DELIM + "cache"; # else // Initialize path_cache // First try $XDG_CACHE_HOME/PROJECT_NAME const char *cache_dir = getenv("XDG_CACHE_HOME"); const char *home_dir = getenv("HOME"); if (cache_dir && cache_dir[0] != '\0') { path_cache = std::string(cache_dir) + DIR_DELIM + PROJECT_NAME; } else if (home_dir) { // Then try $HOME/.cache/PROJECT_NAME path_cache = std::string(home_dir) + DIR_DELIM + ".cache" + DIR_DELIM + PROJECT_NAME; } else { // If neither works, use $PATH_USER/cache path_cache = path_user + DIR_DELIM + "cache"; } // Migrate cache folder to new location if possible migrateCachePath(); # endif // _WIN32 #endif // RUN_IN_PLACE infostream << "Detected share path: " << path_share << std::endl; infostream << "Detected user path: " << path_user << std::endl; infostream << "Detected cache path: " << path_cache << std::endl; #if USE_GETTEXT bool found_localedir = false; # ifdef STATIC_LOCALEDIR /* STATIC_LOCALEDIR may be a generalized path such as /usr/share/locale that * doesn't necessarily contain our locale files, so check data path first. */ path_locale = getDataPath("locale"); if (fs::PathExists(path_locale)) { found_localedir = true; infostream << "Using in-place locale d------------------------------------------------------------------ The ancient comment from the beginning of main.cpp is stored here. ------------------------------------------------------------------ /* =============================== NOTES ============================== NOTE: Things starting with TODO are sometimes only suggestions. NOTE: iostream.imbue(std::locale("C")) is very slow NOTE: Global locale is now set at initialization NOTE: If VBO (EHM_STATIC) is used, remember to explicitly free the hardware buffer (it is not freed automatically) NOTE: A random to-do list saved here as documentation: A list of "active blocks" in which stuff happens. (+=done) + Add a never-resetted game timer to the server + Add a timestamp value to blocks + The simple rule: All blocks near some player are "active" - Do stuff in real time in active blocks + Handle objects - Grow grass, delete leaves without a tree - Spawn some mobs based on some rules - Transform cobble to mossy cobble near water - Run a custom script - ...And all kinds of other dynamic stuff + Keep track of when a block becomes active and becomes inactive + When a block goes inactive: + Store objects statically to block + Store timer value as the timestamp + When a block goes active: + Create active objects out of static objects - Simulate the results of what would have happened if it would have been active for all the time - Grow a lot of grass and so on + Initially it is fine to send information about every active object to every player. Eventually it should be modified to only send info about the nearest ones. + This was left to be done by the old system and it sends only the nearest ones. NOTE: Seeds in 1260:6c77e7dbfd29: 5721858502589302589: Spawns you on a small sand island with a surface dungeon 2983455799928051958: Enormous jungle + a surface dungeon at ~(250,0,0) Old, wild and random suggestions that probably won't be done: ------------------------------------------------------------- SUGG: If player is on ground, mainly fetch ground-level blocks SUGG: Expose Connection's seqnums and ACKs to server and client. - This enables saving many packets and making a faster connection - This also enables server to check if client has received the most recent block sent, for example. SUGG: Add a sane bandwidth throttling system to Connection SUGG: More fine-grained control of client's dumping of blocks from memory - ...What does this mean in the first place? SUGG: A map editing mode (similar to dedicated server mode) SUGG: Transfer more blocks in a single packet SUGG: A blockdata combiner class, to which blocks are added and at destruction it sends all the stuff in as few packets as possible. SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize it by sending more stuff in a single packet. - Add a packet queue to RemoteClient, from which packets will be combined with object data packets - This is not exactly trivial: the object data packets are sometimes very big by themselves - This might not give much network performance gain though. SUGG: Precalculate lighting translation table at runtime (at startup) - This is not doable because it is currently hand-made and not based on some mathematical function. - Note: This has been changing lately SUGG: A version number to blocks, which increments when the block is modified (node add/remove, water update, lighting update) - This can then be used to make sure the most recent version of a block has been sent to client, for example SUGG: Make the amount of blocks sending to client and the total amount of blocks dynamically limited. Transferring blocks is the main network eater of this system, so it is the one that has to be throttled so that RTTs stay low. SUGG: Meshes of blocks could be split into 6 meshes facing into different directions and then only those drawn that need to be SUGG: Background music based on cellular automata? http://www.earslap.com/projectslab/otomata