diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 061cc965c1..0f27246e28 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -11,6 +11,9 @@ Explanation of the issue... 1. 2. +**Dump file** +If you have a dump file, zip it and drag&drop it here. + **Screenshots / Video:** Drag / drop screenshots here. Use https://vid.me to upload video. diff --git a/CMakeLists.txt b/CMakeLists.txt index caed4dda44..c087e91808 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,6 +143,16 @@ if (STATIC) endif (WIN32) endif () +option(WITH_BREAKPAD "Enable breakpad") +if (WITH_BREAKPAD) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_BREAKPAD") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BREAKPAD") + set(BREAKPAD_DIR "/home/janisozaur/workspace/breakpad/src") + set(BREAKPAD_INCLUDE_DIR "${BREAKPAD_DIR}/src") + set(BREAKPAD_LIBRARY_DIR "${BREAKPAD_DIR}/src/client/linux") + set(BREAKPAD_LIBS breakpad_client pthread) +endif (WITH_BREAKPAD) + # find and include SDL2 PKG_CHECK_MODULES(SDL2 REQUIRED sdl2 SDL2_ttf) if (STATIC) @@ -180,9 +190,9 @@ if (UNIX) set(DLLIB dl) endif (UNIX) -INCLUDE_DIRECTORIES(${SDL2_INCLUDE_DIRS} ${LIBCURL_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} ${SPEEX_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS}) +INCLUDE_DIRECTORIES(${SDL2_INCLUDE_DIRS} ${LIBCURL_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} ${SPEEX_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${BREAKPAD_INCLUDE_DIR}) -LINK_DIRECTORIES(${SDL2_LIBRARY_DIRS} ${JANSSON_LIBRARY_DIRS} ${LIBCURL_LIBRARY_DIRS} ${PNG_LIBRARY_DIRS} ${ZLIB_LIBRARY_DIRS}) +LINK_DIRECTORIES(${SDL2_LIBRARY_DIRS} ${JANSSON_LIBRARY_DIRS} ${LIBCURL_LIBRARY_DIRS} ${PNG_LIBRARY_DIRS} ${ZLIB_LIBRARY_DIRS} ${BREAKPAD_LIBRARY_DIR}) if (WIN32) # build as library for now, replace with add_executable @@ -215,7 +225,7 @@ endif (UNIX AND NOT APPLE) # libopenrct2.dll -> openrct2.dll set_target_properties(${PROJECT} PROPERTIES PREFIX "") -TARGET_LINK_LIBRARIES(${PROJECT} ${SDL2LIBS} ${HTTPLIBS} ${NETWORKLIBS} ${SPEEX_LIBRARIES} ${DLLIB} ${REQUIREDLIBS}) +TARGET_LINK_LIBRARIES(${PROJECT} ${SDL2LIBS} ${HTTPLIBS} ${NETWORKLIBS} ${SPEEX_LIBRARIES} ${DLLIB} ${REQUIREDLIBS} ${BREAKPAD_LIBS}) if (APPLE OR STATIC) FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c) diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 72ec93e595..fa60aa53af 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 426da7e58564472ba1b7991f /* crash.cpp in Sources */ = {isa = PBXBuildFile; fileRef = a9793fe06a4244938f5d4b61 /* crash.cpp */; }; 001085F01C90FD030075A2AD /* textinputbuffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 001085EE1C90FD030075A2AD /* textinputbuffer.c */; }; C62A08D51C787C2A00F3AA76 /* drawing_fast.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C62A08D41C787C2A00F3AA76 /* drawing_fast.cpp */; }; D41B73EF1C2101890080A7B9 /* libcurl.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B73EE1C2101890080A7B9 /* libcurl.tbd */; }; @@ -222,6 +223,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + a9793fe06a4244938f5d4b61 /* crash.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = crash.cpp; path = src/platform/crash.cpp; sourceTree = ""; }; 001085EE1C90FD030075A2AD /* textinputbuffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = textinputbuffer.c; sourceTree = ""; }; 001085EF1C90FD030075A2AD /* textinputbuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = textinputbuffer.h; sourceTree = ""; }; C62A08D41C787C2A00F3AA76 /* drawing_fast.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = drawing_fast.cpp; sourceTree = ""; }; @@ -655,7 +657,8 @@ D4EC47711C26342F0024B507 /* title.c */, D4EC47721C26342F0024B507 /* title.h */, D4163F671C2A044D00B83136 /* version.h */, - ); + a9793fe06a4244938f5d4b61 /* crash.cpp */, +); name = Sources; sourceTree = ""; }; @@ -1505,7 +1508,8 @@ D4EC48721C26342F0024B507 /* balloon.c in Sources */, D4EC48571C26342F0024B507 /* save_prompt.c in Sources */, D4EC48701C26342F0024B507 /* viewport.c in Sources */, - ); + 426da7e58564472ba1b7991f /* crash.cpp in Sources */, +); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ diff --git a/appveyor.yml b/appveyor.yml index 2a883cc798..f756ad5db5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,3 +28,5 @@ artifacts: name: OpenRCT2-portable - path: .\artifacts\*.exe name: OpenRCT2-installer +- path: .\artifacts\openrct2-symbols.zip + name: OpenRCT2 debug symbols diff --git a/distribution/changelog.txt b/distribution/changelog.txt index ea95023887..cd8a212060 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -38,6 +38,7 @@ - Technical: lodepng dropped in return for libpng. - Technical: SDL2 upgraded from 2.0.3 to 2.0.4. - Technical: argparse dropped in return for bespoke command line parsing implementation. +- Technical: Integrated breakpad for (manual) crash reporting - Improve: performance of rendering, particularly for highly populated parks. - Improve: performance of loading parks. - Improve: support for hacked parks. diff --git a/openrct2.vcxproj b/openrct2.vcxproj index 3b5ae319ce..b616449867 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -87,6 +87,7 @@ + @@ -261,6 +262,7 @@ + @@ -329,20 +331,26 @@ - $(SolutionDir)lib\include;$(SolutionDir)lib\include\libspeex;$(SolutionDir)lib\include\sdl;$(SolutionDir)lib\include\jansson;$(SolutionDir)lib\include\sdl_ttf;$(SolutionDir)lib\include\libpng;$(SolutionDir)lib\include\zlib;$(IncludePath) + $(SolutionDir)lib\include;$(SolutionDir)lib\include\breakpad;$(SolutionDir)lib\include\libspeex;$(SolutionDir)lib\include\sdl;$(SolutionDir)lib\include\jansson;$(SolutionDir)lib\include\sdl_ttf;$(SolutionDir)lib\include\libpng;$(SolutionDir)lib\include\zlib;$(IncludePath) $(SolutionDir)lib;$(LibraryPath) $(SolutionDir)bin\ $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ - $(SolutionDir)lib\include;$(SolutionDir)lib\include\libspeex;$(SolutionDir)lib\include\sdl;$(SolutionDir)lib\include\jansson;$(SolutionDir)lib\include\sdl_ttf;$(SolutionDir)lib\include\libpng;$(SolutionDir)lib\include\zlib;$(IncludePath) + $(SolutionDir)lib\include;$(SolutionDir)lib\include\breakpad;$(SolutionDir)lib\include\libspeex;$(SolutionDir)lib\include\sdl;$(SolutionDir)lib\include\jansson;$(SolutionDir)lib\include\sdl_ttf;$(SolutionDir)lib\include\libpng;$(SolutionDir)lib\include\zlib;$(IncludePath) $(SolutionDir)lib;$(LibraryPath) $(SolutionDir)bin\ $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + + + USE_BREAKPAD;%(PreprocessorDefinitions) + + + 4091;%(DisableSpecificWarnings) Level3 Disabled true @@ -363,6 +371,7 @@ + 4091;%(DisableSpecificWarnings) Level3 Full true @@ -388,4 +397,4 @@ - \ No newline at end of file + diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters index 333e9eccff..07a3682284 100644 --- a/openrct2.vcxproj.filters +++ b/openrct2.vcxproj.filters @@ -587,6 +587,7 @@ Source\Core + @@ -896,5 +897,6 @@ Source\Core + - \ No newline at end of file + diff --git a/scripts/ps/appveyor_run.ps1 b/scripts/ps/appveyor_run.ps1 index baab8d99bb..a063aedda2 100644 --- a/scripts/ps/appveyor_run.ps1 +++ b/scripts/ps/appveyor_run.ps1 @@ -40,10 +40,12 @@ if (${env:CODE-SIGN-KEY-OPENRCT2.ORG.PFX.PASSWORD} -ne $null) # Enable pushing builds to OpenRCT2.org if token environment variable is set $pushBuilds = $false $installer = $false +$symbols = $false if (${env:OPENRCT2.ORG_TOKEN} -ne $null) { $pushBuilds = $true $installer = $true + $symbols = $true } # Write out summary of the build @@ -98,6 +100,18 @@ if ($installer) -CodeSign $codeSign } +if ($symbols) +{ + publish package ` + -Symbols ` + -Server $server ` + -GitTag $tag ` + -GitBranch $env:APPVEYOR_REPO_BRANCH ` + -GitSha1 $env:APPVEYOR_REPO_COMMIT ` + -GitSha1Short $env:APPVEYOR_REPO_COMMIT_SHORT ` + -CodeSign $codeSign +} + if ($pushBuilds) { $versionExtension = "" @@ -127,4 +141,14 @@ if ($pushBuilds) -version $version ` -flavourId 2 } + + # Push symbols + if ($symbols) + { + Write-Host "Sending symbols to OpenRCT2.org" -ForegroundColor Cyan + Push-Build -file ".\artifacts\openrct2-symbols.zip" ` + -name "$pushFileName-symbols.zip" ` + -version $version ` + -flavourId 5 + } } diff --git a/scripts/ps/build.ps1 b/scripts/ps/build.ps1 index 027985eb0b..99a188f97a 100644 --- a/scripts/ps/build.ps1 +++ b/scripts/ps/build.ps1 @@ -10,7 +10,10 @@ param ( [string]$Configuration = "Release", [Parameter(Mandatory = $false)] - [switch]$Rebuild = $false + [switch]$Rebuild = $false, + + [Parameter(Mandatory = $false)] + [switch]$Breakpad = $false ) # Setup @@ -40,7 +43,7 @@ function Build-OpenRCT2() { $target = "/t:rebuild" } - msbuild $rootPath\openrct2.sln /p:Configuration=$Configuration /p:Platform=Win32 $target /v:minimal | Write-Host + msbuild $rootPath\openrct2.sln /p:Breakpad=$Breakpad /p:Configuration=$Configuration /p:Platform=Win32 $target /v:minimal | Write-Host return $LASTEXITCODE } diff --git a/scripts/ps/install.ps1 b/scripts/ps/install.ps1 index 7b87a2176a..016e181e65 100644 --- a/scripts/ps/install.ps1 +++ b/scripts/ps/install.ps1 @@ -14,7 +14,7 @@ Import-Module "$scriptsPath\common.psm1" -DisableNameChecking # Constants $libsUrl = "https://openrct2.website/files/openrct2-libs-vs2015.zip" -$libsVersion = 6 +$libsVersion = 7 # Get paths $rootPath = Get-RootPath diff --git a/scripts/ps/publish.ps1 b/scripts/ps/publish.ps1 index a0e2a3532c..05b94deddf 100644 --- a/scripts/ps/publish.ps1 +++ b/scripts/ps/publish.ps1 @@ -14,7 +14,8 @@ param ( [string]$GitSha1 = "", [string]$GitSha1Short = "", [bool] $CodeSign = $false, - [switch]$Installer = $false + [switch]$Installer = $false, + [switch]$Symbols = $false ) if ($GitTag -eq "") @@ -77,7 +78,7 @@ function Do-PrepareSource() function Do-Build() { Write-Host "Building OpenRCT2..." -ForegroundColor Cyan - & "$scriptsPath\build.ps1" all -Rebuild + & "$scriptsPath\build.ps1" all -Rebuild -Breakpad if ($LASTEXITCODE -ne 0) { Write-Host "Failed to build OpenRCT2" -ForegroundColor Red @@ -97,6 +98,38 @@ function Do-Build() return 0 } +# Symbols +function Do-Symbols() +{ + Write-Host "Publishing OpenRCT2 debug symbols as zip..." -ForegroundColor Cyan + $artifactsDir = "$rootPath\artifacts" + $releaseDir = "$rootPath\bin" + $outZip = "$rootPath\artifacts\openrct2-symbols.zip" + + Copy-Item -Force "$releaseDir\openrct2.pdb" $artifactsDir -ErrorAction Stop + + # Create archive using 7z (renowned for speed and compression) + $7zcmd = "7za" + if (-not (AppExists($7zcmd))) + { + # AppVeyor in particular uses '7z' instead + $7zcmd = "7z" + if (-not (AppExists($7zcmd))) + { + Write-Host "Publish script requires 7z to be in PATH" -ForegroundColor Red + return 1 + } + } + & $7zcmd a -tzip -mx9 $outZip "$artifactsDir\openrct2.pdb" > $null + if ($LASTEXITCODE -ne 0) + { + Write-Host "Failed to create zip." -ForegroundColor Red + return 1 + } + Remove-Item -Force -Recurse "$artifactsDir\openrct2.pdb" -ErrorAction SilentlyContinue + return 0 +} + # Package function Do-Package() { @@ -209,6 +242,10 @@ function Do-Task-Package() { if (($result = (Do-Installer)) -ne 0) { return $result } } + elseif ($Symbols) + { + if (($result = (Do-Symbols)) -ne 0) { return $result } + } else { if (($result = (Do-Package)) -ne 0) { return $result } diff --git a/src/openrct2.c b/src/openrct2.c index 87ee82cc9b..6f919e89d3 100644 --- a/src/openrct2.c +++ b/src/openrct2.c @@ -33,6 +33,7 @@ #include "network/http.h" #include "network/network.h" #include "openrct2.h" +#include "platform/crash.h" #include "platform/platform.h" #include "ride/ride.h" #include "title.h" @@ -184,6 +185,8 @@ bool openrct2_initialise() return false; } + crash_init(); + if (!openrct2_setup_rct2_segment()) { log_fatal("Unable to load RCT2 data sector"); return false; diff --git a/src/platform/crash.cpp b/src/platform/crash.cpp new file mode 100644 index 0000000000..7503089ceb --- /dev/null +++ b/src/platform/crash.cpp @@ -0,0 +1,135 @@ +#include +#include "crash.h" + +#ifdef USE_BREAKPAD +#include + +#if defined(__WINDOWS__) + #include + #include + #include +#elif defined(__LINUX__) + #include + #define BREAKPAD_PATH "/tmp" +#else + #error Breakpad support not implemented yet for this platform +#endif + +extern "C" { + #include "../localisation/language.h" + #include "../scenario.h" + #include "platform.h" +} + +#include "../core/Console.hpp" + +#define WSZ(x) L"" x + +static bool OnCrash(const wchar_t * dumpPath, + const wchar_t * miniDumpId, + void * context, + EXCEPTION_POINTERS * exinfo, + MDRawAssertionInfo * assertion, + bool succeeded) +{ + if (!succeeded) + { + constexpr char * DumpFailedMessage = "Failed to create the dump. Nothing left to do. Please file an issue with OpenRCT2 on Github and provide latest save."; + printf("%s\n", DumpFailedMessage); + MessageBoxA(NULL, DumpFailedMessage, OPENRCT2_NAME, MB_OK | MB_ICONERROR); + return succeeded; + } + + wchar_t dumpFilePath[MAX_PATH]; + wchar_t saveFilePath[MAX_PATH]; + wsprintfW(dumpFilePath, L"%s%s.dmp", dumpPath, miniDumpId); + wsprintfW(saveFilePath, L"%s%s.sv6", dumpPath, miniDumpId); + + wprintf(L"Dump Path: %s\n", dumpFilePath); + wprintf(L"Dump Id: %s\n", miniDumpId); + wprintf(L"Version: %s\n", WSZ(OPENRCT2_VERSION)); + wprintf(L"Commit: %s\n", WSZ(OPENRCT2_COMMIT_SHA1_SHORT)); + + utf8 * saveFilePathUTF8 = widechar_to_utf8(saveFilePath); + SDL_RWops * rw = SDL_RWFromFile(saveFilePathUTF8, "wb+"); + free(saveFilePathUTF8); + + bool savedGameDumped = false; + if (rw != NULL) { + scenario_save(rw, 0x80000000); + savedGameDumped = true; + SDL_RWclose(rw); + } + + constexpr wchar_t * MessageFormat = L"A crash has occurred and dump was created at\n%s.\n\nPlease create an issue with OpenRCT2 on Github and provide the dump and save.\n\nVersion: %s\nCommit: %s"; + wchar_t message[MAX_PATH * 2]; + swprintf_s(message, + MessageFormat, + dumpFilePath, + WSZ(OPENRCT2_VERSION), + WSZ(OPENRCT2_COMMIT_SHA1_SHORT)); + + // Cannot use platform_show_messagebox here, it tries to set parent window already dead. + MessageBoxW(NULL, message, WSZ(OPENRCT2_NAME), MB_OK | MB_ICONERROR); + HRESULT coInitializeResult = CoInitialize(NULL); + if (SUCCEEDED(coInitializeResult)) + { + ITEMIDLIST * pidl = ILCreateFromPathW(dumpPath); + ITEMIDLIST * files[2]; + uint32 numFiles = 0; + + files[numFiles++] = ILCreateFromPathW(dumpFilePath); + if (savedGameDumped) + { + files[numFiles++] = ILCreateFromPathW(saveFilePath); + } + if (pidl != nullptr) { + HRESULT result = SHOpenFolderAndSelectItems(pidl, numFiles, (LPCITEMIDLIST *)files, 0); + ILFree(pidl); + for (uint32 i = 0; i < numFiles; i++) + { + ILFree(files[i]); + } + } + CoUninitialize(); + } + + // Return whether the dump was successful + return succeeded; +} + +static std::wstring GetDumpDirectory() +{ + char userDirectory[MAX_PATH]; + platform_get_user_directory(userDirectory, NULL); + + wchar_t * userDirectoryW = utf8_to_widechar(userDirectory); + auto result = std::wstring(userDirectoryW); + free(userDirectoryW); + + return result; +} + +#endif // USE_BREAKPAD + +// Using non-null pipe name here lets breakpad try setting OOP crash handling +constexpr wchar_t * PipeName = L"openrct2-bpad"; + +extern "C" CExceptionHandler crash_init() +{ +#ifdef USE_BREAKPAD + // Path must exist and be RW! + auto exHandler = new google_breakpad::ExceptionHandler( + GetDumpDirectory(), + 0, + OnCrash, + 0, + google_breakpad::ExceptionHandler::HANDLER_ALL, + MiniDumpWithDataSegs, + PipeName, + 0); + return reinterpret_cast(exHandler); +#else // USE_BREAKPAD + return nullptr; +#endif // USE_BREAKPAD +} diff --git a/src/platform/crash.h b/src/platform/crash.h new file mode 100644 index 0000000000..35c0d1f74b --- /dev/null +++ b/src/platform/crash.h @@ -0,0 +1,15 @@ +#ifndef _OPENRCT2_CRASH_ +#define _OPENRCT2_CRASH_ + +typedef void * CExceptionHandler; + +#ifdef __cplusplus +extern "C" +{ +#endif + CExceptionHandler crash_init(); +#ifdef __cplusplus +} +#endif + +#endif /* _OPENRCT2_CRASH_ */ diff --git a/src/platform/shared.c b/src/platform/shared.c index 1a8bfc935f..8749755954 100644 --- a/src/platform/shared.c +++ b/src/platform/shared.c @@ -811,7 +811,7 @@ void platform_free() SDL_Quit(); } -void platform_start_text_input(char* buffer, int max_length) +void platform_start_text_input(utf8* buffer, int max_length) { // TODO This doesn't work, and position could be improved to where text entry is SDL_Rect rect = { 10, 10, 100, 100 }; diff --git a/src/platform/windows.c b/src/platform/windows.c index 2f57d3b2c7..deb82e5859 100644 --- a/src/platform/windows.c +++ b/src/platform/windows.c @@ -573,7 +573,7 @@ void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory) } } -void platform_show_messagebox(char *message) +void platform_show_messagebox(utf8 *message) { MessageBoxA(windows_get_window_handle(), message, "OpenRCT2", MB_OK); }