/*
* Copyright 2010-2016 OpenXcom Developers.
*
* This file is part of OpenXcom.
*
* OpenXcom 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 3 of the License, or
* (at your option) any later version.
*
* OpenXcom 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 OpenXcom. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "CrossPlatform.h"
#include <exception>
#include <algorithm>
#include <sstream>
#include <string>
#include <stdint.h>
#include <time.h>
#include <signal.h>
#include <sys/stat.h>
#include "../dirent.h"
#include "Logger.h"
#include "Exception.h"
#include "Options.h"
#include "Unicode.h"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <shellapi.h>
#ifndef __NO_DBGHELP
#include <dbghelp.h>
#endif
#ifdef __MINGW32__
#include <cxxabi.h>
#endif
#define EXCEPTION_CODE_CXX 0xe06d7363
#ifndef __GNUC__
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "shlwapi.lib")
#ifndef __NO_DBGHELP
#pragma comment(lib, "dbghelp.lib")
#endif
#endif
#else
#include <iostream>
#include <fstream>
#include <locale>
#include <SDL_image.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <pwd.h>
#include <execinfo.h>
#include <cxxabi.h>
#include <dlfcn.h>
#include "Unicode.h"
#endif
#include <SDL.h>
#include <SDL_syswm.h>
#ifdef __HAIKU__
#include <FindDirectory.h>
#include <StorageDefs.h>
#endif
namespace OpenXcom
{
namespace CrossPlatform
{
std::string errorDlg;
/**
* Determines the available Linux error dialogs.
*/
void getErrorDialog()
{
#ifndef _WIN32
if (system(NULL))
{
if (getenv("KDE_SESSION_UID") && system("which kdialog 2>&1 > /dev/null") == 0)
errorDlg = "kdialog --error ";
else if (system("which zenity 2>&1 > /dev/null") == 0)
errorDlg = "zenity --no-wrap --error --text=";
else if (system("which kdialog 2>&1 > /dev/null") == 0)
errorDlg = "kdialog --error ";
else if (system("which gdialog 2>&1 > /dev/null") == 0)
errorDlg = "gdialog --msgbox ";
else if (system("which xdialog 2>&1 > /dev/null") == 0)
errorDlg = "xdialog --msgbox ";
}
#endif
}
/**
* Displays a message box with an error message.
* @param error Error message.
*/
void showError(const std::string &error)
{
#ifdef _WIN32
MessageBoxA(NULL, error.c_str(), "OpenXcom Error", MB_ICONERROR | MB_OK);
#else
if (errorDlg.empty())
{
std::cerr << error << std::endl;
}
else
{
std::string nError = '"' + error + '"';
Unicode::replace(nError, "\n", "\\n");
std::string cmd = errorDlg + nError;
if (system(cmd.c_str()) != 0)
std::cerr << error << std::endl;
}
#endif
Log(LOG_FATAL) << error;
}
#ifndef _WIN32
/**
* Gets the user's home folder according to the system.
* @return Absolute path to home folder.
*/
static char const *getHome()
{
char const *home = getenv("HOME");
if (!home)
{
struct passwd *const pwd = getpwuid(getuid());
home = pwd->pw_dir;
}
return home;
}
#endif
/**
* Builds a list of predefined paths for the Data folder
* according to the running system.
* @return List of data paths.
*/
std::vector<std::string> findDataFolders()
{
std::vector<std::string> list;
#ifdef __MORPHOS__
list.push_back("PROGDIR:");
return list;
#endif
#ifdef _WIN32
char path[MAX_PATH];
// Get Documents folder
if (SHGetSpecialFolderPathA(NULL, path, CSIDL_PERSONAL, FALSE))
{
PathAppendA(path, "OpenXcom\\");
list.push_back(path);
}
// Get binary directory
if (GetModuleFileNameA(NULL, path, MAX_PATH) != 0)
{
PathRemoveFileSpecA(path);
list.push_back(path);
}
// Get working directory
if (GetCurrentDirectoryA(MAX_PATH, path) != 0)
{
list.push_back(path);
}
#else
char const *home = getHome();
#ifdef __HAIKU__
char data_path[B_PATH_NAME_LENGTH];
find_directory(B_SYSTEM_SETTINGS_DIRECTORY, 0, true, data_path, sizeof(data_path)-strlen("/OpenXcom/"));
strcat(data_path,"/OpenXcom/");
list.push_back(data_path);
#endif
char path[MAXPATHLEN];
// Get user-specific data folders
if (char const *const xdg_data_home = getenv("XDG_DATA_HOME"))
{
snprintf(path, MAXPATHLEN, "%s/openxcom/", xdg_data_home);
}
else
{
#ifdef __APPLE__
snprintf(path, MAXPATHLEN, "%s/Library/Application Support/OpenXcom/", home);
#else
snprintf(path, MAXPATHLEN, "%s/.local/share/openxcom/", home);
#endif
}
list.push_back(path);
// Get global data folders
if (char const *const xdg_data_dirs = getenv("XDG_DATA_DIRS"))
{
char xdg_data_dirs_copy[strlen(xdg_data_dirs)+1];
strcpy(xdg_data_dirs_copy, xdg_data_dirs);
char *dir = strtok(xdg_data_dirs_copy, ":");
while (dir != 0)
{
snprintf(path, MAXPATHLEN, "%s/openxcom/", dir);
list.push_back(path);
dir = strtok(0, ":");
}
}
#ifdef __APPLE__
list.push_back("/Users/Shared/OpenXcom/");
#else
list.push_back("/usr/local/share/openxcom/");
list.push_back("/usr/share/openxcom/");
#ifdef DATADIR
snprintf(path, MAXPATHLEN, "%s/", DATADIR);
list.push_back(path);
#endif
#endif
// Get working directory
list.push_back("./");
#endif
return list;
}
/**
* Builds a list of predefined paths for the User folder
* according to the running system.
* @return List of data paths.
*/
std::vector<std::string> findUserFolders()
{
std::vector<std::string> list;
#ifdef __MORPHOS__
list.push_back("PROGDIR:");
return list;
#endif
#ifdef _WIN32
char path[MAX_PATH];
// Get Documents folder
if (SHGetSpecialFolderPathA(NULL, path, CSIDL_PERSONAL, FALSE))
{
PathAppendA(path, "OpenXcom\\");
list.push_back(path);
}
// Get binary directory
if (GetModuleFileNameA(NULL, path, MAX_PATH) != 0)
{
PathRemoveFileSpecA(path);
PathAppendA(path, "user\\");
list.push_back(path);
}
// Get working directory
if (GetCurrentDirectoryA(MAX_PATH, path) != 0)
{
PathAppendA(path, "user\\");
list.push_back(path);
}
#else
#ifdef __HAIKU__
char user_path[B_PATH_NAME_LENGTH];
find_directory(B_USER_SETTINGS_DIRECTORY, 0, true, user_path, sizeof(user_path)-strlen("/OpenXcom/"));
strcat(user_path,"/OpenXcom/");
list.push_back(user_path);
#endif
char const *home = getHome();
char path[MAXPATHLEN];
// Get user folders
if (char const *const xdg_data_home = getenv("XDG_DATA_HOME"))
{
snprintf(path, MAXPATHLEN, "%s/openxcom/", xdg_data_home);
}
else
{
#ifdef __APPLE__
snprintf(path, MAXPATHLEN, "%s/Library/Application Support/OpenXcom/", home);
#else
snprintf(path, MAXPATHLEN, "%s/.local/share/openxcom/", home);
#endif
}
list.push_back(path);
// Get old-style folder
snprintf(path, MAXPATHLEN, "%s/.openxcom/", home);
list.push_back(path);
// Get working directory
list.push_back("./user/");
#endif
return list;
}
/**
* Finds the Config folder according to the running system.
* @return Config path.
*/
std::string findConfigFolder()
{
#ifdef __MORPHOS__
return "PROGDIR:";
#endif
#if defined(_WIN32) || defined(__APPLE__)
return "";
#elif defined (__HAIKU__)
char settings_path[B_PATH_NAME_LENGTH];
find_directory(B_USER_SETTINGS_DIRECTORY, 0, true, settings_path, sizeof(settings_path)-strlen("/OpenXcom/"));
strcat(settings_path,"/OpenXcom/");
return settings_path;
#else
char const *home = getHome();
char path[MAXPATHLEN];
// Get config folders
if (char const *const xdg_config_home = getenv("XDG_CONFIG_HOME"))
{
snprintf(path, MAXPATHLEN, "%s/openxcom/", xdg_config_home);
return path;
}
else
{
snprintf(path, MAXPATHLEN, "%s/.config/openxcom/", home);
return path;
}
#endif
}
std::string searchDataFile(const std::string &filename)
{
// Correct folder separator
std::string name = filename;
#ifdef _WIN32
std::replace(name.begin(), name.end(), '/', PATH_SEPARATOR);
#endif
// Check current data path
std::string path = Options::getDataFolder() + name;
if (fileExists(path))
{
return path;
}
// Check every other path
for (std::vector<std::string>::const_iterator i = Options::getDataList().begin(); i != Options::getDataList().end(); ++i)
{
path = *i + name;
if (fileExists(path))
{
Options::setDataFolder(*i);
return path;
}
}
// Give up
return filename;
}
std::string searchDataFolder(const std::string &foldername)
{
// Correct folder separator
std::string name = foldername;
#ifdef _WIN32
std::replace(name.begin(), name.end(), '/', PATH_SEPARATOR);
#endif
// Check current data path
std::string path = Options::getDataFolder() + name;
if (folderExists(path))
{
return path;
}
// Check every other path
for (std::vector<std::string>::const_iterator i = Options::getDataList().begin(); i != Options::getDataList().end(); ++i)
{
path = *i + name;
if (folderExists(path))
{
Options::setDataFolder(*i);
return path;
}
}
// Give up
return foldername;
}
/**
* Creates a folder at the specified path.
* @note Only creates the last folder on the path.
* @param path Full path.
* @return Folder created or not.
*/
bool createFolder(const std::string &path)
{
#ifdef _WIN32
int result = CreateDirectoryA(path.c_str(), 0);
if (result == 0)
return false;
else
return true;
#else
mode_t process_mask = umask(0);
int result = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
umask(process_mask);
if (result == 0)
return true;
else
return false;
#endif
}
/**
* Adds an ending slash to a path if necessary.
* @param path Folder path.
* @return Terminated path.
*/
std::string endPath(const std::string &path)
{
if (!path.empty() && path.at(path.size()-1) != PATH_SEPARATOR)
return path + PATH_SEPARATOR;
return path;
}
/**
* Gets the name of all the files
* contained in a certain folder.
* @param path Full path to folder.
* @param ext Extension of files ("" if it doesn't matter).
* @return Ordered list of all the files.
*/
std::vector<std::string> getFolderContents(const std::string &path, const std::string &ext)
{
std::vector<std::string> files;
DIR *dp = opendir(path.c_str());
if (dp == 0)
{
#ifdef __MORPHOS__
return files;
#else
std::string errorMessage("Failed to open directory: " + path);
throw Exception(errorMessage);
#endif
}
struct dirent *dirp;
while ((dirp = readdir(dp)) != 0)
{
std::string file = dirp->d_name;
if (!file.empty() && file[0] == '.')
{
//skip ".", "..", ".git", ".svn", ".bashrc", ".ssh" etc.
continue;
}
if (!compareExt(file, ext))
{
continue;
}
files.push_back(file);
}
closedir(dp);
std::sort(files.begin(), files.end());
return files;
}
/**
* Checks if a certain path exists and is a folder.
* @param path Full path to folder.
* @return Does it exist?
*/
bool folderExists(const std::string &path)
{
#ifdef _WIN32
return (PathIsDirectoryA(path.c_str()) != FALSE);
#elif __MORPHOS__
BPTR l = Lock( path.c_str(), SHARED_LOCK );
if ( l != NULL )
{
UnLock( l );
return 1;
}
return 0;
#else
struct stat info;
return (stat(path.c_str(), &info) == 0 && S_ISDIR(info.st_mode));
#endif
}
/**
* Checks if a certain path exists and is a file.
* @param path Full path to file.
* @return Does it exist?
*/
bool fileExists(const std::string &path)
{
#ifdef _WIN32
return (PathFileExistsA(path.c_str()) != FALSE);
#elif __MORPHOS__
BPTR l = Lock( path.c_str(), SHARED_LOCK );
if ( l != NULL )
{
UnLock( l );
return 1;
}
return 0;
#else
struct stat info;
return (stat(path.c_str(), &info) == 0 && S_ISREG(info.st_mode));
#endif
}
/**
* Removes a file from the specified path.
* @param path Full path to file.
* @return True if the operation succeeded, False otherwise.
*/
bool deleteFile(const std::string &path)
{
#ifdef _WIN32
return (DeleteFileA(path.c_str()) != 0);
#else
return (remove(path.c_str()) == 0);
#endif
}
/**
* Returns only the filename from a specified path.
* @param path Full path.
* @return Filename component.
*/
std::string baseFilename(const std::string &path)
{
size_t sep = path.find_last_of("/\\");
std::string filename;
if (sep == std::string::npos)
{
filename = path;
}
else if (sep == path.size() - 1)
{
return baseFilename(path.substr(0, path.size() - 1));
}
else
{
filename = path.substr(sep + 1);
}
return filename;
}
/**
* Replaces invalid filesystem characters with _.
* @param filename Original filename.
* @return Filename without invalid characters.
*/
std::string sanitizeFilename(const std::string &filename)
{
std::string newFilename = filename;
for (std::string::iterator i = newFilename.begin(); i != newFilename.end(); ++i)
{
if ((*i) == '<' ||
(*i) == '>' ||
(*i) == ':' ||
(*i) == '"' ||
(*i) == '/' ||
(*i) == '?' ||
(*i) == '\\')
{
*i = '_';
}
}
return newFilename;
}
/**
* Removes the extension from a filename. Only the
* last dot is considered.
* @param filename Original filename.
* @return Filename without the extension.
*/
std::string noExt(const std::string &filename)
{
size_t dot = filename.find_last_of('.');
if (dot == std::string::npos)
{
return filename;
}
return filename.substr(0, dot);
}
/**
* Returns the extension from a filename. Only the
* last dot is considered.
* @param filename Original filename.
* @return Extension component, includes dot.
*/
std::string getExt(const std::string &filename)
{
size_t dot = filename.find_last_of('.');
if (dot == std::string::npos)
{
return "";
}
return filename.substr(dot);
}
/**
* Compares the extension in a filename (case-insensitive).
* @param filename Filename to compare.
* @param extension Extension to compare to.
* @return If the extensions match.
*/
bool compareExt(const std::string &filename, const std::string &extension)
{
if (extension.empty())
return true;
int j = filename.length() - extension.length();
if (j <= 0)
return false;
if (filename[j - 1] != '.')
return false;
for (size_t i = 0; i < extension.length(); ++i)
{
if (::tolower(filename[j + i]) != ::tolower(extension[i]))
return false;
}
return true;
}
/**
* Gets the current locale of the system in language-COUNTRY format.
* @return Locale string.
*/
std::string getLocale()
{
#ifdef _WIN32
char language[9], country[9];
GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, language, 9);
GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, country, 9);
std::ostringstream locale;
locale << language << "-" << country;
return locale.str();
#else
std::locale l;
try
{
l = std::locale("");
}
catch (const std::runtime_error &)
{
return "x-";
}
std::string name = l.name();
size_t dash = name.find_first_of('_'), dot = name.find_first_of('.');
if (dot != std::string::npos)
{
name = name.substr(0, dot - 1);
}
if (dash != std::string::npos)
{
std::string language = name.substr(0, dash - 1);
std::string country = name.substr(dash - 1);
std::ostringstream locale;
locale << language << "-" << country;
return locale.str();
}
else
{
return name + "-";
}
#endif
}
/**
* Checks if the system's default quit shortcut was pressed.
* @param ev SDL event.
* @return Is quitting necessary?
*/
bool isQuitShortcut(const SDL_Event &ev)
{
#ifdef _WIN32
// Alt + F4
return (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_F4 && ev.key.keysym.mod & KMOD_ALT);
#elif __APPLE__
// Command + Q
return (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_q && ev.key.keysym.mod & KMOD_LMETA);
#else
//TODO add other OSs shortcuts.
(void)ev;
return false;
#endif
}
/**
* Gets the last modified date of a file.
* @param path Full path to file.
* @return The timestamp in integral format.
*/
time_t getDateModified(const std::string &path)
{
/*#ifdef _WIN32
WIN32_FILE_ATTRIBUTE_DATA info;
if (GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &info))
{
FILETIME ft = info.ftLastWriteTime;
LARGE_INTEGER li;
li.HighPart = ft.dwHighDateTime;
li.LowPart = ft.dwLowDateTime;
return li.QuadPart;
}
else
{
return 0;
}
#endif*/
struct stat info;
if (stat(path.c_str(), &info) == 0)
{
return info.st_mtime;
}
else
{
return 0;
}
}
/**
* Converts a date/time into a human-readable string
* using the ISO 8601 standard.
* @param time Value in timestamp format.
* @return String pair with date and time.
*/
std::pair<std::string, std::string> timeToString(time_t time)
{
char localDate[25], localTime[25];
/*#ifdef _WIN32
LARGE_INTEGER li;
li.QuadPart = time;
FILETIME ft;
ft.dwHighDateTime = li.HighPart;
ft.dwLowDateTime = li.LowPart;
SYSTEMTIME st;
FileTimeToLocalFileTime(&ft, &ft);
FileTimeToSystemTime(&ft, &st);
GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, localDate, 25);
GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, localTime, 25);
#endif*/
struct tm *timeinfo = localtime(&(time));
strftime(localDate, 25, "%Y-%m-%d", timeinfo);
strftime(localTime, 25, "%H:%M", timeinfo);
return std::make_pair(localDate, localTime);
}
/**
* Moves a file from one path to another,
* replacing any existing file.
* @param src Source path.
* @param dest Destination path.
* @return True if the operation succeeded, False otherwise.
*/
bool moveFile(const std::string &src, const std::string &dest)
{
#ifdef _WIN32
return (MoveFileExA(src.c_str(), dest.c_str(), MOVEFILE_REPLACE_EXISTING) != 0);
#else
//return (rename(src.c_str(), dest.c_str()) == 0);
std::ifstream srcStream;
std::ofstream destStream;
srcStream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
destStream.exceptions(std::ofstream::failbit | std::ofstream::badbit);
try
{
srcStream.open(src.c_str(), std::ios::binary);
destStream.open(dest.c_str(), std::ios::binary);
destStream << srcStream.rdbuf();
srcStream.close();
destStream.close();
}
catch (const std::fstream::failure &)
{
return false;
}
return deleteFile(src);
#endif
}
/**
* Notifies the user that maybe he should have a look.
*/
void flashWindow()
{
#ifdef _WIN32
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version)
if (SDL_GetWMInfo(&wminfo))
{
HWND hwnd = wminfo.window;
FlashWindow(hwnd, true);
}
#endif
}
/**
* Gets the executable path in DOS-style (short) form.
* For non-Windows systems, just use a dummy path.
* @return Executable path.
*/
std::string getDosPath()
{
#ifdef _WIN32
std::string path, bufstr;
char buf[MAX_PATH];
if (GetModuleFileNameA(0, buf, MAX_PATH) != 0)
{
bufstr = buf;
size_t c1 = bufstr.find_first_of('\\');
path += bufstr.substr(0, c1+1);
size_t c2 = bufstr.find_first_of('\\', c1+1);
while (c2 != std::string::npos)
{
std::string dirname = bufstr.substr(c1+1, c2-c1-1);
if (dirname == "..")
{
path = path.substr(0, path.find_last_of('\\', path.length()-2));
}
else
{
if (dirname.length() > 8)
dirname = dirname.substr(0, 6) + "~1";
std::transform(dirname.begin(), dirname.end(), dirname.begin(), toupper);
path += dirname;
}
c1 = c2;
c2 = bufstr.find_first_of('\\', c1+1);
if (c2 != std::string::npos)
path += '\\';
}
}
else
{
path = "C:\\GAMES\\OPENXCOM";
}
return path;
#else
return "C:\\GAMES\\OPENXCOM";
#endif
}
/**
* Sets the window titlebar icon.
* For Windows, use the embedded resource icon.
* For other systems, use a PNG icon.
* @param winResource ID for Windows icon.
* @param unixPath Path to PNG icon for Unix.
*/
#ifdef _WIN32
void setWindowIcon(int winResource, const std::string &)
{
HINSTANCE handle = GetModuleHandle(NULL);
HICON icon = LoadIcon(handle, MAKEINTRESOURCE(winResource));
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version)
if (SDL_GetWMInfo(&wminfo))
{
HWND hwnd = wminfo.window;
SetClassLongPtr(hwnd, GCLP_HICON, (LONG_PTR)icon);
}
}
#else
void setWindowIcon(int, const std::string &unixPath)
{
std::string utf8 = Unicode::convPathToUtf8(unixPath);
SDL_Surface *icon = IMG_Load(utf8.c_str());
if (icon != 0)
{
SDL_WM_SetIcon(icon, NULL);
SDL_FreeSurface(icon);
}
}
#endif
/**
* Logs the stack back trace leading up to this function call.
* @param ctx Pointer to stack context (PCONTEXT on Windows), NULL to use current context.
*/
void stackTrace(void *ctx)
{
#ifdef _WIN32
#ifndef __NO_DBGHELP
const int MAX_SYMBOL_LENGTH = 1024;
CONTEXT context;
if (ctx != 0)
{
context = *((PCONTEXT)ctx);
}
else
{
#ifdef _M_IX86
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_CONTROL;
#ifdef __MINGW32__
asm("Label:\n\t"
"movl %%ebp,%0;\n\t"
"movl %%esp,%1;\n\t"
"movl $Label,%%eax;\n\t"
"movl %%eax,%2;\n\t"
: "=r" (context.Ebp), "=r" (context.Esp), "=r" (context.Eip)
: //no input
: "eax");
#else
_asm {
Label:
mov[context.Ebp], ebp;
mov[context.Esp], esp;
mov eax, [Label];
mov[context.Eip], eax;
}
#endif
#else
RtlCaptureContext(&context);
#endif
}
HANDLE thread = GetCurrentThread();
HANDLE process = GetCurrentProcess();
STACKFRAME64 frame;
memset(&frame, 0, sizeof(STACKFRAME64));
DWORD image;
#ifdef _M_IX86
image = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = context.Eip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Esp;
frame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
image = IMAGE_FILE_MACHINE_AMD64;
frame.AddrPC.Offset = context.Rip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Rbp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Rsp;
frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
image = IMAGE_FILE_MACHINE_IA64;
frame.AddrPC.Offset = context.StIIP;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.IntSp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrBStore.Offset = context.RsBSP;
frame.AddrBStore.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.IntSp;
frame.AddrStack.Mode = AddrModeFlat;
#else
Log(LOG_FATAL) << "Unfortunately, no stack trace information is available";
return;
#endif
SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO) + (MAX_SYMBOL_LENGTH - 1) * sizeof(TCHAR));
symbol->MaxNameLen = MAX_SYMBOL_LENGTH;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
DWORD displacement;
SymInitialize(process, NULL, TRUE);
while (StackWalk64(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
{
if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
{
std::string symname = symbol->Name;
#ifdef __MINGW32__
symname = "_" + symname;
int status = 0;
size_t outSz = 0;
char* demangled = abi::__cxa_demangle(symname.c_str(), 0, &outSz, &status);
if (status == 0)
{
symname = demangled;
if (outSz > 0)
free(demangled);
}
else
{
symname = symbol->Name;
}
#endif
if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
{
std::string filename = line->FileName;
size_t n = filename.find_last_of(PATH_SEPARATOR);
if (n != std::string::npos)
{
filename = filename.substr(n + 1);
}
Log(LOG_FATAL) << "0x" << std::hex << symbol->Address << std::dec << " " << symname << " (" << filename << ":" << line->LineNumber << ")";
}
else
{
Log(LOG_FATAL) << "0x" << std::hex << symbol->Address << std::dec << " " << symname;
}
}
else
{
Log(LOG_FATAL) << "??";
}
}
DWORD err = GetLastError();
if (err)
{
Log(LOG_FATAL) << "Unfortunately, no stack trace information is available";
}
SymCleanup(process);
#else
Log(LOG_FATAL) << "Unfortunately, no stack trace information is available";
#endif
#elif __CYGWIN__
Log(LOG_FATAL) << "Unfortunately, no stack trace information is available";
#else
void *frames[32];
char buf[1024];
int frame_count = backtrace(frames, 32);
char *demangled = NULL;
const char *mangled = NULL;
int status;
size_t sym_offset;
for (int i = 0; i < frame_count; i++) {
Dl_info dl_info;
if (dladdr(frames[i], &dl_info )) {
demangled = NULL;
mangled = dl_info.dli_sname;
if ( mangled != NULL) {
sym_offset = (char *)frames[i] - (char *)dl_info.dli_saddr;
demangled = abi::__cxa_demangle( dl_info.dli_sname, NULL, 0, &status);
snprintf(buf, sizeof(buf), "%s(%s+0x%zx) [%p]",
dl_info.dli_fname,
status == 0 ? demangled : mangled,
sym_offset, frames[i] );
} else { // symbol not found
sym_offset = (char *)frames[i] - (char *)dl_info.dli_fbase;
snprintf(buf, sizeof(buf), "%s(+0x%zx) [%p]", dl_info.dli_fname, sym_offset, frames[i]);
}
free(demangled);
Log(LOG_FATAL) << buf;
} else { // object not found
snprintf(buf, sizeof(buf), "? ? [%p]", frames[i]);
Log(LOG_FATAL) << buf;
}
}
#endif
ctx = (void*)ctx;
}
/**
* Generates a timestamp of the current time.
* @return String in D-M-Y_H-M-S format.
*/
std::string now()
{
const int MAX_LEN = 25, MAX_RESULT = 80;
char result[MAX_RESULT] = { 0 };
#ifdef _WIN32
char date[MAX_LEN], time[MAX_LEN];
if (GetDateFormatA(LOCALE_INVARIANT, 0, 0, "dd'-'MM'-'yyyy", date, MAX_LEN) == 0)
return "00-00-0000";
if (GetTimeFormatA(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT, 0, "HH'-'mm'-'ss", time, MAX_LEN) == 0)
return "00-00-00";
sprintf(result, "%s_%s", date, time);
#else
char buffer[MAX_LEN];
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buffer, MAX_LEN, "%d-%m-%Y_%H-%M-%S", timeinfo);
sprintf(result, "%s", buffer);
#endif
return result;
}
/**
* Logs the details of this crash and shows an error.
* @param ex Pointer to exception data (PEXCEPTION_POINTERS on Windows, signal int on Unix)
* @param err Exception message, if any.
*/
void crashDump(void *ex, const std::string &err)
{
std::ostringstream error;
#ifdef _MSC_VER
PEXCEPTION_POINTERS exception = (PEXCEPTION_POINTERS)ex;
std::exception *cppException = 0;
switch (exception->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_CODE_CXX:
cppException = (std::exception *)exception->ExceptionRecord->ExceptionInformation[1];
error << cppException->what();
break;
case EXCEPTION_ACCESS_VIOLATION:
error << "Memory access violation. This usually indicates something missing in a mod.";
break;
default:
error << "code 0x" << std::hex << exception->ExceptionRecord->ExceptionCode;
break;
}
Log(LOG_FATAL) << "A fatal error has occurred: " << error.str();
stackTrace(exception->ContextRecord);
std::string dumpName = Options::getUserFolder();
dumpName += now() + ".dmp";
HANDLE dumpFile = CreateFileA(dumpName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
exceptionInformation.ThreadId = GetCurrentThreadId();
exceptionInformation.ExceptionPointers = exception;
exceptionInformation.ClientPointers = FALSE;
if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
{
Log(LOG_FATAL) << "Crash dump generated at " << dumpName;
}
else
{
Log(LOG_FATAL) << "No crash dump generated: " << GetLastError();
}
#else
if (ex == 0)
{
error << err;
}
else
{
int signal = *((int*)ex);
switch (signal)
{
case SIGSEGV:
error << "Segmentation fault. This usually indicates something missing in a mod.";
break;
default:
error << "signal " << signal;
break;
}
}
Log(LOG_FATAL) << "A fatal error has occurred: " << error.str();
stackTrace(0);
#endif
std::ostringstream msg;
msg << "OpenXcom has crashed: " << error.str() << std::endl;
msg << "More details here: " << Logger::logFile() << std::endl;
msg << "If this error was unexpected, please report it to the developers.";
showError(msg.str());
}
/**
* Opens a file or web path in the system default browser.
*/
bool openExplorer(const std::string &url)
{
#ifdef _WIN32
HINSTANCE ret = ShellExecuteW(NULL, L"open", Unicode::convMbToWc(url, CP_UTF8).c_str(), NULL, NULL, SW_SHOWNORMAL);
// The return value is not a true HINSTANCE. If the function succeeds, it returns a value greater than 32.
return (static_cast<int>(reinterpret_cast<uintptr_t>(ret)) > 32);
#elif __APPLE__
std::string cmd = "open \"" + url + "\"";
return (system(cmd.c_str()) == 0);
#else
std::string cmd = "xdg-open \"" + url + "\"";
return (system(cmd.c_str()) == 0);
#endif
}
}
}
↑ V570 The 'ctx' variable is assigned to itself.
↑ V595 The 'exception' pointer was utilized before it was verified against nullptr. Check lines: 1135, 1147.
↑ V1001 The 'ctx' variable is assigned but is not used by the end of the function.
↑ V1048 The 'ctx' variable was assigned the same value.
↑ V522 There might be dereferencing of a potential null pointer 'symbol'. Check lines: 993, 992.
↑ V522 There might be dereferencing of a potential null pointer 'line'. Check lines: 996, 995.