1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/crashreporter/client/crashreporter.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,620 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "crashreporter.h" 1.10 + 1.11 +#ifdef _MSC_VER 1.12 +// Disable exception handler warnings. 1.13 +# pragma warning( disable : 4530 ) 1.14 +#endif 1.15 + 1.16 +#include <fstream> 1.17 +#include <sstream> 1.18 +#include <memory> 1.19 +#include <time.h> 1.20 +#include <stdlib.h> 1.21 +#include <string.h> 1.22 +#include "mozilla/NullPtr.h" 1.23 + 1.24 +using std::string; 1.25 +using std::istream; 1.26 +using std::ifstream; 1.27 +using std::istringstream; 1.28 +using std::ostringstream; 1.29 +using std::ostream; 1.30 +using std::ofstream; 1.31 +using std::vector; 1.32 +using std::auto_ptr; 1.33 + 1.34 +namespace CrashReporter { 1.35 + 1.36 +StringTable gStrings; 1.37 +string gSettingsPath; 1.38 +int gArgc; 1.39 +char** gArgv; 1.40 + 1.41 +static auto_ptr<ofstream> gLogStream(nullptr); 1.42 +static string gReporterDumpFile; 1.43 +static string gExtraFile; 1.44 + 1.45 +static string kExtraDataExtension = ".extra"; 1.46 + 1.47 +void UIError(const string& message) 1.48 +{ 1.49 + string errorMessage; 1.50 + if (!gStrings[ST_CRASHREPORTERERROR].empty()) { 1.51 + char buf[2048]; 1.52 + UI_SNPRINTF(buf, 2048, 1.53 + gStrings[ST_CRASHREPORTERERROR].c_str(), 1.54 + message.c_str()); 1.55 + errorMessage = buf; 1.56 + } else { 1.57 + errorMessage = message; 1.58 + } 1.59 + 1.60 + UIError_impl(errorMessage); 1.61 +} 1.62 + 1.63 +static string Unescape(const string& str) 1.64 +{ 1.65 + string ret; 1.66 + for (string::const_iterator iter = str.begin(); 1.67 + iter != str.end(); 1.68 + iter++) { 1.69 + if (*iter == '\\') { 1.70 + iter++; 1.71 + if (*iter == '\\'){ 1.72 + ret.push_back('\\'); 1.73 + } else if (*iter == 'n') { 1.74 + ret.push_back('\n'); 1.75 + } else if (*iter == 't') { 1.76 + ret.push_back('\t'); 1.77 + } 1.78 + } else { 1.79 + ret.push_back(*iter); 1.80 + } 1.81 + } 1.82 + 1.83 + return ret; 1.84 +} 1.85 + 1.86 +static string Escape(const string& str) 1.87 +{ 1.88 + string ret; 1.89 + for (string::const_iterator iter = str.begin(); 1.90 + iter != str.end(); 1.91 + iter++) { 1.92 + if (*iter == '\\') { 1.93 + ret += "\\\\"; 1.94 + } else if (*iter == '\n') { 1.95 + ret += "\\n"; 1.96 + } else if (*iter == '\t') { 1.97 + ret += "\\t"; 1.98 + } else { 1.99 + ret.push_back(*iter); 1.100 + } 1.101 + } 1.102 + 1.103 + return ret; 1.104 +} 1.105 + 1.106 +bool ReadStrings(istream& in, StringTable& strings, bool unescape) 1.107 +{ 1.108 + string currentSection; 1.109 + while (!in.eof()) { 1.110 + string line; 1.111 + std::getline(in, line); 1.112 + int sep = line.find('='); 1.113 + if (sep >= 0) { 1.114 + string key, value; 1.115 + key = line.substr(0, sep); 1.116 + value = line.substr(sep + 1); 1.117 + if (unescape) 1.118 + value = Unescape(value); 1.119 + strings[key] = value; 1.120 + } 1.121 + } 1.122 + 1.123 + return true; 1.124 +} 1.125 + 1.126 +bool ReadStringsFromFile(const string& path, 1.127 + StringTable& strings, 1.128 + bool unescape) 1.129 +{ 1.130 + ifstream* f = UIOpenRead(path); 1.131 + bool success = false; 1.132 + if (f->is_open()) { 1.133 + success = ReadStrings(*f, strings, unescape); 1.134 + f->close(); 1.135 + } 1.136 + 1.137 + delete f; 1.138 + return success; 1.139 +} 1.140 + 1.141 +bool WriteStrings(ostream& out, 1.142 + const string& header, 1.143 + StringTable& strings, 1.144 + bool escape) 1.145 +{ 1.146 + out << "[" << header << "]" << std::endl; 1.147 + for (StringTable::iterator iter = strings.begin(); 1.148 + iter != strings.end(); 1.149 + iter++) { 1.150 + out << iter->first << "="; 1.151 + if (escape) 1.152 + out << Escape(iter->second); 1.153 + else 1.154 + out << iter->second; 1.155 + 1.156 + out << std::endl; 1.157 + } 1.158 + 1.159 + return true; 1.160 +} 1.161 + 1.162 +bool WriteStringsToFile(const string& path, 1.163 + const string& header, 1.164 + StringTable& strings, 1.165 + bool escape) 1.166 +{ 1.167 + ofstream* f = UIOpenWrite(path.c_str()); 1.168 + bool success = false; 1.169 + if (f->is_open()) { 1.170 + success = WriteStrings(*f, header, strings, escape); 1.171 + f->close(); 1.172 + } 1.173 + 1.174 + delete f; 1.175 + return success; 1.176 +} 1.177 + 1.178 +void LogMessage(const std::string& message) 1.179 +{ 1.180 + if (gLogStream.get()) { 1.181 + char date[64]; 1.182 + time_t tm; 1.183 + time(&tm); 1.184 + if (strftime(date, sizeof(date) - 1, "%c", localtime(&tm)) == 0) 1.185 + date[0] = '\0'; 1.186 + (*gLogStream) << "[" << date << "] " << message << std::endl; 1.187 + } 1.188 +} 1.189 + 1.190 +static void OpenLogFile() 1.191 +{ 1.192 + string logPath = gSettingsPath + UI_DIR_SEPARATOR + "submit.log"; 1.193 + gLogStream.reset(UIOpenWrite(logPath.c_str(), true)); 1.194 +} 1.195 + 1.196 +static bool ReadConfig() 1.197 +{ 1.198 + string iniPath; 1.199 + if (!UIGetIniPath(iniPath)) 1.200 + return false; 1.201 + 1.202 + if (!ReadStringsFromFile(iniPath, gStrings, true)) 1.203 + return false; 1.204 + 1.205 + // See if we have a string override file, if so process it 1.206 + char* overrideEnv = getenv("MOZ_CRASHREPORTER_STRINGS_OVERRIDE"); 1.207 + if (overrideEnv && *overrideEnv && UIFileExists(overrideEnv)) 1.208 + ReadStringsFromFile(overrideEnv, gStrings, true); 1.209 + 1.210 + return true; 1.211 +} 1.212 + 1.213 +static string GetExtraDataFilename(const string& dumpfile) 1.214 +{ 1.215 + string filename(dumpfile); 1.216 + int dot = filename.rfind('.'); 1.217 + if (dot < 0) 1.218 + return ""; 1.219 + 1.220 + filename.replace(dot, filename.length() - dot, kExtraDataExtension); 1.221 + return filename; 1.222 +} 1.223 + 1.224 +static string Basename(const string& file) 1.225 +{ 1.226 + int slashIndex = file.rfind(UI_DIR_SEPARATOR); 1.227 + if (slashIndex >= 0) 1.228 + return file.substr(slashIndex + 1); 1.229 + else 1.230 + return file; 1.231 +} 1.232 + 1.233 +static bool MoveCrashData(const string& toDir, 1.234 + string& dumpfile, 1.235 + string& extrafile) 1.236 +{ 1.237 + if (!UIEnsurePathExists(toDir)) { 1.238 + UIError(gStrings[ST_ERROR_CREATEDUMPDIR]); 1.239 + return false; 1.240 + } 1.241 + 1.242 + string newDump = toDir + UI_DIR_SEPARATOR + Basename(dumpfile); 1.243 + string newExtra = toDir + UI_DIR_SEPARATOR + Basename(extrafile); 1.244 + 1.245 + if (!UIMoveFile(dumpfile, newDump)) { 1.246 + UIError(gStrings[ST_ERROR_DUMPFILEMOVE]); 1.247 + return false; 1.248 + } 1.249 + 1.250 + if (!UIMoveFile(extrafile, newExtra)) { 1.251 + UIError(gStrings[ST_ERROR_EXTRAFILEMOVE]); 1.252 + return false; 1.253 + } 1.254 + 1.255 + dumpfile = newDump; 1.256 + extrafile = newExtra; 1.257 + 1.258 + return true; 1.259 +} 1.260 + 1.261 +static bool AddSubmittedReport(const string& serverResponse) 1.262 +{ 1.263 + StringTable responseItems; 1.264 + istringstream in(serverResponse); 1.265 + ReadStrings(in, responseItems, false); 1.266 + 1.267 + if (responseItems.find("StopSendingReportsFor") != responseItems.end()) { 1.268 + // server wants to tell us to stop sending reports for a certain version 1.269 + string reportPath = 1.270 + gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + 1.271 + responseItems["StopSendingReportsFor"]; 1.272 + 1.273 + ofstream* reportFile = UIOpenWrite(reportPath); 1.274 + if (reportFile->is_open()) { 1.275 + // don't really care about the contents 1.276 + *reportFile << 1 << "\n"; 1.277 + reportFile->close(); 1.278 + } 1.279 + delete reportFile; 1.280 + } 1.281 + 1.282 + if (responseItems.find("Discarded") != responseItems.end()) { 1.283 + // server discarded this report... save it so the user can resubmit it 1.284 + // manually 1.285 + return false; 1.286 + } 1.287 + 1.288 + if (responseItems.find("CrashID") == responseItems.end()) 1.289 + return false; 1.290 + 1.291 + string submittedDir = 1.292 + gSettingsPath + UI_DIR_SEPARATOR + "submitted"; 1.293 + if (!UIEnsurePathExists(submittedDir)) { 1.294 + return false; 1.295 + } 1.296 + 1.297 + string path = submittedDir + UI_DIR_SEPARATOR + 1.298 + responseItems["CrashID"] + ".txt"; 1.299 + 1.300 + ofstream* file = UIOpenWrite(path); 1.301 + if (!file->is_open()) { 1.302 + delete file; 1.303 + return false; 1.304 + } 1.305 + 1.306 + char buf[1024]; 1.307 + UI_SNPRINTF(buf, 1024, 1.308 + gStrings["CrashID"].c_str(), 1.309 + responseItems["CrashID"].c_str()); 1.310 + *file << buf << "\n"; 1.311 + 1.312 + if (responseItems.find("ViewURL") != responseItems.end()) { 1.313 + UI_SNPRINTF(buf, 1024, 1.314 + gStrings["CrashDetailsURL"].c_str(), 1.315 + responseItems["ViewURL"].c_str()); 1.316 + *file << buf << "\n"; 1.317 + } 1.318 + 1.319 + file->close(); 1.320 + delete file; 1.321 + 1.322 + return true; 1.323 +} 1.324 + 1.325 +void DeleteDump() 1.326 +{ 1.327 + const char* noDelete = getenv("MOZ_CRASHREPORTER_NO_DELETE_DUMP"); 1.328 + if (!noDelete || *noDelete == '\0') { 1.329 + if (!gReporterDumpFile.empty()) 1.330 + UIDeleteFile(gReporterDumpFile); 1.331 + if (!gExtraFile.empty()) 1.332 + UIDeleteFile(gExtraFile); 1.333 + } 1.334 +} 1.335 + 1.336 +void SendCompleted(bool success, const string& serverResponse) 1.337 +{ 1.338 + if (success) { 1.339 + if (AddSubmittedReport(serverResponse)) { 1.340 + DeleteDump(); 1.341 + } 1.342 + else { 1.343 + string directory = gReporterDumpFile; 1.344 + int slashpos = directory.find_last_of("/\\"); 1.345 + if (slashpos < 2) 1.346 + return; 1.347 + directory.resize(slashpos); 1.348 + UIPruneSavedDumps(directory); 1.349 + } 1.350 + } 1.351 +} 1.352 + 1.353 +bool ShouldEnableSending() 1.354 +{ 1.355 + srand(time(0)); 1.356 + return ((rand() % 100) < MOZ_CRASHREPORTER_ENABLE_PERCENT); 1.357 +} 1.358 + 1.359 +} // namespace CrashReporter 1.360 + 1.361 +using namespace CrashReporter; 1.362 + 1.363 +void RewriteStrings(StringTable& queryParameters) 1.364 +{ 1.365 + // rewrite some UI strings with the values from the query parameters 1.366 + string product = queryParameters["ProductName"]; 1.367 + string vendor = queryParameters["Vendor"]; 1.368 + if (vendor.empty()) { 1.369 + // Assume Mozilla if no vendor is specified 1.370 + vendor = "Mozilla"; 1.371 + } 1.372 + 1.373 + char buf[4096]; 1.374 + UI_SNPRINTF(buf, sizeof(buf), 1.375 + gStrings[ST_CRASHREPORTERVENDORTITLE].c_str(), 1.376 + vendor.c_str()); 1.377 + gStrings[ST_CRASHREPORTERTITLE] = buf; 1.378 + 1.379 + 1.380 + string str = gStrings[ST_CRASHREPORTERPRODUCTERROR]; 1.381 + // Only do the replacement here if the string has two 1.382 + // format specifiers to start. Otherwise 1.383 + // we assume it has the product name hardcoded. 1.384 + string::size_type pos = str.find("%s"); 1.385 + if (pos != string::npos) 1.386 + pos = str.find("%s", pos+2); 1.387 + if (pos != string::npos) { 1.388 + // Leave a format specifier for UIError to fill in 1.389 + UI_SNPRINTF(buf, sizeof(buf), 1.390 + gStrings[ST_CRASHREPORTERPRODUCTERROR].c_str(), 1.391 + product.c_str(), 1.392 + "%s"); 1.393 + gStrings[ST_CRASHREPORTERERROR] = buf; 1.394 + } 1.395 + else { 1.396 + // product name is hardcoded 1.397 + gStrings[ST_CRASHREPORTERERROR] = str; 1.398 + } 1.399 + 1.400 + UI_SNPRINTF(buf, sizeof(buf), 1.401 + gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(), 1.402 + product.c_str()); 1.403 + gStrings[ST_CRASHREPORTERDESCRIPTION] = buf; 1.404 + 1.405 + UI_SNPRINTF(buf, sizeof(buf), 1.406 + gStrings[ST_CHECKSUBMIT].c_str(), 1.407 + vendor.c_str()); 1.408 + gStrings[ST_CHECKSUBMIT] = buf; 1.409 + 1.410 + UI_SNPRINTF(buf, sizeof(buf), 1.411 + gStrings[ST_CHECKEMAIL].c_str(), 1.412 + vendor.c_str()); 1.413 + gStrings[ST_CHECKEMAIL] = buf; 1.414 + 1.415 + UI_SNPRINTF(buf, sizeof(buf), 1.416 + gStrings[ST_RESTART].c_str(), 1.417 + product.c_str()); 1.418 + gStrings[ST_RESTART] = buf; 1.419 + 1.420 + UI_SNPRINTF(buf, sizeof(buf), 1.421 + gStrings[ST_QUIT].c_str(), 1.422 + product.c_str()); 1.423 + gStrings[ST_QUIT] = buf; 1.424 + 1.425 + UI_SNPRINTF(buf, sizeof(buf), 1.426 + gStrings[ST_ERROR_ENDOFLIFE].c_str(), 1.427 + product.c_str()); 1.428 + gStrings[ST_ERROR_ENDOFLIFE] = buf; 1.429 +} 1.430 + 1.431 +bool CheckEndOfLifed(string version) 1.432 +{ 1.433 + string reportPath = 1.434 + gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + version; 1.435 + return UIFileExists(reportPath); 1.436 +} 1.437 + 1.438 +int main(int argc, char** argv) 1.439 +{ 1.440 + gArgc = argc; 1.441 + gArgv = argv; 1.442 + 1.443 + if (!ReadConfig()) { 1.444 + UIError("Couldn't read configuration."); 1.445 + return 0; 1.446 + } 1.447 + 1.448 + if (!UIInit()) 1.449 + return 0; 1.450 + 1.451 + if (argc > 1) { 1.452 + gReporterDumpFile = argv[1]; 1.453 + } 1.454 + 1.455 + if (gReporterDumpFile.empty()) { 1.456 + // no dump file specified, run the default UI 1.457 + UIShowDefaultUI(); 1.458 + } else { 1.459 + gExtraFile = GetExtraDataFilename(gReporterDumpFile); 1.460 + if (gExtraFile.empty()) { 1.461 + UIError(gStrings[ST_ERROR_BADARGUMENTS]); 1.462 + return 0; 1.463 + } 1.464 + 1.465 + if (!UIFileExists(gExtraFile)) { 1.466 + UIError(gStrings[ST_ERROR_EXTRAFILEEXISTS]); 1.467 + return 0; 1.468 + } 1.469 + 1.470 + StringTable queryParameters; 1.471 + if (!ReadStringsFromFile(gExtraFile, queryParameters, true)) { 1.472 + UIError(gStrings[ST_ERROR_EXTRAFILEREAD]); 1.473 + return 0; 1.474 + } 1.475 + 1.476 + if (queryParameters.find("ProductName") == queryParameters.end()) { 1.477 + UIError(gStrings[ST_ERROR_NOPRODUCTNAME]); 1.478 + return 0; 1.479 + } 1.480 + 1.481 + // There is enough information in the extra file to rewrite strings 1.482 + // to be product specific 1.483 + RewriteStrings(queryParameters); 1.484 + 1.485 + if (queryParameters.find("ServerURL") == queryParameters.end()) { 1.486 + UIError(gStrings[ST_ERROR_NOSERVERURL]); 1.487 + return 0; 1.488 + } 1.489 + 1.490 + // Hopefully the settings path exists in the environment. Try that before 1.491 + // asking the platform-specific code to guess. 1.492 +#ifdef XP_WIN32 1.493 + static const wchar_t kDataDirKey[] = L"MOZ_CRASHREPORTER_DATA_DIRECTORY"; 1.494 + const wchar_t *settingsPath = _wgetenv(kDataDirKey); 1.495 + if (settingsPath && *settingsPath) { 1.496 + gSettingsPath = WideToUTF8(settingsPath); 1.497 + } 1.498 +#else 1.499 + static const char kDataDirKey[] = "MOZ_CRASHREPORTER_DATA_DIRECTORY"; 1.500 + const char *settingsPath = getenv(kDataDirKey); 1.501 + if (settingsPath && *settingsPath) { 1.502 + gSettingsPath = settingsPath; 1.503 + } 1.504 +#endif 1.505 + else { 1.506 + string product = queryParameters["ProductName"]; 1.507 + string vendor = queryParameters["Vendor"]; 1.508 + if (!UIGetSettingsPath(vendor, product, gSettingsPath)) { 1.509 + gSettingsPath.clear(); 1.510 + } 1.511 + } 1.512 + 1.513 + if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) { 1.514 + UIError(gStrings[ST_ERROR_NOSETTINGSPATH]); 1.515 + return 0; 1.516 + } 1.517 + 1.518 + OpenLogFile(); 1.519 + 1.520 + if (!UIFileExists(gReporterDumpFile)) { 1.521 + UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]); 1.522 + return 0; 1.523 + } 1.524 + 1.525 + string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending"; 1.526 + if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile)) { 1.527 + return 0; 1.528 + } 1.529 + 1.530 + string sendURL = queryParameters["ServerURL"]; 1.531 + // we don't need to actually send this 1.532 + queryParameters.erase("ServerURL"); 1.533 + 1.534 + queryParameters["Throttleable"] = "1"; 1.535 + 1.536 + // re-set XUL_APP_FILE for xulrunner wrapped apps 1.537 + const char *appfile = getenv("MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE"); 1.538 + if (appfile && *appfile) { 1.539 + const char prefix[] = "XUL_APP_FILE="; 1.540 + char *env = (char*) malloc(strlen(appfile) + strlen(prefix) + 1); 1.541 + if (!env) { 1.542 + UIError("Out of memory"); 1.543 + return 0; 1.544 + } 1.545 + strcpy(env, prefix); 1.546 + strcat(env, appfile); 1.547 + putenv(env); 1.548 + free(env); 1.549 + } 1.550 + 1.551 + vector<string> restartArgs; 1.552 + 1.553 + ostringstream paramName; 1.554 + int i = 0; 1.555 + paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; 1.556 + const char *param = getenv(paramName.str().c_str()); 1.557 + while (param && *param) { 1.558 + restartArgs.push_back(param); 1.559 + 1.560 + paramName.str(""); 1.561 + paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; 1.562 + param = getenv(paramName.str().c_str()); 1.563 + }; 1.564 + 1.565 + // allow override of the server url via environment variable 1.566 + //XXX: remove this in the far future when our robot 1.567 + // masters force everyone to use XULRunner 1.568 + char* urlEnv = getenv("MOZ_CRASHREPORTER_URL"); 1.569 + if (urlEnv && *urlEnv) { 1.570 + sendURL = urlEnv; 1.571 + } 1.572 + 1.573 + // see if this version has been end-of-lifed 1.574 + if (queryParameters.find("Version") != queryParameters.end() && 1.575 + CheckEndOfLifed(queryParameters["Version"])) { 1.576 + UIError(gStrings[ST_ERROR_ENDOFLIFE]); 1.577 + DeleteDump(); 1.578 + return 0; 1.579 + } 1.580 + 1.581 + if (!UIShowCrashUI(gReporterDumpFile, queryParameters, sendURL, restartArgs)) 1.582 + DeleteDump(); 1.583 + } 1.584 + 1.585 + UIShutdown(); 1.586 + 1.587 + return 0; 1.588 +} 1.589 + 1.590 +#if defined(XP_WIN) && !defined(__GNUC__) 1.591 +#include <windows.h> 1.592 + 1.593 +// We need WinMain in order to not be a console app. This function is unused 1.594 +// if we are a console application. 1.595 +int WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR args, int ) 1.596 +{ 1.597 + // Remove everything except close window from the context menu 1.598 + { 1.599 + HKEY hkApp; 1.600 + RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", 0, 1.601 + nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, 1.602 + &hkApp, nullptr); 1.603 + RegCloseKey(hkApp); 1.604 + if (RegCreateKeyExW(HKEY_CURRENT_USER, 1.605 + L"Software\\Classes\\Applications\\crashreporter.exe", 1.606 + 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, 1.607 + nullptr, &hkApp, nullptr) == ERROR_SUCCESS) { 1.608 + RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0); 1.609 + RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0); 1.610 + RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0); 1.611 + RegCloseKey(hkApp); 1.612 + } 1.613 + } 1.614 + 1.615 + char** argv = static_cast<char**>(malloc(__argc * sizeof(char*))); 1.616 + for (int i = 0; i < __argc; i++) { 1.617 + argv[i] = strdup(WideToUTF8(__wargv[i]).c_str()); 1.618 + } 1.619 + 1.620 + // Do the real work. 1.621 + return main(__argc, argv); 1.622 +} 1.623 +#endif