summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--BuildConfig.cpp.in2
-rw-r--r--BuildConfig.h4
-rw-r--r--CMakeLists.txt29
-rw-r--r--HandleCrash.cpp376
-rw-r--r--HandleCrash.h18
-rw-r--r--MultiMC.cpp3
-rw-r--r--MultiMC.h3
-rw-r--r--WinBacktrace.cpp77
-rw-r--r--WinBacktrace.h44
-rw-r--r--gui/MainWindow.cpp5
-rw-r--r--main.cpp11
11 files changed, 571 insertions, 1 deletions
diff --git a/BuildConfig.cpp.in b/BuildConfig.cpp.in
index 1ea07374..76196b7e 100644
--- a/BuildConfig.cpp.in
+++ b/BuildConfig.cpp.in
@@ -29,7 +29,9 @@ Config::Config()
UPDATER_FORCE_LOCAL = @MultiMC_UPDATER_FORCE_LOCAL_value@;
GIT_COMMIT = "@MultiMC_GIT_COMMIT@";
+ GIT_COMMIT_CSTR = "@MultiMC_GIT_COMMIT@";
VERSION_STR = "@MultiMC_VERSION_STRING@";
+ VERSION_CSTR = "@MultiMC_VERSION_STRING@";
NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@";
}
diff --git a/BuildConfig.h b/BuildConfig.h
index baf2ad6d..5e10beb0 100644
--- a/BuildConfig.h
+++ b/BuildConfig.h
@@ -61,10 +61,14 @@ public:
/// The commit hash of this build
QString GIT_COMMIT;
+ const char* GIT_COMMIT_CSTR;
/// This is printed on start to standard output
QString VERSION_STR;
+ /// Version string as a char string. Used by the crash handling system to avoid touching heap memory.
+ const char* VERSION_CSTR;
+
/**
* This is used to fetch the news RSS feed.
* It defaults in CMakeLists.txt to "http://multimc.org/rss.xml"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 17589f9a..531f7342 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,8 +75,29 @@ if(${BIGENDIAN})
endif(${BIGENDIAN})
-######## Set URLs ########
+######## Dark magic crash reports ########
+option(MultiMC_HANDLE_SEGV "Handle fatal crashes and generate crash reports." OFF)
+set(CRASH_HANDLER_IMPL "")
+message(STATUS "Crash dumps are ${MultiMC_HANDLE_SEGV}")
+if (MultiMC_HANDLE_SEGV)
+ add_definitions(-DHANDLE_SEGV)
+ if (WIN32)
+ find_package(DbgHelp)
+ set(MultiMC_LINK_ADDITIONAL_LIBS "${MultiMC_LINK_ADDITIONAL_LIBS}dbghelp")
+ set(MultiMC_CRASH_HANDLER_EXTRA_H "WinBacktrace.h")
+ set(MultiMC_CRASH_HANDLER_EXTRA_CPP "WinBacktrace.cpp")
+ endif ()
+endif ()
+
+option(MultiMC_TEST_SEGV "Intentionally segfault sometimes to test crash handling." OFF)
+if (MultiMC_TEST_SEGV)
+ # TODO: Make this a unit test instead.
+ message(WARNING "You have enabled crash handler testing. MULTIMC WILL INTENTIONALLY CRASH ITSELF. Crashes should occur on exit and when the new instance button is pressed.")
+ add_definitions(-DTEST_SEGV)
+endif ()
+
+######## Set URLs ########
set(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
@@ -250,6 +271,12 @@ SET(MULTIMC_SOURCES
BuildConfig.h
${PROJECT_BINARY_DIR}/BuildConfig.cpp
+ # Crash handling
+ HandleCrash.h
+ HandleCrash.cpp
+ ${MultiMC_CRASH_HANDLER_EXTRA_H} # Extra platform specific stuff
+ ${MultiMC_CRASH_HANDLER_EXTRA_CPP}
+
# Logging
logger/QsDebugOutput.cpp
logger/QsDebugOutput.h
diff --git a/HandleCrash.cpp b/HandleCrash.cpp
new file mode 100644
index 00000000..ed319cf1
--- /dev/null
+++ b/HandleCrash.cpp
@@ -0,0 +1,376 @@
+/* Copyright 2014 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This is the Unix implementation of MultiMC's crash handling system.
+#include <stdio.h>
+#include <iostream>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <MultiMC.h>
+
+#if defined Q_OS_UNIX
+#include <sys/utsname.h>
+#include <execinfo.h>
+#elif defined Q_OS_WIN32
+#include <windows.h>
+#include <dbghelp.h>
+#include <WinBacktrace.h>
+#endif
+
+#include "BuildConfig.h"
+
+#include "HandleCrash.h"
+
+// The maximum number of frames to include in the backtrace.
+#define BT_SIZE 20
+
+
+#define DUMPF_NAME_FMT "mmc-crash-%X.bm" // Black magic? Bowel movement? Dump?
+// 1234567890 1234
+// The maximum number of digits in a unix timestamp when encoded in hexadecimal is about 17.
+// Our format string is ~14 characters long.
+// The maximum length of the dump file's filename should be well over both of these. 42 is a good number.
+#define DUMPF_NAME_LEN 42
+
+// {{{ Platform hackery
+
+#if defined Q_OS_UNIX
+
+struct CrashData
+{
+ int signal = 0;
+};
+
+// This has to be declared here, after the CrashData struct, but before the function that uses it.
+void handleCrash(CrashData);
+
+void handler(int sig)
+{
+ CrashData cData;
+ cData.signal = sig;
+ handleCrash(cData);
+}
+
+#elif defined Q_OS_WIN32
+
+// Struct for storing platform specific crash information.
+// This gets passed into the generic handler, which will use
+// it to access platform specific information.
+struct CrashData
+{
+ EXCEPTION_RECORD* exceptionInfo;
+ CONTEXT* context;
+};
+
+void handleCrash(CrashData);
+
+LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* eInfo)
+{
+ CrashData cData;
+ cData.exceptionInfo = eInfo->ExceptionRecord;
+ cData.context = eInfo->ContextRecord;
+ handleCrash(cData);
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+#endif
+
+// }}}
+
+// {{{ Handling
+
+#ifdef Q_OS_WIN32
+// #ThanksMicrosoft
+// Blame Microsoft for this atrocity.
+void dprintf(int fd, const char* fmt...)
+{
+ va_list args;
+ va_start(args, fmt);
+ char buffer[10240];
+ // Just sprintf to a really long string and hope it works...
+ // This is a hack, but I can't think of a better way to do it easily.
+ int len = vsnprintf(buffer, 10240, fmt, args);
+ printf(buffer, fmt, args);
+ write(fd, buffer, len);
+ va_end(args);
+}
+#endif
+
+void getVsnType(char* out);
+void readFromTo(int from, int to);
+
+void dumpErrorInfo(int dumpFile, CrashData crash)
+{
+#ifdef Q_OS_UNIX
+ // TODO: Moar unix
+ dprintf(dumpFile, "Signal: %d\n", crash.signal);
+#elif defined Q_OS_WIN32
+ EXCEPTION_RECORD* excInfo = crash.exceptionInfo;
+
+ dprintf(dumpFile, "Exception Code: %d\n", excInfo->ExceptionCode);
+ dprintf(dumpFile, "Exception Address: 0x%0X\n", excInfo->ExceptionAddress);
+#endif
+}
+
+void dumpMiscInfo(int dumpFile)
+{
+ char vsnType[42]; // The version type. If it's more than 42 chars, the universe might implode...
+
+ // Get MMC info.
+ getVsnType(vsnType);
+
+ // Get MMC info.
+ getVsnType(vsnType);
+
+ dprintf(dumpFile, "MultiMC Version: %s\n", BuildConfig.VERSION_CSTR);
+ dprintf(dumpFile, "MultiMC Version Type: %s\n", vsnType);
+}
+
+void dumpBacktrace(int dumpFile, CrashData crash)
+{
+#ifdef Q_OS_UNIX
+ // Variables for storing crash info.
+ void* trace[BT_SIZE]; // Backtrace frames
+ size_t size; // The backtrace size
+
+ // Get the backtrace.
+ size = backtrace(trace, BT_SIZE);
+
+ // Dump the backtrace
+ dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n");
+ backtrace_symbols_fd(trace, size, dumpFile);
+ dprintf(dumpFile, "---- END BACKTRACE ----\n");
+#elif defined Q_OS_WIN32
+ dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n");
+
+ StackFrame stack[BT_SIZE];
+ size_t size;
+
+ SYMBOL_INFO *symbol;
+ HANDLE process;
+
+ size = getBacktrace(stack, BT_SIZE, *crash.context);
+
+ // FIXME: Accessing heap memory is supposedly "dangerous",
+ // but I can't find another way of doing this.
+
+ // Initialize
+ process = GetCurrentProcess();
+ if (!SymInitialize(process, NULL, true))
+ {
+ dprintf(dumpFile, "Failed to initialize symbol handler. Can't print stack trace.\n");
+ dprintf(dumpFile, "Here's a list of addresses in the call stack instead:\n");
+ for(int i = 0; i < size; i++)
+ {
+ dprintf(dumpFile, "0x%0X\n", (DWORD64)stack[i].address);
+ }
+ } else {
+ // Allocate memory... ._.
+ symbol = (SYMBOL_INFO *) calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
+ symbol->MaxNameLen = 255;
+ symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+
+ // Dump stacktrace
+ for(int i = 0; i < size; i++)
+ {
+ DWORD64 addr = (DWORD64)stack[i].address;
+ if (!SymFromAddr(process, (DWORD64)(addr), 0, symbol))
+ dprintf(dumpFile, "?? - 0x%0X\n", addr);
+ else
+ dprintf(dumpFile, "%s - 0x%0X\n", symbol->Name, symbol->Address);
+ }
+
+ free(symbol);
+ }
+
+ dprintf(dumpFile, "---- END BACKTRACE ----\n");
+#endif
+}
+
+void dumpSysInfo(int dumpFile)
+{
+#ifdef Q_OS_UNIX
+ bool gotSysInfo = false; // True if system info check succeeded
+ utsname sysinfo; // System information
+
+ // Dump system info
+ if (uname(&sysinfo) >= 0)
+ {
+ dprintf(dumpFile, "OS System: %s\n", sysinfo.sysname);
+ dprintf(dumpFile, "OS Machine: %s\n", sysinfo.machine);
+ dprintf(dumpFile, "OS Release: %s\n", sysinfo.release);
+ dprintf(dumpFile, "OS Version: %s\n", sysinfo.version);
+ } else {
+ dprintf(dumpFile, "OS System: Unknown Unix");
+ }
+#else
+ // TODO: Get more information here.
+ dprintf(dumpFile, "OS System: Windows");
+#endif
+}
+
+void dumpLogs(int dumpFile)
+{
+ int otherFile;
+
+ // Attempt to attach the log file if the logger was initialized.
+ dprintf(dumpFile, "---- BEGIN LOGS ----\n");
+ if (loggerInitialized)
+ {
+ otherFile = open("MultiMC-0.log", O_RDONLY);
+ readFromTo(otherFile, dumpFile);
+ } else {
+ dprintf(dumpFile, "Logger not initialized.\n");
+ }
+ dprintf(dumpFile, "---- END LOGS ----\n");
+}
+
+// The signal handler. If this function is called, it means shit has probably collided with some sort of device one might use to keep oneself cool.
+// This is the generic part of the code that will be called after platform specific handling is finished.
+void handleCrash(CrashData crash)
+{
+#ifdef Q_OS_UNIX
+ fprintf(stderr, "Fatal error! Received signal %d\n", crash.signal);
+#endif
+
+ time_t unixTime = 0; // Unix timestamp. Used to give our crash dumps "unique" names.
+
+ char dumpFileName[DUMPF_NAME_LEN]; // The name of the file we're dumping to.
+ int dumpFile; // File descriptor for our dump file.
+
+ // Determine what our dump file should be called.
+ // We'll just call it "mmc-crash-<unixtime>.dump"
+ // First, check the time.
+ time(&unixTime);
+
+ // Now we get to do some !!FUN!! hackery to ensure we don't use the stack when we convert
+ // the timestamp from an int to a string. To do this, we just allocate a fixed size array
+ // of chars on the stack, and sprintf into it. We know the timestamp won't ever be longer
+ // than a certain number of digits, so this should work just fine.
+ // sprintf doesn't support writing signed values as hex, so this breaks on negative timestamps.
+ // It really shouldn't matter, though...
+ sprintf(dumpFileName, DUMPF_NAME_FMT, unixTime);
+
+ // Now, we need to open the file.
+ // Fail if it already exists. This should never happen.
+ dumpFile = open(dumpFileName, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+
+
+ if (dumpFile >= 0)
+ {
+ // If we opened the dump file successfully.
+ // Dump everything we can and GTFO.
+ fprintf(stderr, "Dumping crash report to %s\n", dumpFileName);
+
+ // Dump misc info
+ dprintf(dumpFile, "Unix Time: %d\n", unixTime);
+ dumpErrorInfo(dumpFile, crash);
+ dumpMiscInfo(dumpFile);
+
+ dprintf(dumpFile, "\n");
+
+ dumpSysInfo(dumpFile);
+
+ dprintf(dumpFile, "\n");
+
+ dumpBacktrace(dumpFile, crash);
+
+ dprintf(dumpFile, "\n");
+
+ // DIE DIE DIE!
+ exit(1);
+ }
+ else
+ {
+ fprintf(stderr, "Failed to open dump file %s to write crash info (ERRNO: %d)\n", dumpFileName, errno);
+ exit(2);
+ }
+}
+
+
+// Reads data from the file descriptor on the first argument into the second argument.
+void readFromTo(int from, int to)
+{
+ char buffer[1024];
+ size_t lastread = 1;
+ while (lastread > 0)
+ {
+ lastread = read(from, buffer, 1024);
+ if (lastread > 0) write(to, buffer, lastread);
+ }
+}
+
+// Writes the current version type to the given char buffer.
+void getVsnType(char* out)
+{
+ switch (BuildConfig.versionTypeEnum)
+ {
+ case Config::Release:
+ sprintf(out, "Release");
+ break;
+ case Config::ReleaseCandidate:
+ sprintf(out, "ReleaseCandidate");
+ break;
+ case Config::Development:
+ sprintf(out, "Development");
+ break;
+ default:
+ sprintf(out, "Unknown");
+ break;
+ }
+}
+
+// }}}
+
+// {{{ Misc
+
+#if defined TEST_SEGV
+// Causes a crash. For testing.
+void testCrash()
+{
+ char* lol = (char*)MMC->settings().get();
+ lol -= 8;
+
+ // Throw shit at the fan.
+ for (int i = 0; i < 8; i++)
+ lol[i] = 'f';
+}
+#endif
+
+// Initializes the Unix crash handler.
+void initBlackMagic()
+{
+#ifdef Q_OS_UNIX
+ // Register the handler.
+ signal(SIGSEGV, handler);
+ signal(SIGABRT, handler);
+#elif defined Q_OS_WIN32
+ // I hate Windows
+ SetUnhandledExceptionFilter(ExceptionFilter);
+#endif
+
+#ifdef TEST_SEGV
+ testCrash();
+#endif
+}
+
+// }}}
+
diff --git a/HandleCrash.h b/HandleCrash.h
new file mode 100644
index 00000000..3b2e7d00
--- /dev/null
+++ b/HandleCrash.h
@@ -0,0 +1,18 @@
+// This is a simple header file for the crash handling system. It exposes only one method,
+// initBlackMagic, which initializes the system, registering signal handlers, or doing
+// whatever stupid things need to be done on Windows.
+// The platform specific implementations for this system are in UnixCrash.cpp and
+// WinCrash.cpp.
+
+#if defined Q_OS_WIN
+#warning Crash handling is not yet implemented on Windows.
+#elif defined Q_OS_UNIX
+#else
+#warning Crash handling is not supported on this platform.
+#endif
+
+/**
+ * Initializes the crash handling system.
+ */
+void initBlackMagic();
+
diff --git a/MultiMC.cpp b/MultiMC.cpp
index 41c81b7b..bdebe181 100644
--- a/MultiMC.cpp
+++ b/MultiMC.cpp
@@ -340,8 +340,11 @@ void MultiMC::initLogger()
logger.addDestination(m_debugDestination.get());
// log all the things
logger.setLoggingLevel(QsLogging::TraceLevel);
+ loggerInitialized = true;
}
+bool loggerInitialized = false;
+
void MultiMC::initGlobalSettings()
{
m_settings.reset(new INISettingsObject("multimc.cfg", this));
diff --git a/MultiMC.h b/MultiMC.h
index 00eb97f8..0fd60b7d 100644
--- a/MultiMC.h
+++ b/MultiMC.h
@@ -48,6 +48,9 @@ enum UpdateFlag
Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag);
Q_DECLARE_OPERATORS_FOR_FLAGS(UpdateFlags);
+// Global var used by the crash handling system to determine if a log file should be included in a crash report.
+extern bool loggerInitialized;
+
class MultiMC : public QApplication
{
Q_OBJECT
diff --git a/WinBacktrace.cpp b/WinBacktrace.cpp
new file mode 100644
index 00000000..1ea079bf
--- /dev/null
+++ b/WinBacktrace.cpp
@@ -0,0 +1,77 @@
+/* Copyright 2014 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// CAUTION:
+// This file contains all manner of hackery and insanity.
+// I will not be responsible for any loss of sanity due to reading this code.
+// Here be dragons!
+
+#include "WinBacktrace.h"
+
+#include <windows.h>
+
+#ifndef __i386__
+#error WinBacktrace is only supported on x86 architectures.
+#endif
+
+// We need to do some crazy shit to walk through the stack.
+// Windows unwinds the stack when an exception is thrown, so we
+// need to examine the EXCEPTION_POINTERS's CONTEXT.
+size_t getBacktrace(StackFrame *stack, size_t size, CONTEXT ctx)
+{
+ // Written using information and a bit of pseudocode from
+ // http://www.eptacom.net/pubblicazioni/pub_eng/except.html
+ // This is probably one of the most horrifying things I've ever written.
+
+ // This tracks whether the current EBP is valid.
+ // When an invalid EBP is encountered, we stop walking the stack.
+ bool validEBP = true;
+ DWORD ebp = ctx.Ebp; // The current EBP (Extended Base Pointer)
+ DWORD eip = ctx.Eip;
+ int i;
+ for (i = 0; i < size; i++)
+ {
+ if (ebp & 3)
+ validEBP = false;
+ // FIXME: This function is obsolete, according to MSDN.
+ else if (IsBadReadPtr((void*) ebp, 8))
+ validEBP = false;
+
+ if (!validEBP) break;
+
+ // Find the caller.
+ // On the first iteration, the caller is whatever EIP points to.
+ // On successive iterations, the caller is the byte after EBP.
+ BYTE* caller = !i ? (BYTE*)eip : *((BYTE**) ebp + 1);
+ // The first ebp is the EBP from the CONTEXT.
+ // On successive iterations, the EBP is the DWORD that the previous EBP points to.
+ ebp = !i ? ebp : *(DWORD*)ebp;
+
+ // Find the caller's module.
+ // We'll use VirtualQuery to get information about the caller's address.
+ MEMORY_BASIC_INFORMATION mbi;
+ VirtualQuery(caller, &mbi, sizeof(mbi));
+
+ // We can get the instance handle from the allocation base.
+ HINSTANCE hInst = (HINSTANCE)mbi.AllocationBase;
+
+ // If the handle is 0, then the EBP is invalid.
+ if (hInst == 0) validEBP = false;
+ // Otherwise, dump info about the caller.
+ else stack[i].address = (void*)caller;
+ }
+
+ return i;
+}
diff --git a/WinBacktrace.h b/WinBacktrace.h
new file mode 100644
index 00000000..d1e6301a
--- /dev/null
+++ b/WinBacktrace.h
@@ -0,0 +1,44 @@
+/* Copyright 2014 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <windows.h>
+
+#ifndef SF_STR_LEN
+// The max length of all strings in the StackFrame struct.
+// Because it must be stack allocated, this must be known at compile time.
+// Stuff longer than this will be truncated.
+// Defaults to 4096 (4kB)
+#define SF_STR_LEN 4096
+#endif
+
+// Data structure for holding information about a stack frame.
+// There's some more hackery in here so it can be allocated on the stack.
+struct StackFrame
+{
+ // The address of this stack frame.
+ void* address;
+
+ // The name of the function at this address.
+ char funcName[SF_STR_LEN];
+};
+
+// This function walks through the given CONTEXT structure, extracting a
+// backtrace from it.
+// The backtrace will be put into the array given by the `stack` argument
+// with a maximum length of `size`.
+// This function returns the size of the backtrace retrieved.
+size_t getBacktrace(StackFrame* stack, size_t size, CONTEXT ctx);
diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp
index 554a58ec..5ba05b2a 100644
--- a/gui/MainWindow.cpp
+++ b/gui/MainWindow.cpp
@@ -709,6 +709,11 @@ void MainWindow::setCatBackground(bool enabled)
void MainWindow::on_actionAddInstance_triggered()
{
+#ifdef TEST_SEGV
+ // For further testing stuff.
+ int v = *((int*)-1);
+#endif
+
if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask &&
m_versionLoadTask->isRunning())
{
diff --git a/main.cpp b/main.cpp
index 181d7299..a79cc014 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,6 +1,12 @@
#include "MultiMC.h"
#include "gui/MainWindow.h"
+// Crash handling
+#ifdef HANDLE_SEGV
+#include <HandleCrash.h>
+#endif
+
+
int main_gui(MultiMC &app)
{
// show main window
@@ -23,6 +29,11 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(multimc);
Q_INIT_RESOURCE(backgrounds);
+#ifdef HANDLE_SEGV
+ // Register signal handler for generating crash reports.
+ initBlackMagic();
+#endif
+
switch (app.status())
{
case MultiMC::Initialized: