From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- toolkit/crashreporter/client/crashreporter.cpp | 833 +++++++++++++++++++++++++ 1 file changed, 833 insertions(+) create mode 100644 toolkit/crashreporter/client/crashreporter.cpp (limited to 'toolkit/crashreporter/client/crashreporter.cpp') diff --git a/toolkit/crashreporter/client/crashreporter.cpp b/toolkit/crashreporter/client/crashreporter.cpp new file mode 100644 index 0000000000..4426e6446c --- /dev/null +++ b/toolkit/crashreporter/client/crashreporter.cpp @@ -0,0 +1,833 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "crashreporter.h" + +#ifdef _MSC_VER +// Disable exception handler warnings. +# pragma warning(disable : 4530) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef XP_LINUX +# include +#endif + +#include "json/json.h" +#include "nss.h" +#include "sechash.h" + +using std::ifstream; +using std::ios; +using std::istream; +using std::istringstream; +using std::ofstream; +using std::ostream; +using std::ostringstream; +using std::string; +using std::unique_ptr; +using std::vector; + +namespace CrashReporter { + +StringTable gStrings; +Json::Value gData; +string gSettingsPath; +string gEventsPath; +string gPingPath; +int gArgc; +char** gArgv; +bool gAutoSubmit; + +enum SubmissionResult { Succeeded, Failed }; + +static unique_ptr gLogStream(nullptr); +static string gReporterDumpFile; +static string gExtraFile; +static string gMemoryFile; + +static const char kExtraDataExtension[] = ".extra"; +static const char kMemoryReportExtension[] = ".memory.json.gz"; + +void UIError(const string& message) { + if (gAutoSubmit) { + return; + } + + string errorMessage; + if (!gStrings[ST_CRASHREPORTERERROR].empty()) { + char buf[2048]; + snprintf(buf, 2048, gStrings[ST_CRASHREPORTERERROR].c_str(), + message.c_str()); + errorMessage = buf; + } else { + errorMessage = message; + } + + UIError_impl(errorMessage); +} + +static string Unescape(const string& str) { + string ret; + for (string::const_iterator iter = str.begin(); iter != str.end(); iter++) { + if (*iter == '\\') { + iter++; + if (*iter == '\\') { + ret.push_back('\\'); + } else if (*iter == 'n') { + ret.push_back('\n'); + } else if (*iter == 't') { + ret.push_back('\t'); + } + } else { + ret.push_back(*iter); + } + } + + return ret; +} + +bool ReadStrings(istream& in, StringTable& strings, bool unescape) { + string currentSection; + while (!in.eof()) { + string line; + std::getline(in, line); + int sep = line.find('='); + if (sep >= 0) { + string key, value; + key = line.substr(0, sep); + value = line.substr(sep + 1); + if (unescape) value = Unescape(value); + strings[key] = value; + } + } + + return true; +} + +bool ReadStringsFromFile(const string& path, StringTable& strings, + bool unescape) { + ifstream* f = UIOpenRead(path, ios::in); + bool success = false; + if (f->is_open()) { + success = ReadStrings(*f, strings, unescape); + f->close(); + } + + delete f; + return success; +} + +static bool ReadExtraFile(const string& aExtraDataPath, Json::Value& aExtra) { + bool success = false; + ifstream* f = UIOpenRead(aExtraDataPath, ios::in); + if (f->is_open()) { + Json::CharReaderBuilder builder; + success = parseFromStream(builder, *f, &aExtra, nullptr); + } + + delete f; + return success; +} + +static string Basename(const string& file) { + string::size_type slashIndex = file.rfind(UI_DIR_SEPARATOR); + if (slashIndex != string::npos) { + return file.substr(slashIndex + 1); + } + return file; +} + +static bool ReadEventFile(const string& aPath, string& aEventVersion, + string& aTime, string& aUuid, Json::Value& aData) { + bool res = false; + ifstream* f = UIOpenRead(aPath, ios::binary); + + if (f->is_open()) { + std::getline(*f, aEventVersion, '\n'); + res = f->good(); + std::getline(*f, aTime, '\n'); + res &= f->good(); + std::getline(*f, aUuid, '\n'); + res &= f->good(); + + if (res) { + Json::CharReaderBuilder builder; + res = parseFromStream(builder, *f, &aData, nullptr); + } + } + + delete f; + return res; +} + +static void OverwriteEventFile(const string& aPath, const string& aEventVersion, + const string& aTime, const string& aUuid, + const Json::Value& aData) { + ofstream* f = UIOpenWrite(aPath, ios::binary | ios::trunc); + if (f->is_open()) { + f->write(aEventVersion.c_str(), aEventVersion.length()) << '\n'; + f->write(aTime.c_str(), aTime.length()) << '\n'; + f->write(aUuid.c_str(), aUuid.length()) << '\n'; + + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + std::unique_ptr writer(builder.newStreamWriter()); + writer->write(aData, f); + *f << "\n"; + } + + delete f; +} + +static void UpdateEventFile(const Json::Value& aExtraData, const string& aHash, + const string& aPingUuid) { + if (gEventsPath.empty()) { + // If there is no path for finding the crash event, skip this step. + return; + } + + string localId = CrashReporter::GetDumpLocalID(); + string path = gEventsPath + UI_DIR_SEPARATOR + localId; + string eventVersion; + string crashTime; + string crashUuid; + Json::Value eventData; + + if (!ReadEventFile(path, eventVersion, crashTime, crashUuid, eventData)) { + return; + } + + if (!aHash.empty()) { + eventData["MinidumpSha256Hash"] = aHash; + } + + if (!aPingUuid.empty()) { + eventData["CrashPingUUID"] = aPingUuid; + } + + if (aExtraData.isMember("StackTraces")) { + eventData["StackTraces"] = aExtraData["StackTraces"]; + } + + OverwriteEventFile(path, eventVersion, crashTime, crashUuid, eventData); +} + +static void WriteSubmissionEvent(SubmissionResult result, + const string& remoteId) { + if (gEventsPath.empty()) { + // If there is no path for writing the submission event, skip it. + return; + } + + string localId = CrashReporter::GetDumpLocalID(); + string fpath = gEventsPath + UI_DIR_SEPARATOR + localId + "-submission"; + ofstream* f = UIOpenWrite(fpath, ios::binary); + time_t tm; + time(&tm); + + if (f->is_open()) { + *f << "crash.submission.1\n"; + *f << tm << "\n"; + *f << localId << "\n"; + *f << (result == Succeeded ? "true" : "false") << "\n"; + *f << remoteId; + + f->close(); + } + + delete f; +} + +void LogMessage(const std::string& message) { + if (gLogStream.get()) { + char date[64]; + time_t tm; + time(&tm); + if (strftime(date, sizeof(date) - 1, "%c", localtime(&tm)) == 0) + date[0] = '\0'; + (*gLogStream) << "[" << date << "] " << message << std::endl; + } +} + +static void OpenLogFile() { + string logPath = gSettingsPath + UI_DIR_SEPARATOR + "submit.log"; + gLogStream.reset(UIOpenWrite(logPath, ios::app)); +} + +static bool ReadConfig() { + string iniPath; + if (!UIGetIniPath(iniPath)) { + return false; + } + + if (!ReadStringsFromFile(iniPath, gStrings, true)) return false; + + // See if we have a string override file, if so process it + char* overrideEnv = getenv("MOZ_CRASHREPORTER_STRINGS_OVERRIDE"); + if (overrideEnv && *overrideEnv && UIFileExists(overrideEnv)) + ReadStringsFromFile(overrideEnv, gStrings, true); + + return true; +} + +static string GetAdditionalFilename(const string& dumpfile, + const char* extension) { + string filename(dumpfile); + int dot = filename.rfind('.'); + if (dot < 0) return ""; + + filename.replace(dot, filename.length() - dot, extension); + return filename; +} + +static bool MoveCrashData(const string& toDir, string& dumpfile, + string& extrafile, string& memoryfile) { + if (!UIEnsurePathExists(toDir)) { + UIError(gStrings[ST_ERROR_CREATEDUMPDIR]); + return false; + } + + string newDump = toDir + UI_DIR_SEPARATOR + Basename(dumpfile); + string newExtra = toDir + UI_DIR_SEPARATOR + Basename(extrafile); + string newMemory = toDir + UI_DIR_SEPARATOR + Basename(memoryfile); + + if (!UIMoveFile(dumpfile, newDump)) { + UIError(gStrings[ST_ERROR_DUMPFILEMOVE]); + return false; + } + + if (!UIMoveFile(extrafile, newExtra)) { + UIError(gStrings[ST_ERROR_EXTRAFILEMOVE]); + return false; + } + + if (!memoryfile.empty()) { + // Ignore errors from moving the memory file + if (!UIMoveFile(memoryfile, newMemory)) { + UIDeleteFile(memoryfile); + newMemory.erase(); + } + memoryfile = newMemory; + } + + dumpfile = newDump; + extrafile = newExtra; + + return true; +} + +static bool AddSubmittedReport(const string& serverResponse) { + StringTable responseItems; + istringstream in(serverResponse); + ReadStrings(in, responseItems, false); + + if (responseItems.find("StopSendingReportsFor") != responseItems.end()) { + // server wants to tell us to stop sending reports for a certain version + string reportPath = gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + + responseItems["StopSendingReportsFor"]; + + ofstream* reportFile = UIOpenWrite(reportPath, ios::trunc); + if (reportFile->is_open()) { + // don't really care about the contents + *reportFile << 1 << "\n"; + reportFile->close(); + } + delete reportFile; + } + + if (responseItems.find("Discarded") != responseItems.end()) { + // server discarded this report... save it so the user can resubmit it + // manually + return false; + } + + if (responseItems.find("CrashID") == responseItems.end()) return false; + + string submittedDir = gSettingsPath + UI_DIR_SEPARATOR + "submitted"; + if (!UIEnsurePathExists(submittedDir)) { + return false; + } + + string path = + submittedDir + UI_DIR_SEPARATOR + responseItems["CrashID"] + ".txt"; + + ofstream* file = UIOpenWrite(path, ios::trunc); + if (!file->is_open()) { + delete file; + return false; + } + + char buf[1024]; + snprintf(buf, 1024, gStrings["CrashID"].c_str(), + responseItems["CrashID"].c_str()); + *file << buf << "\n"; + + if (responseItems.find("ViewURL") != responseItems.end()) { + snprintf(buf, 1024, gStrings["CrashDetailsURL"].c_str(), + responseItems["ViewURL"].c_str()); + *file << buf << "\n"; + } + + file->close(); + delete file; + + WriteSubmissionEvent(Succeeded, responseItems["CrashID"]); + return true; +} + +void DeleteDump() { + const char* noDelete = getenv("MOZ_CRASHREPORTER_NO_DELETE_DUMP"); + if (!noDelete || *noDelete == '\0') { + if (!gReporterDumpFile.empty()) UIDeleteFile(gReporterDumpFile); + if (!gExtraFile.empty()) UIDeleteFile(gExtraFile); + if (!gMemoryFile.empty()) UIDeleteFile(gMemoryFile); + } +} + +void SendCompleted(bool success, const string& serverResponse) { + if (success) { + if (AddSubmittedReport(serverResponse)) { + DeleteDump(); + } else { + string directory = gReporterDumpFile; + int slashpos = directory.find_last_of("/\\"); + if (slashpos < 2) return; + directory.resize(slashpos); + UIPruneSavedDumps(directory); + WriteSubmissionEvent(Failed, ""); + } + } else { + WriteSubmissionEvent(Failed, ""); + } +} + +static string ComputeDumpHash() { +#ifdef XP_LINUX + // On Linux we rely on the system-provided libcurl which uses nss so we have + // to also use the system-provided nss instead of the ones we have bundled. + const char* libnssNames[] = { + "libnss3.so", +# ifndef HAVE_64BIT_BUILD + // 32-bit versions on 64-bit hosts + "/usr/lib32/libnss3.so", +# endif + }; + void* lib = nullptr; + + for (const char* libname : libnssNames) { + lib = dlopen(libname, RTLD_NOW); + + if (lib) { + break; + } + } + + if (!lib) { + return ""; + } + + SECStatus (*NSS_Initialize)(const char*, const char*, const char*, + const char*, PRUint32); + HASHContext* (*HASH_Create)(HASH_HashType); + void (*HASH_Destroy)(HASHContext*); + void (*HASH_Begin)(HASHContext*); + void (*HASH_Update)(HASHContext*, const unsigned char*, unsigned int); + void (*HASH_End)(HASHContext*, unsigned char*, unsigned int*, unsigned int); + + *(void**)(&NSS_Initialize) = dlsym(lib, "NSS_Initialize"); + *(void**)(&HASH_Create) = dlsym(lib, "HASH_Create"); + *(void**)(&HASH_Destroy) = dlsym(lib, "HASH_Destroy"); + *(void**)(&HASH_Begin) = dlsym(lib, "HASH_Begin"); + *(void**)(&HASH_Update) = dlsym(lib, "HASH_Update"); + *(void**)(&HASH_End) = dlsym(lib, "HASH_End"); + + if (!HASH_Create || !HASH_Destroy || !HASH_Begin || !HASH_Update || + !HASH_End) { + return ""; + } +#endif + // Minimal NSS initialization so we can use the hash functions + const PRUint32 kNssFlags = NSS_INIT_READONLY | NSS_INIT_NOROOTINIT | + NSS_INIT_NOMODDB | NSS_INIT_NOCERTDB; + if (NSS_Initialize(nullptr, "", "", "", kNssFlags) != SECSuccess) { + return ""; + } + + HASHContext* hashContext = HASH_Create(HASH_AlgSHA256); + + if (!hashContext) { + return ""; + } + + HASH_Begin(hashContext); + + ifstream* f = UIOpenRead(gReporterDumpFile, ios::binary); + bool error = false; + + // Read the minidump contents + if (f->is_open()) { + uint8_t buff[4096]; + + do { + f->read((char*)buff, sizeof(buff)); + + if (f->bad()) { + error = true; + break; + } + + HASH_Update(hashContext, buff, f->gcount()); + } while (!f->eof()); + + f->close(); + } else { + error = true; + } + + delete f; + + // Finalize the hash computation + uint8_t result[SHA256_LENGTH]; + uint32_t resultLen = 0; + + HASH_End(hashContext, result, &resultLen, SHA256_LENGTH); + + if (resultLen != SHA256_LENGTH) { + error = true; + } + + HASH_Destroy(hashContext); + + if (!error) { + ostringstream hash; + + for (size_t i = 0; i < SHA256_LENGTH; i++) { + hash << std::setw(2) << std::setfill('0') << std::hex + << static_cast(result[i]); + } + + return hash.str(); + } + return ""; // If we encountered an error, return an empty hash +} + +string GetDumpLocalID() { + string localId = Basename(gReporterDumpFile); + string::size_type dot = localId.rfind('.'); + + if (dot == string::npos) return ""; + + return localId.substr(0, dot); +} + +string GetProgramPath(const string& exename) { + string path = gArgv[0]; + size_t pos = path.rfind(UI_CRASH_REPORTER_FILENAME BIN_SUFFIX); + path.erase(pos); +#ifdef XP_MACOSX + // On macOS the crash reporter client is shipped as an application bundle + // contained within Firefox' main application bundle. So when it's invoked + // its current working directory looks like: + // Firefox.app/Contents/MacOS/crashreporter.app/Contents/MacOS/ + // The other applications we ship with Firefox are stored in the main bundle + // (Firefox.app/Contents/MacOS/) so we we need to go back three directories + // to reach them. + path.append("../../../"); +#endif // XP_MACOSX + path.append(exename + BIN_SUFFIX); + + return path; +} + +} // namespace CrashReporter + +using namespace CrashReporter; + +Json::Value kEmptyJsonString(""); + +void RewriteStrings(Json::Value& aExtraData) { + // rewrite some UI strings with the values from the query parameters + string product = aExtraData.get("ProductName", kEmptyJsonString).asString(); + Json::Value mozilla("Mozilla"); + string vendor = aExtraData.get("Vendor", mozilla).asString(); + + char buf[4096]; + snprintf(buf, sizeof(buf), gStrings[ST_CRASHREPORTERVENDORTITLE].c_str(), + vendor.c_str()); + gStrings[ST_CRASHREPORTERTITLE] = buf; + + string str = gStrings[ST_CRASHREPORTERPRODUCTERROR]; + // Only do the replacement here if the string has two + // format specifiers to start. Otherwise + // we assume it has the product name hardcoded. + string::size_type pos = str.find("%s"); + if (pos != string::npos) pos = str.find("%s", pos + 2); + if (pos != string::npos) { + // Leave a format specifier for UIError to fill in + snprintf(buf, sizeof(buf), gStrings[ST_CRASHREPORTERPRODUCTERROR].c_str(), + product.c_str(), "%s"); + gStrings[ST_CRASHREPORTERERROR] = buf; + } else { + // product name is hardcoded + gStrings[ST_CRASHREPORTERERROR] = str; + } + + snprintf(buf, sizeof(buf), gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(), + product.c_str()); + gStrings[ST_CRASHREPORTERDESCRIPTION] = buf; + + snprintf(buf, sizeof(buf), gStrings[ST_CHECKSUBMIT].c_str(), vendor.c_str()); + gStrings[ST_CHECKSUBMIT] = buf; + + snprintf(buf, sizeof(buf), gStrings[ST_RESTART].c_str(), product.c_str()); + gStrings[ST_RESTART] = buf; + + snprintf(buf, sizeof(buf), gStrings[ST_QUIT].c_str(), product.c_str()); + gStrings[ST_QUIT] = buf; + + snprintf(buf, sizeof(buf), gStrings[ST_ERROR_ENDOFLIFE].c_str(), + product.c_str()); + gStrings[ST_ERROR_ENDOFLIFE] = buf; +} + +bool CheckEndOfLifed(const Json::Value& aVersion) { + if (!aVersion.isString()) { + return false; + } + + string reportPath = + gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + aVersion.asString(); + return UIFileExists(reportPath); +} + +int main(int argc, char** argv) { + gArgc = argc; + gArgv = argv; + + string autoSubmitEnv = UIGetEnv("MOZ_CRASHREPORTER_AUTO_SUBMIT"); + gAutoSubmit = !autoSubmitEnv.empty(); + + if (!ReadConfig()) { + UIError("Couldn't read configuration."); + return 0; + } + + if (!UIInit()) { + return 0; + } + + if (argc > 1) { + gReporterDumpFile = argv[1]; + } + + if (gReporterDumpFile.empty()) { + // no dump file specified, run the default UI + if (!gAutoSubmit) { + UIShowDefaultUI(); + } + } else { + // Start by running minidump analyzer to gather stack traces. + string reporterDumpFile = gReporterDumpFile; + vector args = {reporterDumpFile}; + string dumpAllThreadsEnv = UIGetEnv("MOZ_CRASHREPORTER_DUMP_ALL_THREADS"); + if (!dumpAllThreadsEnv.empty()) { + args.insert(args.begin(), "--full"); + } + UIRunProgram(CrashReporter::GetProgramPath(UI_MINIDUMP_ANALYZER_FILENAME), + args, + /* wait */ true); + + // go ahead with the crash reporter + gExtraFile = GetAdditionalFilename(gReporterDumpFile, kExtraDataExtension); + if (gExtraFile.empty()) { + UIError(gStrings[ST_ERROR_BADARGUMENTS]); + return 0; + } + + if (!UIFileExists(gExtraFile)) { + UIError(gStrings[ST_ERROR_EXTRAFILEEXISTS]); + return 0; + } + + gMemoryFile = + GetAdditionalFilename(gReporterDumpFile, kMemoryReportExtension); + if (!UIFileExists(gMemoryFile)) { + gMemoryFile.erase(); + } + + Json::Value extraData; + if (!ReadExtraFile(gExtraFile, extraData)) { + UIError(gStrings[ST_ERROR_EXTRAFILEREAD]); + return 0; + } + + if (!extraData.isMember("ProductName")) { + UIError(gStrings[ST_ERROR_NOPRODUCTNAME]); + return 0; + } + + // There is enough information in the extra file to rewrite strings + // to be product specific + RewriteStrings(extraData); + + if (!extraData.isMember("ServerURL")) { + UIError(gStrings[ST_ERROR_NOSERVERURL]); + return 0; + } + + // Hopefully the settings path exists in the environment. Try that before + // asking the platform-specific code to guess. + gSettingsPath = UIGetEnv("MOZ_CRASHREPORTER_DATA_DIRECTORY"); + if (gSettingsPath.empty()) { + string product = + extraData.get("ProductName", kEmptyJsonString).asString(); + string vendor = extraData.get("Vendor", kEmptyJsonString).asString(); + + if (!UIGetSettingsPath(vendor, product, gSettingsPath)) { + gSettingsPath.clear(); + } + } + + if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) { + UIError(gStrings[ST_ERROR_NOSETTINGSPATH]); + return 0; + } + + OpenLogFile(); + + gEventsPath = UIGetEnv("MOZ_CRASHREPORTER_EVENTS_DIRECTORY"); + gPingPath = UIGetEnv("MOZ_CRASHREPORTER_PING_DIRECTORY"); + + // Assemble and send the crash ping + string hash = ComputeDumpHash(); + + string pingUuid; + SendCrashPing(extraData, hash, pingUuid, gPingPath); + UpdateEventFile(extraData, hash, pingUuid); + + if (!UIFileExists(gReporterDumpFile)) { + UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]); + return 0; + } + + string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending"; + if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile, + gMemoryFile)) { + return 0; + } + + string sendURL = extraData.get("ServerURL", kEmptyJsonString).asString(); + // we don't need to actually send these + extraData.removeMember("ServerURL"); + extraData.removeMember("StackTraces"); + + extraData["SubmittedFrom"] = "Client"; + extraData["Throttleable"] = "1"; + + // re-set XUL_APP_FILE for xulrunner wrapped apps + const char* appfile = getenv("MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE"); + if (appfile && *appfile) { + const char prefix[] = "XUL_APP_FILE="; + char* env = (char*)malloc(strlen(appfile) + strlen(prefix) + 1); + if (!env) { + UIError("Out of memory"); + return 0; + } + strcpy(env, prefix); + strcat(env, appfile); + putenv(env); + free(env); + } + + vector restartArgs; + + ostringstream paramName; + int i = 0; + paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; + const char* param = getenv(paramName.str().c_str()); + while (param && *param) { + restartArgs.push_back(param); + + paramName.str(""); + paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; + param = getenv(paramName.str().c_str()); + } + + // allow override of the server url via environment variable + // XXX: remove this in the far future when our robot + // masters force everyone to use XULRunner + char* urlEnv = getenv("MOZ_CRASHREPORTER_URL"); + if (urlEnv && *urlEnv) { + sendURL = urlEnv; + } + + // see if this version has been end-of-lifed + + if (extraData.isMember("Version") && + CheckEndOfLifed(extraData["Version"])) { + UIError(gStrings[ST_ERROR_ENDOFLIFE]); + DeleteDump(); + return 0; + } + + StringTable files; + files["upload_file_minidump"] = gReporterDumpFile; + if (!gMemoryFile.empty()) { + files["memory_report"] = gMemoryFile; + } + + if (!UIShowCrashUI(files, extraData, sendURL, restartArgs)) { + DeleteDump(); + } + } + + UIShutdown(); + + return 0; +} + +#if defined(XP_WIN) && !defined(__GNUC__) +# include + +// We need WinMain in order to not be a console app. This function is unused +// if we are a console application. +int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR args, int) { + // Remove everything except close window from the context menu + { + HKEY hkApp; + RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", 0, + nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr); + RegCloseKey(hkApp); + if (RegCreateKeyExW(HKEY_CURRENT_USER, + L"Software\\Classes\\Applications\\crashreporter.exe", + 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr) == ERROR_SUCCESS) { + RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0); + RegCloseKey(hkApp); + } + } + + char** argv = static_cast(malloc(__argc * sizeof(char*))); + for (int i = 0; i < __argc; i++) { + argv[i] = strdup(WideToUTF8(__wargv[i]).c_str()); + } + + // Do the real work. + return main(__argc, argv); +} +#endif -- cgit v1.2.3