Index: ps/trunk/source/lib/sysdep/os/win/wseh.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wseh.cpp (revision 27072) +++ ps/trunk/source/lib/sysdep/os/win/wseh.cpp (revision 27073) @@ -1,390 +1,390 @@ /* Copyright (C) 2022 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Structured Exception Handling support */ #include "precompiled.h" #include "lib/sysdep/os/win/wseh.h" #include "lib/byte_order.h" // FOURCC #include "lib/utf8.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/os/win/wdbg_sym.h" // wdbg_sym_WriteMinidump #include // __security_init_cookie #define NEED_COOKIE_INIT // note: several excellent references are pointed to by comments below. //----------------------------------------------------------------------------- // analyze an exception (determine description and locus) // VC++ exception handling internals. // see http://www.codeproject.com/cpp/exceptionhandler.asp struct XTypeInfo { DWORD _; const std::type_info* ti; // .. }; struct XTypeInfoArray { DWORD count; const XTypeInfo* types[1]; }; struct XInfo { DWORD _[3]; const XTypeInfoArray* array; }; // does the given SEH exception look like a C++ exception? // (compiler-specific). static bool IsCppException(const EXCEPTION_RECORD* er) { #if MSC_VERSION // notes: // - value of multibyte character constants (e.g. 'msc') aren't // specified by C++, so use FOURCC instead. // - "MS C" compiler is the only interpretation of this magic value that // makes sense, so it is apparently stored in big-endian format. if(er->ExceptionCode != FOURCC_BE(0xe0, 'm','s','c')) return false; // exception info = (magic, &thrown_Cpp_object, &XInfo) if(er->NumberParameters != 3) return false; // MAGIC_NUMBER1 from exsup.inc if(er->ExceptionInformation[0] != 0x19930520) return false; return true; #else # error "port" #endif } /** * @param er An exception record for which IsCppException returned true. * @param description * @param maxChars **/ static const wchar_t* GetCppExceptionDescription(const EXCEPTION_RECORD* er, wchar_t* description, size_t maxChars) { // see above for interpretation const ULONG_PTR* const ei = er->ExceptionInformation; // note: we can't share a __try below - the failure of // one attempt must not abort the others. // get std::type_info char type_buf[100] = {'\0'}; const char* type_name = type_buf; __try { const XInfo* xi = (XInfo*)ei[2]; const XTypeInfoArray* xta = xi->array; const XTypeInfo* xti = xta->types[0]; const std::type_info* ti = xti->ti; // strip "class " from start of string (clutter) strcpy_s(type_buf, ARRAY_SIZE(type_buf), ti->name()); if(!strncmp(type_buf, "class ", 6)) type_name += 6; } __except(EXCEPTION_EXECUTE_HANDLER) { } // std::exception.what() char what[160] = {'\0'}; __try { std::exception* e = (std::exception*)ei[1]; strcpy_s(what, ARRAY_SIZE(what), e->what()); } __except(EXCEPTION_EXECUTE_HANDLER) { } // format the info we got (if both are empty, then something is seriously // wrong; it's better to show empty strings than returning 0 to have our // caller display the SEH info) swprintf_s(description, maxChars, L"%hs(\"%hs\")", type_name, what); return description; } static const wchar_t* GetSehExceptionDescription(const EXCEPTION_RECORD* er, wchar_t* description, size_t maxChars) { const DWORD code = er->ExceptionCode; const ULONG_PTR* ei = er->ExceptionInformation; // rationale: we don't use FormatMessage because it is unclear whether // NTDLL's symbol table will always include English-language strings // (we don't want to receive crashlogs in foreign gobbledygook). // it also adds unwanted formatting (e.g. {EXCEPTION} and trailing .). switch(code) { case EXCEPTION_ACCESS_VIOLATION: { // special case: display type and address. const wchar_t* accessType = (ei[0])? L"writing" : L"reading"; const ULONG_PTR address = ei[1]; swprintf_s(description, maxChars, L"Access violation %ls 0x%08X", accessType, address); return description; } case EXCEPTION_DATATYPE_MISALIGNMENT: return L"Datatype misalignment"; case EXCEPTION_BREAKPOINT: return L"Breakpoint"; case EXCEPTION_SINGLE_STEP: return L"Single step"; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return L"Array bounds exceeded"; case EXCEPTION_FLT_DENORMAL_OPERAND: return L"FPU denormal operand"; case EXCEPTION_FLT_DIVIDE_BY_ZERO: return L"FPU divide by zero"; case EXCEPTION_FLT_INEXACT_RESULT: return L"FPU inexact result"; case EXCEPTION_FLT_INVALID_OPERATION: return L"FPU invalid operation"; case EXCEPTION_FLT_OVERFLOW: return L"FPU overflow"; case EXCEPTION_FLT_STACK_CHECK: return L"FPU stack check"; case EXCEPTION_FLT_UNDERFLOW: return L"FPU underflow"; case EXCEPTION_INT_DIVIDE_BY_ZERO: return L"Integer divide by zero"; case EXCEPTION_INT_OVERFLOW: return L"Integer overflow"; case EXCEPTION_PRIV_INSTRUCTION: return L"Privileged instruction"; case EXCEPTION_IN_PAGE_ERROR: return L"In page error"; case EXCEPTION_ILLEGAL_INSTRUCTION: return L"Illegal instruction"; case EXCEPTION_NONCONTINUABLE_EXCEPTION: return L"Noncontinuable exception"; case EXCEPTION_STACK_OVERFLOW: return L"Stack overflow"; case EXCEPTION_INVALID_DISPOSITION: return L"Invalid disposition"; case EXCEPTION_GUARD_PAGE: return L"Guard page"; case EXCEPTION_INVALID_HANDLE: return L"Invalid handle"; } // anything else => unknown; display its exception code. // we don't punt to GetExceptionDescription because anything // we get called for will actually be a SEH exception. swprintf_s(description, maxChars, L"Unknown (0x%08X)", code); return description; } /** * @return a description of the exception type and cause (in English). **/ static const wchar_t* GetExceptionDescription(const EXCEPTION_POINTERS* ep, wchar_t* description, size_t maxChars) { const EXCEPTION_RECORD* const er = ep->ExceptionRecord; if(IsCppException(er)) return GetCppExceptionDescription(er, description, maxChars); else return GetSehExceptionDescription(er, description, maxChars); } // return location at which the exception occurred. // params: see debug_ResolveSymbol. static void GetExceptionLocus(EXCEPTION_POINTERS* ep, wchar_t* file, int* line, wchar_t* func) { // HACK: provides no useful information - ExceptionAddress always // points to kernel32!RaiseException. we use debug_GetCaller to // determine the real location. const wchar_t* const lastFuncToSkip = L"RaiseException"; void* func_addr = debug_GetCaller(ep->ContextRecord, lastFuncToSkip); (void)debug_ResolveSymbol(func_addr, func, file, line); } //----------------------------------------------------------------------------- // exception filter // called when an exception is detected (see below); provides detailed // debugging information and exits. // // note: keep memory allocs and locking to an absolute minimum, because // they may deadlock the process! long __stdcall wseh_ExceptionFilter(struct _EXCEPTION_POINTERS* ep) { // OutputDebugString raises an exception, which OUGHT to be swallowed // by WaitForDebugEvent but sometimes isn't. if we see it, ignore it. if(ep->ExceptionRecord->ExceptionCode == 0x40010006) // DBG_PRINTEXCEPTION_C return EXCEPTION_CONTINUE_EXECUTION; // if run in a debugger, let it handle exceptions (tends to be more // convenient since it can bring up the crash location) if(IsDebuggerPresent()) return EXCEPTION_CONTINUE_SEARCH; // make sure we don't recurse infinitely if this function raises an // SEH exception. (we may only have the guard page's 4 KB worth of // stack space if the exception is EXCEPTION_STACK_OVERFLOW) static intptr_t nestingLevel = 0; cpu_AtomicAdd(&nestingLevel, 1); if(nestingLevel >= 3) return EXCEPTION_CONTINUE_SEARCH; // someone is already holding the dbghelp lock - this is bad. // we'll report this problem first and then try to display the // exception info regardless (maybe dbghelp won't blow up). if(wutil_IsLocked(WDBG_SYM_CS) == 1) DEBUG_DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock.."); // a dump file is essential for debugging, so write it before // anything else goes wrong (especially before showing the error // dialog because the user could choose to exit immediately) wdbg_sym_WriteMinidump(ep); // extract details from ExceptionRecord. wchar_t descriptionBuf[150]; const wchar_t* description = GetExceptionDescription(ep, descriptionBuf, ARRAY_SIZE(descriptionBuf)); wchar_t file[DEBUG_FILE_CHARS] = {0}; int line = 0; wchar_t func[DEBUG_SYMBOL_CHARS] = {0}; GetExceptionLocus(ep, file, &line, func); wchar_t message[600]; const wchar_t* messageFormat = L"Much to our regret we must report the program has encountered an error.\r\n" L"\r\n" - L"Please let us know at http://trac.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n" + L"Please let us know at https://trac.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n" L"You may find paths to these files at https://trac.wildfiregames.com/wiki/GameDataPaths \r\n" L"\r\n" L"Details: unhandled exception (%ls)\r\n"; swprintf_s(message, ARRAY_SIZE(message), messageFormat, description); size_t flags = 0; if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) flags = DE_NO_CONTINUE; const wchar_t* const lastFuncToSkip = WIDEN(STRINGIZE(DECORATED_NAME(wseh_ExceptionFilter))); ErrorReaction er = debug_DisplayError(message, flags, ep->ContextRecord, lastFuncToSkip, file,line,utf8_from_wstring(func).c_str(), 0); ENSURE(er == ER_CONTINUE); // nothing else possible // invoke the Win32 default handler - it calls ExitProcess for // most exception types. return EXCEPTION_CONTINUE_SEARCH; } //----------------------------------------------------------------------------- // install SEH exception handler /* rationale: we want to replace the OS "program error" dialog box because it is not all too helpful in debugging. to that end, there are 5 ways to make sure unhandled SEH exceptions are caught: - via WaitForDebugEvent; the app is run from a separate debugger process. the exception is in another address space, so we'd have to deal with that and basically implement a full-featured debugger - overkill. - by wrapping all threads (their handler chains are in TLS) in __try. this can be done with the cooperation of wpthread, but threads not under our control aren't covered. - with a vectored exception handler. this works across threads, but it's only available on WinXP (unacceptable). since it is called before __try blocks, we would receive expected/legitimate exceptions. (see http://msdn.microsoft.com/msdnmag/issues/01/09/hood/default.aspx) - by setting the per-process unhandled exception filter. as above, this works across threads and is at least portable across Win32. unfortunately, some Win32 DLLs appear to register their own handlers, so this isn't reliable. - by hooking the exception dispatcher. this isn't future-proof. note that the vectored and unhandled-exception filters aren't called when the process is being debugged (messing with the PEB flag doesn't help; root cause is the Win32 KiUserExceptionDispatcher implementation). however, this is fine since the IDE's debugger is more helpful than our dialog (it is able to jump directly to the offending code). wrapping all threads in a __try appears to be the best choice. unfortunately, we cannot retroactively install an SEH handler: the OS ensures SEH chain nodes are on the thread's stack (as defined by NT_TIB) in ascending order. (see http://www.microsoft.com/msj/0197/exception/exception.aspx) the handler would also have to be marked via .safeseh, but that is doable (see http://blogs.msdn.com/greggm/archive/2004/07/22/191544.aspx) consequently, we'll have to run within a __try; if the init code is to be covered, this must happen within the program entry point. note: since C++ exceptions are implemented via SEH, we can also catch those here; it's nicer than a global try{} and avoids duplicating this code. we can still get at the C++ information (std::exception.what()) by examining the internal exception data structures. these are compiler-specific, but haven't changed from VC5-VC7.1. alternatively, _set_se_translator could to translate all SEH exceptions to C++ classes. this way is more reliable/documented, but has several drawbacks: - it wouldn't work at all in C programs, - a new fat exception class would have to be created to hold the SEH exception information (e.g. CONTEXT for a stack trace), and - this information would not be available for C++ exceptions. (see http://blogs.msdn.com/cbrumme/archive/2003/10/01/51524.aspx) */ #include "lib/utf8.h" EXTERN_C int wmainCRTStartup(); static int CallStartupWithinTryBlock() { int ret; __try { ret = wmainCRTStartup(); } __except(wseh_ExceptionFilter(GetExceptionInformation())) { ret = -1; } return ret; } EXTERN_C int wseh_EntryPoint() { #ifdef NEED_COOKIE_INIT // 2006-02-16 workaround for R6035 on VC8: // // SEH code compiled with /GS pushes a "security cookie" onto the // stack. since we're called before CRT init, the cookie won't have // been initialized yet, which would cause the CRT to FatalAppExit. // to solve this, we must call __security_init_cookie before any // hidden compiler-generated SEH registration code runs, // which means the __try block must be moved into a helper function. // // NB: wseh_EntryPoint() must not contain local string buffers, // either - /GS would install a cookie here as well (same problem). // // see http://msdn2.microsoft.com/en-US/library/ms235603.aspx __security_init_cookie(); #endif return CallStartupWithinTryBlock(); } Index: ps/trunk/source/tools/dist/0ad.nsi =================================================================== --- ps/trunk/source/tools/dist/0ad.nsi (revision 27072) +++ ps/trunk/source/tools/dist/0ad.nsi (revision 27073) @@ -1,300 +1,300 @@ ; To generate the installer (on Linux): ; Do an 'svn export' into a directory called e.g. "export-win32" ; makensis -nocd -dcheckoutpath=export-win32 -drevision=1234 -dprefix=0ad-0.1.2-alpha export-win32/source/tools/dist/0ad.nsi SetCompressor /SOLID LZMA !include "MUI2.nsh" !include "LogicLib.nsh" !include "source/tools/dist/FileAssociation.nsh" ;Control whether to include source code (and component selection screen) ;Off by default, uncomment or pass directly to use. !ifndef INCLUDE_SOURCE ;!define INCLUDE_SOURCE 1 !endif ;-------------------------------- ;General ;Properly display all languages (Installer will not work on Windows 95, 98 or ME!) Unicode true ;Name and file Name "0 A.D." OutFile "${PREFIX}-win32.exe" ;Default installation folder InstallDir "$LOCALAPPDATA\0 A.D. alpha" ; NOTE: we can't use folder names ending in "." because they seemingly get stripped ;Get installation folder from registry if available InstallDirRegKey HKCU "Software\0 A.D." "" RequestExecutionLevel user ;-------------------------------- ;Variables Var StartMenuFolder ;-------------------------------- ;Interface Settings !define MUI_WELCOMEFINISHPAGE_BITMAP ${CHECKOUTPATH}\build\resources\installer.bmp !define MUI_ICON ${CHECKOUTPATH}\build\resources\ps.ico !define MUI_ABORTWARNING !define MUI_LANGDLL_ALLLANGUAGES ;-------------------------------- ;Language Selection Dialog Settings ;Remember the installer language !define MUI_LANGDLL_REGISTRY_ROOT "HKCU" !define MUI_LANGDLL_REGISTRY_KEY "Software\0 A.D." !define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language" ;-------------------------------- ;Pages !insertmacro MUI_PAGE_WELCOME !ifdef INCLUDE_SOURCE !insertmacro MUI_PAGE_COMPONENTS !endif !insertmacro MUI_PAGE_DIRECTORY ;Start Menu Folder Page Configuration !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\0 A.D." !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" !define MUI_STARTMENUPAGE_DEFAULTFOLDER "0 A.D. alpha" !insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder !insertmacro MUI_PAGE_INSTFILES !define MUI_FINISHPAGE_SHOWREADME "" !define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED !define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Desktop Shortcut" !define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopLink !define MUI_FINISHPAGE_RUN $INSTDIR\binaries\system\pyrogenesis.exe !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES ;-------------------------------- ;Languages ;Keep in sync with build-archives.sh. !insertmacro MUI_LANGUAGE "English" ; The first language is the default language !insertmacro MUI_LANGUAGE "Asturian" !insertmacro MUI_LANGUAGE "Basque" !insertmacro MUI_LANGUAGE "Catalan" !insertmacro MUI_LANGUAGE "Czech" !insertmacro MUI_LANGUAGE "Dutch" !insertmacro MUI_LANGUAGE "Finnish" !insertmacro MUI_LANGUAGE "French" !insertmacro MUI_LANGUAGE "German" !insertmacro MUI_LANGUAGE "Greek" !insertmacro MUI_LANGUAGE "Hungarian" !insertmacro MUI_LANGUAGE "Indonesian" !insertmacro MUI_LANGUAGE "Italian" !insertmacro MUI_LANGUAGE "Polish" !insertmacro MUI_LANGUAGE "PortugueseBR" !insertmacro MUI_LANGUAGE "Russian" !insertmacro MUI_LANGUAGE "ScotsGaelic" !insertmacro MUI_LANGUAGE "Slovak" !insertmacro MUI_LANGUAGE "Spanish" !insertmacro MUI_LANGUAGE "Swedish" !insertmacro MUI_LANGUAGE "Turkish" !insertmacro MUI_LANGUAGE "Ukrainian" ;-------------------------------- ;Installer Sections Section "!Game and data files" GameSection SetOutPath "$INSTDIR" File "${CHECKOUTPATH}\*.txt" File "${CHECKOUTPATH}\source\tools\openlogsfolder\*.*" ; Binaries: exclude debug DLLs and related files SetOutPath "$INSTDIR\binaries\data" File /r /x "public" /x "mod" /x "tests" /x "_test.*" /x "dev.cfg" "${CHECKOUTPATH}\binaries\data\" ; Warning: libraries that end in 'd' need to be added explicitly. ; There are currently none. SetOutPath "$INSTDIR\binaries\system" File /r /x "*d.dll" /x "*_dbg*" /x "*debug*" "${CHECKOUTPATH}\binaries\system\*.dll" File /r /x "*d.pdb" /x "*_dbg*" /x "*debug*" /x "test" "${CHECKOUTPATH}\binaries\system\*.pdb" File /r /x "*_dbg*" /x "*debug*" /x "test" "${CHECKOUTPATH}\binaries\system\*.exe" File /r "${CHECKOUTPATH}\binaries\system\*.bat" File /r "${CHECKOUTPATH}\binaries\system\*.txt" ; Copy logs for writable root - SetOutPath "$INSTDIR\binaries\logs" + SetOutPath "$INSTDIR\binaries" File /r "${CHECKOUTPATH}\binaries\logs" !ifdef ARCHIVE_PATH SetOutPath "$INSTDIR\binaries\data\mods\" File /r "${ARCHIVE_PATH}" !else SetOutPath "$INSTDIR\binaries\data\mods\public" File "${CHECKOUTPATH}\binaries\data\mods\public\public.zip" File "${CHECKOUTPATH}\binaries\data\mods\public\mod.json" SetOutPath "$INSTDIR\binaries\data\mods\mod" File "${CHECKOUTPATH}\binaries\data\mods\mod\mod.zip" !endif ; Create shortcuts in the root installation folder. ; Keep synched with the start menu shortcuts. SetOutPath "$INSTDIR" CreateShortCut "$INSTDIR\0 A.D..lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" "" CreateShortCut "$INSTDIR\Map editor.lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" "-editor" "$INSTDIR\binaries\data\tools\atlas\icons\ScenarioEditor.ico" - WriteINIStr "$INSTDIR\Web site.url" "InternetShortcut" "URL" "http://play0ad.com/" + WriteINIStr "$INSTDIR\Web site.url" "InternetShortcut" "URL" "https://play0ad.com/" ;Store installation folder WriteRegStr SHCTX "Software\0 A.D." "" $INSTDIR ;Create uninstaller WriteUninstaller "$INSTDIR\Uninstall.exe" ;Add uninstall information WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "DisplayName" "0 A.D." WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "DisplayVersion" "r${REVISION}-alpha" WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "VersionMajor" 0 WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "VersionMinor" ${REVISION} WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "Publisher" "Wildfire Games" WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "DisplayIcon" "$\"$INSTDIR\binaries\system\pyrogenesis.exe$\"" WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "InstallLocation" "$\"$INSTDIR$\"" WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "UninstallString" "$\"$INSTDIR\Uninstall.exe$\"" WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "QuietUninstallString" "$\"$INSTDIR\Uninstall.exe$\" /S" - WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "URLInfoAbout" "http://play0ad.com" + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "URLInfoAbout" "https://play0ad.com" WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "NoModify" 1 WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "NoRepair" 1 !insertmacro MUI_STARTMENU_WRITE_BEGIN Application ;Create shortcuts CreateDirectory "$SMPROGRAMS\$StartMenuFolder" SetOutPath "$INSTDIR\binaries\system" ;Set working directory of shortcuts CreateShortCut "$SMPROGRAMS\$StartMenuFolder\0 A.D..lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" "" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Map editor.lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" "-editor" "$INSTDIR\binaries\data\tools\atlas\icons\ScenarioEditor.ico" SetOutPath "$INSTDIR" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Open logs folder.lnk" "$INSTDIR\OpenLogsFolder.bat" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe" - WriteINIStr "$SMPROGRAMS\$StartMenuFolder\Web site.url" "InternetShortcut" "URL" "http://play0ad.com/" + WriteINIStr "$SMPROGRAMS\$StartMenuFolder\Web site.url" "InternetShortcut" "URL" "https://play0ad.com/" !insertmacro MUI_STARTMENU_WRITE_END ;Register .pyromod file association ${registerExtension} "$INSTDIR\binaries\system\pyrogenesis.exe" ".pyromod" "Pyrogenesis mod" SectionEnd !ifdef INCLUDE_SOURCE Section /o "Source code" SourceSection SetOutPath "$INSTDIR" File /r "${CHECKOUTPATH}\source\" File /r "${CHECKOUTPATH}\docs\" File /r "${CHECKOUTPATH}\build" File /r "${CHECKOUTPATH}\libraries" SectionEnd !endif ;-------------------------------- ;Installer Functions Function .onInit !insertmacro MUI_LANGDLL_DISPLAY ReadRegStr $R0 SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." "UninstallString" StrCmp $R0 "" done MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \ "0 A.D. is already installed.$\n$\nClick $\"OK$\" to remove the previous version, or $\"Cancel$\" to stop this installation." \ IDOK uninst Abort ;Run the uninstaller uninst: ClearErrors ExecWait '$R0 _?=$INSTDIR' ;Do not copy the uninstaller to a temp file done: FunctionEnd Function CreateDesktopLink CreateShortCut "$DESKTOP\0 A.D..lnk" "$INSTDIR\binaries\system\pyrogenesis.exe" "" FunctionEnd ;-------------------------------- ;Descriptions !ifdef INCLUDE_SOURCE ;Assign descriptions to sections !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${GameSection} "0 A.D. game executable and data." !insertmacro MUI_DESCRIPTION_TEXT ${SourceSection} "Source code and build tools." !insertmacro MUI_FUNCTION_DESCRIPTION_END !endif ;-------------------------------- ;Uninstaller Section Section "Uninstall" RMDir /r "$INSTDIR\binaries" !ifdef INCLUDE_SOURCE RMDir /r "$INSTDIR\source" RMDir /r "$INSTDIR\docs" RMDir /r "$INSTDIR\build" RMDir /r "$INSTDIR\libraries" !endif Delete "$INSTDIR\*.txt" Delete "$INSTDIR\*.bat" Delete "$INSTDIR\OpenLogsFolder.vbs" Delete "$INSTDIR\Map editor.lnk" Delete "$INSTDIR\0 A.D..lnk" Delete "$INSTDIR\Web site.url" Delete /REBOOTOK "$INSTDIR\Uninstall.exe" RMDir /REBOOTOK "$INSTDIR" RMDir /r "$LOCALAPPDATA\0ad\cache" RMDir /r "$LOCALAPPDATA\0ad\logs" ; leave the other directories (screenshots, config files, etc) Delete "$DESKTOP\0 A.D..lnk" !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder Delete "$SMPROGRAMS\$StartMenuFolder\Open logs folder.lnk" Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" Delete "$SMPROGRAMS\$StartMenuFolder\Map editor.lnk" Delete "$SMPROGRAMS\$StartMenuFolder\0 A.D..lnk" Delete "$SMPROGRAMS\$StartMenuFolder\Web site.url" RMDir "$SMPROGRAMS\$StartMenuFolder" DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\0 A.D." DeleteRegKey /ifempty SHCTX "Software\0 A.D." ;Unregister .pyromod file association ${unregisterExtension} ".pyromod" "Pyrogenesis mod" SectionEnd ;-------------------------------- ;Uninstaller Functions Function un.onInit !insertmacro MUI_UNGETLANGUAGE FunctionEnd