diff --git a/Boxer.xcodeproj/project.pbxproj b/Boxer.xcodeproj/project.pbxproj index d73a1f93..8ae38718 100644 --- a/Boxer.xcodeproj/project.pbxproj +++ b/Boxer.xcodeproj/project.pbxproj @@ -23,6 +23,24 @@ 05F9A20124BAA7AA00969496 /* SDL2_net.framework in Copy Bundled Frameworks */ = {isa = PBXBuildFile; fileRef = 05F9A1FE24BAA64100969496 /* SDL2_net.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 05F9A20224BAA7AB00969496 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0532FBF624BAA1A4008EAD86 /* SDL2.framework */; }; 05F9A20324BAA7AB00969496 /* SDL2.framework in Copy Bundled Frameworks */ = {isa = PBXBuildFile; fileRef = 0532FBF624BAA1A4008EAD86 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 0FFCA147303264D20076F2F5 /* ide.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA146303264D20076F2F5 /* ide.cpp */; }; + 0FFCA148303264D20076F2F5 /* ide.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA146303264D20076F2F5 /* ide.cpp */; }; + 0FFCA14A303265380076F2F5 /* program_serial.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA149303265380076F2F5 /* program_serial.cpp */; }; + 0FFCA14B303265380076F2F5 /* program_serial.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA149303265380076F2F5 /* program_serial.cpp */; }; + 0FFCA14F3032657E0076F2F5 /* program_placeholder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA14E3032657E0076F2F5 /* program_placeholder.cpp */; }; + 0FFCA1503032657E0076F2F5 /* program_placeholder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA14E3032657E0076F2F5 /* program_placeholder.cpp */; }; + 0FFCA152303265A60076F2F5 /* ethernet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA151303265A60076F2F5 /* ethernet.cpp */; }; + 0FFCA153303265A60076F2F5 /* ethernet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA151303265A60076F2F5 /* ethernet.cpp */; }; + 0FFCA160303265EF0076F2F5 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 0FFCA15C303265EF0076F2F5 /* LICENSE */; }; + 0FFCA163303265EF0076F2F5 /* fs_std_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA15B303265EF0076F2F5 /* fs_std_impl.cpp */; }; + 0FFCA164303265EF0076F2F5 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 0FFCA15C303265EF0076F2F5 /* LICENSE */; }; + 0FFCA167303265EF0076F2F5 /* fs_std_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA15B303265EF0076F2F5 /* fs_std_impl.cpp */; }; + 0FFCA16F303266110076F2F5 /* LICENSE.MIT in Resources */ = {isa = PBXBuildFile; fileRef = 0FFCA168303266110076F2F5 /* LICENSE.MIT */; }; + 0FFCA172303266110076F2F5 /* LICENSE.WTFPLv2 in Resources */ = {isa = PBXBuildFile; fileRef = 0FFCA169303266110076F2F5 /* LICENSE.WTFPLv2 */; }; + 0FFCA173303266110076F2F5 /* whereami.c in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA16D303266110076F2F5 /* whereami.c */; }; + 0FFCA174303266110076F2F5 /* LICENSE.MIT in Resources */ = {isa = PBXBuildFile; fileRef = 0FFCA168303266110076F2F5 /* LICENSE.MIT */; }; + 0FFCA177303266110076F2F5 /* LICENSE.WTFPLv2 in Resources */ = {isa = PBXBuildFile; fileRef = 0FFCA169303266110076F2F5 /* LICENSE.WTFPLv2 */; }; + 0FFCA178303266110076F2F5 /* whereami.c in Sources */ = {isa = PBXBuildFile; fileRef = 0FFCA16D303266110076F2F5 /* whereami.c */; }; 5514500A24BE81E00002CE28 /* opl3.c in Sources */ = {isa = PBXBuildFile; fileRef = 55144FEC24BE81DF0002CE28 /* opl3.c */; }; 5514500B24BE81E00002CE28 /* opl3.c in Sources */ = {isa = PBXBuildFile; fileRef = 55144FEC24BE81DF0002CE28 /* opl3.c */; }; 5514500C24BE81E00002CE28 /* vorbis.c in Sources */ = {isa = PBXBuildFile; fileRef = 55144FEE24BE81E00002CE28 /* vorbis.c */; }; @@ -1044,6 +1062,23 @@ 05E6BA7324BA26FD00BAAD2D /* BXMetalRenderingView+BXImageCapture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "BXMetalRenderingView+BXImageCapture.m"; sourceTree = ""; }; 05E6BA7524BA279000BAAD2D /* BXMetalRenderingView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BXMetalRenderingView+Private.h"; sourceTree = ""; }; 05F9A1FE24BAA64100969496 /* SDL2_net.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2_net.framework; sourceTree = ""; }; + 0FFCA146303264D20076F2F5 /* ide.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ide.cpp; sourceTree = ""; }; + 0FFCA149303265380076F2F5 /* program_serial.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = program_serial.cpp; sourceTree = ""; }; + 0FFCA14C3032653F0076F2F5 /* program_serial.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = program_serial.h; sourceTree = ""; }; + 0FFCA14D3032657E0076F2F5 /* program_placeholder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = program_placeholder.h; sourceTree = ""; }; + 0FFCA14E3032657E0076F2F5 /* program_placeholder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = program_placeholder.cpp; sourceTree = ""; }; + 0FFCA151303265A60076F2F5 /* ethernet.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ethernet.cpp; sourceTree = ""; }; + 0FFCA155303265EF0076F2F5 /* filesystem.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = filesystem.hpp; sourceTree = ""; }; + 0FFCA156303265EF0076F2F5 /* fs_fwd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = fs_fwd.hpp; sourceTree = ""; }; + 0FFCA157303265EF0076F2F5 /* fs_impl.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = fs_impl.hpp; sourceTree = ""; }; + 0FFCA158303265EF0076F2F5 /* fs_std.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = fs_std.hpp; sourceTree = ""; }; + 0FFCA159303265EF0076F2F5 /* fs_std_fwd.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = fs_std_fwd.hpp; sourceTree = ""; }; + 0FFCA15B303265EF0076F2F5 /* fs_std_impl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = fs_std_impl.cpp; sourceTree = ""; }; + 0FFCA15C303265EF0076F2F5 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 0FFCA168303266110076F2F5 /* LICENSE.MIT */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.MIT; sourceTree = ""; }; + 0FFCA169303266110076F2F5 /* LICENSE.WTFPLv2 */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.WTFPLv2; sourceTree = ""; }; + 0FFCA16C303266110076F2F5 /* whereami.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = whereami.h; sourceTree = ""; }; + 0FFCA16D303266110076F2F5 /* whereami.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = whereami.c; sourceTree = ""; }; 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -2141,6 +2176,39 @@ sourceTree = ""; usesTabs = 0; }; + 0FFCA15A303265EF0076F2F5 /* ghc */ = { + isa = PBXGroup; + children = ( + 0FFCA155303265EF0076F2F5 /* filesystem.hpp */, + 0FFCA156303265EF0076F2F5 /* fs_fwd.hpp */, + 0FFCA157303265EF0076F2F5 /* fs_impl.hpp */, + 0FFCA158303265EF0076F2F5 /* fs_std.hpp */, + 0FFCA159303265EF0076F2F5 /* fs_std_fwd.hpp */, + ); + path = ghc; + sourceTree = ""; + }; + 0FFCA15F303265EF0076F2F5 /* ghc */ = { + isa = PBXGroup; + children = ( + 0FFCA15A303265EF0076F2F5 /* ghc */, + 0FFCA15B303265EF0076F2F5 /* fs_std_impl.cpp */, + 0FFCA15C303265EF0076F2F5 /* LICENSE */, + ); + path = ghc; + sourceTree = ""; + }; + 0FFCA16E303266110076F2F5 /* whereami */ = { + isa = PBXGroup; + children = ( + 0FFCA168303266110076F2F5 /* LICENSE.MIT */, + 0FFCA169303266110076F2F5 /* LICENSE.WTFPLv2 */, + 0FFCA16C303266110076F2F5 /* whereami.h */, + 0FFCA16D303266110076F2F5 /* whereami.c */, + ); + path = whereami; + sourceTree = ""; + }; 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { isa = PBXGroup; children = ( @@ -3109,7 +3177,11 @@ 55DB14F927EDA2C70024AE9E /* program_mount.cpp */, 55DB14EA27EDA2C60024AE9E /* program_mount.h */, 55DB14E827EDA2C60024AE9E /* program_rescan.cpp */, + 0FFCA14D3032657E0076F2F5 /* program_placeholder.h */, + 0FFCA14E3032657E0076F2F5 /* program_placeholder.cpp */, 55DB14FB27EDA2C70024AE9E /* program_rescan.h */, + 0FFCA14C3032653F0076F2F5 /* program_serial.h */, + 0FFCA149303265380076F2F5 /* program_serial.cpp */, ); path = dos; sourceTree = ""; @@ -3170,6 +3242,7 @@ 9F77212512B38C4400072AE8 /* hardware.cpp */, 9F77212612B38C4400072AE8 /* iohandler.cpp */, 55DB14DC27EDA2020024AE9E /* iohandler_containers.cpp */, + 0FFCA146303264D20076F2F5 /* ide.cpp */, 9F77212712B38C4400072AE8 /* ipx.cpp */, 9F77212812B38C4400072AE8 /* ipxserver.cpp */, 9F77212912B38C4400072AE8 /* joystick.cpp */, @@ -3254,12 +3327,14 @@ 9F77216412B38C4400072AE8 /* libs */ = { isa = PBXGroup; children = ( + 0FFCA15F303265EF0076F2F5 /* ghc */, 55F0B73C26ABFEE80077E58C /* residfp */, 55144FED24BE81E00002CE28 /* decoders */, 55144FE924BE81DF0002CE28 /* nuked */, 5514500424BE81E00002CE28 /* ppscale */, 9F77216512B38C4400072AE8 /* gui_tk */, 9F77216812B38C4400072AE8 /* zmbv */, + 0FFCA16E303266110076F2F5 /* whereami */, ); path = libs; sourceTree = ""; @@ -3287,6 +3362,7 @@ children = ( 9F77216C12B38C4400072AE8 /* cross.cpp */, 55540E252545604400B88BE3 /* fs_utils_posix.cpp */, + 0FFCA151303265A60076F2F5 /* ethernet.cpp */, 9F77216D12B38C4400072AE8 /* messages.cpp */, 55844D0F26C70E7700AA5924 /* pacer.cpp */, 9F77216E12B38C4400072AE8 /* programs.cpp */, @@ -3980,6 +4056,7 @@ 9FFE3EA81BE68DBA005332E0 /* Localizable.strings in Resources */, 9F4199E81BD416E400A2D165 /* LauncherItem.xib in Resources */, 55635A432D35DEB700C70C0E /* Configuration.xcstrings in Resources */, + 0FFCA160303265EF0076F2F5 /* LICENSE in Resources */, 9F4199F21BD416E400A2D165 /* ProgramPanel.xib in Resources */, 9F12F97C144875F400FC9645 /* DummyCD.iso in Resources */, 9F4199F31BD416E400A2D165 /* ImportTipsPanel.xib in Resources */, @@ -3991,6 +4068,8 @@ 058BC52A24C27BA10078C244 /* SliderCell.xib in Resources */, 9F4199F01BD416E400A2D165 /* Preferences.xib in Resources */, 9F4199E31BD416E400A2D165 /* PrintStatusPanel.xib in Resources */, + 0FFCA174303266110076F2F5 /* LICENSE.MIT in Resources */, + 0FFCA177303266110076F2F5 /* LICENSE.WTFPLv2 in Resources */, 9FE32383157A9559004492E8 /* Shaders in Resources */, 9F4199F51BD416E400A2D165 /* Welcome.xib in Resources */, 9F4199F11BD416E400A2D165 /* About.xib in Resources */, @@ -4008,6 +4087,7 @@ buildActionMask = 2147483647; files = ( 9F2D30D615B8233800FAE848 /* LockClosing.aiff in Resources */, + 0FFCA164303265EF0076F2F5 /* LICENSE in Resources */, 9FAA7EB01BE698ED00F4FCA6 /* Localizable.strings in Resources */, 55220E4824CFFFC900A6736A /* SliderCell.xib in Resources */, 9F2D30D715B8233800FAE848 /* LockOpening.aiff in Resources */, @@ -4024,6 +4104,8 @@ 9FCB7B2715B877E500CC7CC7 /* DOS Toolkit in Resources */, 9FCB7B2815B8782800CC7CC7 /* Bezel.xib in Resources */, 9F1E959A15BD88D700C3C6A0 /* UserDefaults.plist in Resources */, + 0FFCA16F303266110076F2F5 /* LICENSE.MIT in Resources */, + 0FFCA172303266110076F2F5 /* LICENSE.WTFPLv2 in Resources */, 55220E4624CFFFC900A6736A /* CheckboxCell.xib in Resources */, 9F1E959C15BD899500C3C6A0 /* GameDefaults.plist in Resources */, 9F7472D61BE7D96E00B9C9ED /* StandaloneAbout.xib in Resources */, @@ -4102,6 +4184,7 @@ 9FDAD87F10C182B3004EA7D0 /* BXCoverArtWell.m in Sources */, 9FD011BD10C2A02F00670CE1 /* BXGameboxPanelController.m in Sources */, 9F76D1B910CA9DFB00C3B081 /* NSWorkspace+ADBFileTypes.m in Sources */, + 0FFCA14B303265380076F2F5 /* program_serial.cpp in Sources */, 9F76D45610CBE5C500C3B081 /* BXSession+BXDragDrop.m in Sources */, 8D11072D0486CEB800E47090 /* main.m in Sources */, 9F44E5E410D17A4C0081B8D2 /* BXEmulator+BXPaste.mm in Sources */, @@ -4170,6 +4253,7 @@ 9F44500212AEC0FC00A2D405 /* BXSimpleDriveImport.m in Sources */, 5514502224BE81E00002CE28 /* ppscale.c in Sources */, 9F2120F813015653002AB1B7 /* BXShelfArt.m in Sources */, + 0FFCA178303266110076F2F5 /* whereami.c in Sources */, 9F2120FE1301597E002AB1B7 /* NSImage+ADBSaveImages.m in Sources */, 9F2122EA1301BDE1002AB1B7 /* BXShelfAppearanceOperation.m in Sources */, 55DB14FE27EDA2C70024AE9E /* program_mount_common.cpp in Sources */, @@ -4183,6 +4267,7 @@ 55574801208FA8040045E635 /* ymf262.cpp in Sources */, 9FBAA878134A66C80092BF95 /* BXBinCueImageImport.m in Sources */, 9FF865CF134DAB5A001CE857 /* ADBTaskOperation.m in Sources */, + 0FFCA152303265A60076F2F5 /* ethernet.cpp in Sources */, 9FDE341F1351DE26000DCE44 /* BXBlueprintBackgroundView.m in Sources */, 9FDE34341351DFD4000DCE44 /* BXImportTipsPanelController.m in Sources */, 9F731B2E136323D2005E564F /* ADBHIDMonitor.m in Sources */, @@ -4206,6 +4291,7 @@ 9FB446EB13D23BD900E69AF5 /* BXG25ControllerProfile.m in Sources */, 9FA0C9E313D24FF3000F6FCB /* DDHidUsage+ADBUsageExtensions.m in Sources */, 9FC2F84013D60FBD00BD4F6B /* BXDualActionControllerProfile.m in Sources */, + 0FFCA1503032657E0076F2F5 /* program_placeholder.cpp in Sources */, 9F70277B13D706CC00606B4C /* BXBezelController.m in Sources */, 9F70278413D72D8100606B4C /* BXThemedLabel.m in Sources */, 9F8F482513DC74CA00C7E022 /* NSShadow+ADBShadowExtensions.m in Sources */, @@ -4343,6 +4429,7 @@ 55F0B79826ABFEE80077E58C /* version.cc in Sources */, 9F77219D12B38C4400072AE8 /* drives.cpp in Sources */, 9F77219E12B38C4400072AE8 /* dosbox.cpp in Sources */, + 0FFCA163303265EF0076F2F5 /* fs_std_impl.cpp in Sources */, 55574803208FA8040045E635 /* fmopl.cpp in Sources */, 9F77219F12B38C4400072AE8 /* fpu.cpp in Sources */, 9F7721A112B38C4400072AE8 /* render.cpp in Sources */, @@ -4387,6 +4474,7 @@ 55A76C5E2B678C0700DEF246 /* URL+ADBAliasHelpers.swift in Sources */, 9F7721C712B38C4400072AE8 /* vga_draw.cpp in Sources */, 9F7721C812B38C4400072AE8 /* vga_gfx.cpp in Sources */, + 0FFCA147303264D20076F2F5 /* ide.cpp in Sources */, 9F7721C912B38C4400072AE8 /* vga_memory.cpp in Sources */, 9F7721CA12B38C4400072AE8 /* vga_misc.cpp in Sources */, 55F0B77426ABFEE80077E58C /* EnvelopeGenerator.cpp in Sources */, @@ -4517,6 +4605,7 @@ 5514501F24BE81E00002CE28 /* flac.c in Sources */, 9F2D301815B8233800FAE848 /* BXInputController+BXKeyboardInput.m in Sources */, 9F2D301915B8233800FAE848 /* BXEmulatedJoystick.mm in Sources */, + 0FFCA167303265EF0076F2F5 /* fs_std_impl.cpp in Sources */, 9F2D301A15B8233800FAE848 /* BXCHFlightstick.mm in Sources */, 9F2D301B15B8233800FAE848 /* callback.cpp in Sources */, 55F643FA25F41DF5009E1800 /* rwqueue.cpp in Sources */, @@ -4573,6 +4662,7 @@ 9F2D304A15B8233800FAE848 /* dbopl.cpp in Sources */, 9F2D304B15B8233800FAE848 /* disney.cpp in Sources */, 9F2D304C15B8233800FAE848 /* dma.cpp in Sources */, + 0FFCA153303265A60076F2F5 /* ethernet.cpp in Sources */, 9F2D304D15B8233800FAE848 /* gameblaster.cpp in Sources */, 9F2D304E15B8233800FAE848 /* gus.cpp in Sources */, 9F2D304F15B8233800FAE848 /* hardware.cpp in Sources */, @@ -4728,6 +4818,7 @@ 9F98410315BEE64400B50CDA /* ADBShadowedFilesystem.m in Sources */, 9FB60E8F15C5552F00CD0D63 /* NSURL+ADBFilesystemHelpers.m in Sources */, 9FB60E9315C5643200CD0D63 /* NSError+ADBErrorHelpers.mm in Sources */, + 0FFCA14A303265380076F2F5 /* program_serial.cpp in Sources */, 9F458D9D15D83B8C00DF9102 /* BXLaunchPanelController.m in Sources */, 9F458D9E15D83B9000DF9102 /* BXDOSWindowBackgroundView.m in Sources */, 9F57684A15DA27A300B50500 /* BXHUDSpinningProgressIndicator.m in Sources */, @@ -4744,6 +4835,7 @@ 9FD6AA1916314A5B002B774E /* printer_charmaps.cpp in Sources */, 9FD6AA1B16314A5B002B774E /* printer_redir.cpp in Sources */, 9FD6AA2116315278002B774E /* BXEmulatedPrinter.mm in Sources */, + 0FFCA14F3032657E0076F2F5 /* program_placeholder.cpp in Sources */, 9F596503163D8D910094FD6B /* BXSession+BXPrinting.m in Sources */, 55F0B78526ABFEE80077E58C /* Filter6581.cpp in Sources */, 9FF982121646C7640080F763 /* BXPrintSession.m in Sources */, @@ -4763,10 +4855,12 @@ 9F80E80016DA3170001C3162 /* ADBFileHandle.m in Sources */, 9F693D5F16DD40DD00CCEA7C /* ADBEnumerationHelpers.m in Sources */, 9F3756B51A2228860060E131 /* BXStandaloneLaunchPanelController.m in Sources */, + 0FFCA148303264D20076F2F5 /* ide.cpp in Sources */, 9F38B38C16DFB1960050AB15 /* ADBLocalFilesystem.m in Sources */, 9F1E8CDB16E16C0400F1C908 /* ADBScanOperation.m in Sources */, 9F1E8CDF16E16EB800F1C908 /* ADBMountableImage.m in Sources */, 55F0B79326ABFEE80077E58C /* Filter8580.cpp in Sources */, + 0FFCA173303266110076F2F5 /* whereami.c in Sources */, 9FCA937D16FF37A000720B81 /* BXDocumentationPanelController.m in Sources */, 9FCA937E16FF38E700720B81 /* BXDocumentationBrowser.m in Sources */, 9F8B282B1709C4A100B31A14 /* ADBFilesystemBase.m in Sources */, @@ -5138,11 +5232,14 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO; - CODE_SIGN_IDENTITY = ""; + CODE_SIGN_IDENTITY = "Developer ID Application: David Seto (GHTQCRGWTR)"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_SHORT_VERSION_STRING = 1.0; CURRENT_PROJECT_VERSION = 20210308; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = GHTQCRGWTR; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Frameworks\"", @@ -5170,9 +5267,11 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = "2.0.0-alpha"; OTHER_CFLAGS = "-mstackrealign"; - PRODUCT_BUNDLE_IDENTIFIER = "net.washboardabs.boxer-standalone"; + PRODUCT_BUNDLE_IDENTIFIER = "com.dtseto.boxer-standalone"; PRODUCT_NAME = "Boxer Standalone"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "Standalone/Boxer Standalone-Bridging-Header.h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "Boxer-Swift.h"; SWIFT_VERSION = 5.0; @@ -5185,11 +5284,14 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO; - CODE_SIGN_IDENTITY = ""; + CODE_SIGN_IDENTITY = "Developer ID Application: David Seto (GHTQCRGWTR)"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_SHORT_VERSION_STRING = 1.0; CURRENT_PROJECT_VERSION = 20210308; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = GHTQCRGWTR; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Frameworks\"", @@ -5205,6 +5307,8 @@ "\"$(SRCROOT)/Frameworks/SDL2.framework/Headers\"", "\"$(SRCROOT)/DOSBox-Staging/submodules/loguru\"", "\"$(SRCROOT)/Vendor/OpenEmuShaders/Source/RendererPrivate\"", + "$(SRCROOT)/DOSBox-Staging/src/libs/loguru", + "$(SRCROOT)/DOSBox-Staging/src/libs/ghc", ); INFOPLIST_FILE = "Standalone/Boxer Standalone-Info.plist"; LD_NO_PIE = YES; @@ -5213,9 +5317,11 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = "2.0.0-alpha"; OTHER_CFLAGS = "-mstackrealign"; - PRODUCT_BUNDLE_IDENTIFIER = "net.washboardabs.boxer-standalone"; + PRODUCT_BUNDLE_IDENTIFIER = "com.dtseto.boxer-standalone"; PRODUCT_NAME = "Boxer Standalone"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "Standalone/Boxer Standalone-Bridging-Header.h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "Boxer-Swift.h"; SWIFT_VERSION = 5.0; @@ -5231,11 +5337,14 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_EMPTY_BODY = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Developer ID Application: David Seto (GHTQCRGWTR)"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 20191017; - ENABLE_HARDENED_RUNTIME = YES; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = GHTQCRGWTR; + ENABLE_HARDENED_RUNTIME = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -5253,9 +5362,10 @@ "$(inherited)", ); MARKETING_VERSION = 1.2.4; - PRODUCT_BUNDLE_IDENTIFIER = "net.washboardabs.boxer-standalone-bundler"; + PRODUCT_BUNDLE_IDENTIFIER = "com.dtseto.boxer-standalone-bundler"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; STRINGS_FILE_OUTPUT_ENCODING = binary; WRAPPER_EXTENSION = app; }; @@ -5270,10 +5380,13 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_EMPTY_BODY = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Developer ID Application: David Seto (GHTQCRGWTR)"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; CURRENT_PROJECT_VERSION = 20191017; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = GHTQCRGWTR; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -5286,9 +5399,10 @@ "$(inherited)", ); MARKETING_VERSION = 1.2.4; - PRODUCT_BUNDLE_IDENTIFIER = "net.washboardabs.boxer-standalone-bundler"; + PRODUCT_BUNDLE_IDENTIFIER = "com.dtseto.boxer-standalone-bundler"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; STRINGS_FILE_OUTPUT_ENCODING = binary; WRAPPER_EXTENSION = app; }; @@ -5319,8 +5433,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__ARC_BRIDGE_CAST_NONARC = NO; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; CURRENT_PROJECT_VERSION = 20120907.1; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = GHTQCRGWTR; + ENABLE_HARDENED_RUNTIME = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -5336,6 +5453,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; MACOSX_DEPLOYMENT_TARGET = 10.14.4; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -5348,16 +5466,19 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = boxer; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO; CODE_SIGN_ENTITLEMENTS = Boxer/Boxer.entitlements; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Developer ID Application: David Seto (GHTQCRGWTR)"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: David Seto (GHTQCRGWTR)"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "2.0.0-alpha+20210308"; DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = GHTQCRGWTR; ENABLE_HARDENED_RUNTIME = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5379,16 +5500,19 @@ "\"$(SRCROOT)/Vendor/OpenEmuShaders/Source/RendererPrivate\"", ); INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; LD_NO_PIE = YES; "LD_NO_PIE[arch=arm64]" = NO; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = "2.0.0-alpha"; + MARKETING_VERSION = "2.0.2-alpha"; MTL_LANGUAGE_REVISION = UseDeploymentTarget; OTHER_CFLAGS = "-mstackrealign"; - PRODUCT_BUNDLE_IDENTIFIER = net.washboardabs.boxer; + PRODUCT_BUNDLE_IDENTIFIER = com.dtseto.boxer; PRODUCT_NAME = Boxer; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SKIP_INSTALL = YES; STRINGS_FILE_OUTPUT_ENCODING = binary; SWIFT_OBJC_BRIDGING_HEADER = "Boxer/Boxer-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -5399,17 +5523,20 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = boxer; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO; CODE_SIGN_ENTITLEMENTS = Boxer/Boxer.entitlements; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Developer ID Application: David Seto (GHTQCRGWTR)"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: David Seto (GHTQCRGWTR)"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "2.0.0-alpha+20210308"; DEVELOPMENT_TEAM = ""; - ENABLE_HARDENED_RUNTIME = NO; + "DEVELOPMENT_TEAM[sdk=macosx*]" = GHTQCRGWTR; + ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Frameworks\"", @@ -5426,18 +5553,23 @@ "\"$(SRCROOT)/Frameworks/SDL2.framework/Headers\"", "\"$(SRCROOT)/DOSBox-Staging/submodules/loguru\"", "\"$(SRCROOT)/Vendor/OpenEmuShaders/Source/RendererPrivate\"", + "$(SRCROOT)/DOSBox-Staging/src/libs/loguru", + "$(SRCROOT)/DOSBox-Staging/src/libs/ghc", ); INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; LD_NO_PIE = YES; "LD_NO_PIE[arch=arm64]" = NO; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = "2.0.0-alpha"; + MARKETING_VERSION = "2.0.2-alpha"; MTL_LANGUAGE_REVISION = UseDeploymentTarget; OTHER_CFLAGS = "-mstackrealign"; - PRODUCT_BUNDLE_IDENTIFIER = net.washboardabs.boxer; + PRODUCT_BUNDLE_IDENTIFIER = com.dtseto.boxer; PRODUCT_NAME = Boxer; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SKIP_INSTALL = NO; STRINGS_FILE_OUTPUT_ENCODING = binary; SWIFT_OBJC_BRIDGING_HEADER = "Boxer/Boxer-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -5469,8 +5601,12 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__ARC_BRIDGE_CAST_NONARC = NO; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 20120907.1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = GHTQCRGWTR; + ENABLE_HARDENED_RUNTIME = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ""; @@ -5484,7 +5620,9 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; MACOSX_DEPLOYMENT_TARGET = 10.14.4; + PRODUCT_BUNDLE_IDENTIFIER = com.dtseto.boxer; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Boxer/Application Delegate/BXApplication.m b/Boxer/Application Delegate/BXApplication.m index 78f75f25..b7960358 100644 --- a/Boxer/Application Delegate/BXApplication.m +++ b/Boxer/Application Delegate/BXApplication.m @@ -6,10 +6,170 @@ */ #import "BXApplication.h" +#import "BXBaseAppController.h" #import "BXBaseAppController+BXHotKeys.h" +#import "BXEmulatorErrors.h" +#import + +static NSString *BXApplicationCrashDumpSafeFilenameComponent(NSString *string) +{ + NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; + [allowedCharacters addCharactersInString: @"-_. "]; + + NSMutableString *result = [NSMutableString stringWithCapacity: string.length]; + for (NSUInteger index = 0; index < string.length; index++) + { + unichar character = [string characterAtIndex: index]; + if ([allowedCharacters characterIsMember: character]) + [result appendFormat: @"%C", character]; + else + [result appendString: @"-"]; + } + + return result; +} + +static BOOL BXApplicationIsReportingEmulatorExceptionFromSession(void) +{ + for (NSString *symbol in NSThread.callStackSymbols) + { + if ([symbol containsString: @"_reportEmulatorException:"]) + return YES; + } + + return NO; +} + +static NSURL *BXApplicationWriteFallbackCrashDumpForException(NSException *exception) +{ + NSFileManager *manager = [NSFileManager defaultManager]; + NSURL *applicationSupportURL = [manager URLsForDirectory: NSApplicationSupportDirectory + inDomains: NSUserDomainMask].firstObject; + if (!applicationSupportURL) + return nil; + + NSURL *dumpFolderURL = [[applicationSupportURL URLByAppendingPathComponent: @"Boxer" + isDirectory: YES] URLByAppendingPathComponent: @"Crash Dumps" + isDirectory: YES]; + + NSError *folderError = nil; + if (![manager createDirectoryAtURL: dumpFolderURL + withIntermediateDirectories: YES + attributes: nil + error: &folderError]) + { + NSLog(@"Could not create Boxer fallback crash dump folder at %@: %@", dumpFolderURL.path, folderError); + return nil; + } + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier: @"en_US_POSIX"]; + dateFormatter.dateFormat = @"yyyy-MM-dd HH.mm.ss"; + + NSString *safeName = BXApplicationCrashDumpSafeFilenameComponent(exception.name ?: @"Exception"); + NSString *fileName = [NSString stringWithFormat: @"Boxer Fallback Crash %@ - %@.txt", + [dateFormatter stringFromDate: [NSDate date]], + safeName]; + NSURL *dumpURL = [dumpFolderURL URLByAppendingPathComponent: fileName]; + + NSMutableString *dump = [NSMutableString string]; + [dump appendString: @"Boxer Fallback Crash Dump\n"]; + [dump appendString: @"==========================\n\n"]; + [dump appendFormat: @"Created: %@\n", [NSDate date]]; + [dump appendFormat: @"Boxer version: %@ (%@)\n", + [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"] ?: @"unknown", + [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey] ?: @"unknown"]; + + [dump appendString: @"\nException\n"]; + [dump appendString: @"---------\n"]; + [dump appendFormat: @"Name: %@\n", exception.name ?: @"unknown"]; + [dump appendFormat: @"Reason: %@\n", exception.reason ?: @"none"]; + [dump appendFormat: @"Source file: %@\n", [exception.userInfo objectForKey: @"file"] ?: @"unknown"]; + [dump appendFormat: @"Function: %@\n", [exception.userInfo objectForKey: @"function"] ?: @"unknown"]; + [dump appendFormat: @"Line: %@\n", [exception.userInfo objectForKey: @"line"] ?: @"unknown"]; + + [dump appendString: @"\nException call stack symbols\n"]; + [dump appendString: @"----------------------------\n"]; + if (exception.callStackSymbols.count) + [dump appendFormat: @"%@\n", [exception.callStackSymbols componentsJoinedByString: @"\n"]]; + else + [dump appendString: @"none\n"]; + + [dump appendString: @"\nCurrent thread call stack symbols\n"]; + [dump appendString: @"---------------------------------\n"]; + [dump appendFormat: @"%@\n", [NSThread.callStackSymbols componentsJoinedByString: @"\n"]]; + + NSError *writeError = nil; + if (![dump writeToURL: dumpURL + atomically: YES + encoding: NSUTF8StringEncoding + error: &writeError]) + { + NSLog(@"Could not write Boxer fallback crash dump to %@: %@", dumpURL.path, writeError); + return nil; + } + + NSLog(@"Saved Boxer fallback crash dump to %@", dumpURL.path); + return dumpURL; +} + +static BOOL BXApplicationWriteLocalReportFromLegacyBugReportURL(NSURL *URL) +{ + if (![[URL.host lowercaseString] isEqualToString: @"boxerapp.com"] || ![URL.path isEqualToString: @"/report-an-issue"]) + return NO; + + NSURLComponents *components = [NSURLComponents componentsWithURL: URL resolvingAgainstBaseURL: NO]; + NSString *title = nil; + NSString *body = nil; + for (NSURLQueryItem *item in components.queryItems) + { + if ([item.name isEqualToString: @"title"]) + title = item.value; + else if ([item.name isEqualToString: @"body"]) + body = item.value; + } + + BXBaseAppController *controller = (BXBaseAppController *)NSApp.delegate; + if ([controller respondsToSelector: @selector(reportIssueWithTitle:body:)]) + { + [controller reportIssueWithTitle: title body: body]; + return YES; + } + + return NO; +} + +@implementation NSWorkspace (BXLocalReports) + ++ (void) load +{ + Method originalMethod = class_getInstanceMethod(self, @selector(openURL:)); + Method replacementMethod = class_getInstanceMethod(self, @selector(bx_openURL:)); + method_exchangeImplementations(originalMethod, replacementMethod); +} + +- (BOOL) bx_openURL: (NSURL *)URL +{ + if (BXApplicationWriteLocalReportFromLegacyBugReportURL(URL)) + return YES; + + return [self bx_openURL: URL]; +} + +@end @implementation BXApplication +- (void) reportException: (NSException *)exception +{ + if ([exception.name isEqualToString: BXEmulatorUnrecoverableException] && !BXApplicationIsReportingEmulatorExceptionFromSession()) + { + BXApplicationWriteFallbackCrashDumpForException(exception); + } + + [super reportException: exception]; +} + - (void) sendEvent: (NSEvent *)theEvent { //Dispatch media key events. diff --git a/Boxer/Application Delegate/BXBaseAppController.h b/Boxer/Application Delegate/BXBaseAppController.h index c6b9f135..53461192 100644 --- a/Boxer/Application Delegate/BXBaseAppController.h +++ b/Boxer/Application Delegate/BXBaseAppController.h @@ -162,19 +162,24 @@ @end -/// Top-level methods for reporting fatal errors to Boxer's error reporting page. +/// Top-level methods for saving fatal error reports. @interface BXBaseAppController (BXErrorReporting) -/// Opens an issue tracker page for a new issue, prefilling with optional issue data. -/// @param title If provided, the title field of the issue form will be prefilled with this string. -/// @param body If provided, the content field of the issue form will be prefilled with this string. +/// Saves an error report file, prefilled with optional issue data. +/// @param title If provided, the title of the issue. +/// @param body If provided, the content of the issue report. - (void) reportIssueWithTitle: (NSString *)title body: (NSString *)body; -/// Opens an issue tracker page prefilled with the details of the specified error. -/// @param error The error whose details should be prefilled into the issue form. -/// @param session The session that triggered the error. Details of the session will be included in the issue text. +/// Saves an error report file prefilled with the details of the specified error. +/// @param error The error whose details should be prefilled into the report. +/// @param session The session that triggered the error. Details of the session will be included in the report. - (void) reportIssueForError: (NSError *)error inSession: (BXSession *)session; +/// Saves an error report for the specified error and returns the file URL. +- (NSURL *) writeIssueForError: (NSError *)error + inSession: (BXSession *)session + revealInFinder: (BOOL)revealInFinder; + @end diff --git a/Boxer/Application Delegate/BXBaseAppController.m b/Boxer/Application Delegate/BXBaseAppController.m index 95415548..8f0e2a97 100644 --- a/Boxer/Application Delegate/BXBaseAppController.m +++ b/Boxer/Application Delegate/BXBaseAppController.m @@ -696,23 +696,99 @@ - (IBAction) decrementVolume: (id)sender @implementation BXBaseAppController (BXErrorReporting) -- (void) reportIssueWithTitle: (NSString *)title body: (NSString *)body -{ - NSString *issueURLString = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"BugReportURL"]; - NSAssert(issueURLString.length, @"No issue URL found in Info.plist for key %@", @"BugReportURL"); +- (NSURL *) _writeIssueWithTitle: (NSString *)title + body: (NSString *)body + revealInFinder: (BOOL)revealInFinder +{ + NSFileManager *manager = [NSFileManager defaultManager]; + NSURL *applicationSupportURL = [manager URLsForDirectory: NSApplicationSupportDirectory + inDomains: NSUserDomainMask].firstObject; + if (!applicationSupportURL) + { + NSBeep(); + return nil; + } + + NSURL *reportFolderURL = [[applicationSupportURL URLByAppendingPathComponent: @"Boxer" + isDirectory: YES] URLByAppendingPathComponent: @"Crash Dumps" + isDirectory: YES]; - if (issueURLString.length) + NSError *folderError = nil; + if (![manager createDirectoryAtURL: reportFolderURL + withIntermediateDirectories: YES + attributes: nil + error: &folderError]) { - NSString *encodedTitle = (title) ? [title stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet.URLQueryAllowedCharacterSet] : @""; - NSString *encodedBody = (body) ? [body stringByAddingPercentEncodingWithAllowedCharacters: NSCharacterSet.URLQueryAllowedCharacterSet] : @""; - - NSString *completeURLString = [NSString stringWithFormat: @"%@?title=%@&body=%@", issueURLString, encodedTitle, encodedBody]; - [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: completeURLString]]; + NSLog(@"Could not create Boxer report folder at %@: %@", reportFolderURL.path, folderError); + NSBeep(); + return nil; + } + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier: @"en_US_POSIX"]; + dateFormatter.dateFormat = @"yyyy-MM-dd HH.mm.ss"; + + NSString *reportTitle = title.length ? title : @"Boxer Error Report"; + NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; + [allowedCharacters addCharactersInString: @"-_. "]; + + NSMutableString *safeTitle = [NSMutableString stringWithCapacity: reportTitle.length]; + for (NSUInteger index = 0; index < reportTitle.length; index++) + { + unichar character = [reportTitle characterAtIndex: index]; + if ([allowedCharacters characterIsMember: character]) + [safeTitle appendFormat: @"%C", character]; + else + [safeTitle appendString: @"-"]; + } + + NSString *fileName = [NSString stringWithFormat: @"Boxer Error Report %@ - %@.md", + [dateFormatter stringFromDate: [NSDate date]], + safeTitle]; + NSURL *reportURL = [reportFolderURL URLByAppendingPathComponent: fileName]; + + NSMutableString *report = [NSMutableString string]; + [report appendFormat: @"# %@\n\n", reportTitle]; + [report appendFormat: @"Created: %@\n\n", [NSDate date]]; + if (body.length) + [report appendString: body]; + else + [report appendString: @"No additional error details were available.\n"]; + + NSError *writeError = nil; + if (![report writeToURL: reportURL + atomically: YES + encoding: NSUTF8StringEncoding + error: &writeError]) + { + NSLog(@"Could not write Boxer error report to %@: %@", reportURL.path, writeError); + NSBeep(); + return nil; } + + NSLog(@"Saved Boxer error report to %@", reportURL.path); + if (revealInFinder) + { + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: @[reportURL]]; + } + + return reportURL; +} + +- (void) reportIssueWithTitle: (NSString *)title body: (NSString *)body +{ + [self _writeIssueWithTitle: title body: body revealInFinder: YES]; } - (void) reportIssueForError: (NSError *)error inSession: (BXSession *)session +{ + [self writeIssueForError: error inSession: session revealInFinder: YES]; +} + +- (NSURL *) writeIssueForError: (NSError *)error + inSession: (BXSession *)session + revealInFinder: (BOOL)revealInFinder { if ([error matchesDomain: BXEmulatorErrorDomain code: BXEmulatorUnrecoverableError]) { @@ -741,21 +817,28 @@ - (void) reportIssueForError: (NSError *)error //---- [issueBody appendString: @"## Error details ##\n\n"]; - [issueBody appendFormat: @"**Error message:** %@\n", exception.reason]; + [issueBody appendFormat: @"**Error message:** %@\n", exception.reason ?: error.localizedDescription ?: @"Unknown error"]; if (function) { [issueBody appendFormat: @"**In function:** `%@` (line %@)\n", function, lineNumber]; } - [issueBody appendString: @"**Full stack trace:**\n\n"]; - for (NSDictionary *description in exception.callStackDescriptions) + if (exception) { - NSString *libraryName = [description objectForKey: ADBCallstackLibraryName]; - NSString *funcName = [description objectForKey: ADBCallstackHumanReadableFunctionName]; - NSNumber *offset = [description objectForKey: ADBCallstackSymbolOffset]; - - [issueBody appendFormat: @" %@ -- %@ (%@)\n", libraryName, funcName, offset]; + [issueBody appendString: @"**Full stack trace:**\n\n"]; + for (NSDictionary *description in exception.callStackDescriptions) + { + NSString *libraryName = [description objectForKey: ADBCallstackLibraryName]; + NSString *funcName = [description objectForKey: ADBCallstackHumanReadableFunctionName]; + NSNumber *offset = [description objectForKey: ADBCallstackSymbolOffset]; + + [issueBody appendFormat: @" %@ -- %@ (%@)\n", libraryName, funcName, offset]; + } + } + else + { + [issueBody appendString: @"**Full stack trace:** unavailable; the error did not include an exception object.\n"]; } //---- @@ -782,12 +865,18 @@ - (void) reportIssueForError: (NSError *)error } } - [(BXBaseAppController *)[NSApp delegate] reportIssueWithTitle: issueTitle body: issueBody]; + NSURL *crashDumpURL = [error.userInfo objectForKey: @"crashDumpURL"]; + if (crashDumpURL) + { + [issueBody appendFormat: @"\n\n## Local crash dump ##\n\n%@\n", crashDumpURL.path]; + } + + return [(BXBaseAppController *)[NSApp delegate] _writeIssueWithTitle: issueTitle body: issueBody revealInFinder: revealInFinder]; } //We don't yet have suitable formulations for other kinds of errors, so just open the issue page blank. else { - [(BXBaseAppController *)[NSApp delegate] reportIssueWithTitle: nil body: nil]; + return [(BXBaseAppController *)[NSApp delegate] _writeIssueWithTitle: nil body: nil revealInFinder: revealInFinder]; } } @end diff --git a/Boxer/BXEmulator+BXAudio.mm b/Boxer/BXEmulator+BXAudio.mm index 7c380748..6f5113dd 100644 --- a/Boxer/BXEmulator+BXAudio.mm +++ b/Boxer/BXEmulator+BXAudio.mm @@ -178,14 +178,14 @@ void _renderMIDIOutput(Bitu numFrames) { //We need to look up the corresponding channel for this because DOSBox's //mixer doesn't pass any context with its callbacks. - MixerChannel *channel = MIXER_FindChannel(BXMIDIChannelName); + MixerChannel *channel = MIXER_FindChannel(BXMIDIChannelName).get(); if (channel) [[BXEmulator currentEmulator] _renderMIDIOutputToChannel: channel frames: numFrames]; } - (MixerChannel *) _MIDIMixerChannel { - return MIXER_FindChannel(BXMIDIChannelName); + return MIXER_FindChannel(BXMIDIChannelName).get(); } - (MixerChannel *) _addMIDIMixerChannelWithSampleRate: (NSUInteger)sampleRate @@ -198,7 +198,7 @@ - (MixerChannel *) _addMIDIMixerChannelWithSampleRate: (NSUInteger)sampleRate } else { - channel = MIXER_AddChannel(_renderMIDIOutput, sampleRate, BXMIDIChannelName); + channel = MIXER_AddChannel(_renderMIDIOutput, sampleRate, BXMIDIChannelName).get(); } channel->Enable(true); return channel; @@ -210,7 +210,7 @@ - (void) _removeMIDIMixerChannel if (channel) { channel->Enable(false); - MIXER_DelChannel(channel); + MIXER_DelChannel(BXMIDIChannelName); } } diff --git a/Boxer/BXEmulator+BXDOSFileSystem.mm b/Boxer/BXEmulator+BXDOSFileSystem.mm index 3b1d1bba..6db8632e 100644 --- a/Boxer/BXEmulator+BXDOSFileSystem.mm +++ b/Boxer/BXEmulator+BXDOSFileSystem.mm @@ -1107,7 +1107,7 @@ - (DOS_Drive *) _floppyDriveFromImageAtPath: (NSString *)path //If the path couldn't be encoded, don't attempt to go further if (!drivePath) return nil; - fatDrive *drive = new fatDrive(drivePath, 0, 0, 0, 0, 0); + fatDrive *drive = new fatDrive(drivePath, 0, 0, 0, 0, 0, false); if (!drive || !drive->created_successfully) { delete drive; diff --git a/Boxer/BXEmulator.mm b/Boxer/BXEmulator.mm index 1a5e3ebe..9bf4d245 100644 --- a/Boxer/BXEmulator.mm +++ b/Boxer/BXEmulator.mm @@ -653,7 +653,9 @@ - (BXJoystickSupportLevel) joystickSupport { switch (joytype) { - case JOY_NONE: + case JOY_DISABLED: + case JOY_NONE_FOUND: + case JOY_ONLY_FOR_MAPPING: return BXNoJoystickSupport; break; case JOY_2AXIS: @@ -1008,8 +1010,8 @@ - (void) _startDOSBox //Create a new configuration instance and feed it an empty set of parameters. char const *argv[0]; commandLine = new CommandLine(0, argv); - configuration = new Config(commandLine); - control = configuration; + control.reset(new Config(commandLine)); + configuration = control.get(); //Sets up the vast swathes of DOSBox configuration file parameters, //and registers the shell to start up when we finish initializing. @@ -1035,7 +1037,16 @@ - (void) _startDOSBox catch (char *errMessage) { self.executing = NO; - + + // ObjC exceptions don't trigger C++ stack unwinding, so we must + // clean up DOSBox state explicitly before raising. + SDL_Quit(); + [self.videoHandler shutdown]; + control.reset(); + configuration = NULL; + delete commandLine; + commandLine = NULL; + NSString *reason = [NSString stringWithCString: errMessage encoding: BXDirectStringEncoding]; [NSException raise: BXEmulatorUnrecoverableException format: @"DOSBox aborted with the following error: %@", reason]; @@ -1043,9 +1054,18 @@ - (void) _startDOSBox catch (boxer_emulatorException &e) { self.executing = NO; + + // ObjC exceptions don't trigger C++ stack unwinding, so we must + // clean up DOSBox state explicitly before raising. + SDL_Quit(); + [self.videoHandler shutdown]; + control.reset(); + configuration = NULL; + delete commandLine; + commandLine = NULL; + NSException *exception = [BXEmulatorException exceptionWithName: BXEmulatorUnrecoverableException originalException: &e]; - [exception raise]; } catch (int) @@ -1055,10 +1075,9 @@ - (void) _startDOSBox //Any other exception is a genuine fuckup and needs to be thrown all the way up. //Clean up after DOSBox finishes. - SDL_Quit(); + SDL_Quit(); [self.videoHandler shutdown]; - control = NULL; - delete configuration; + control.reset(); configuration = NULL; delete commandLine; commandLine = NULL; diff --git a/Boxer/BXGamebox.m b/Boxer/BXGamebox.m index b9d5c066..3d0b80da 100644 --- a/Boxer/BXGamebox.m +++ b/Boxer/BXGamebox.m @@ -116,6 +116,16 @@ - (void) dealloc [self.undoDelegate removeAllUndoActionsForClient: self]; } +- (NSURL *) resourceURL +{ + return super.resourceURL ?: self.bundleURL; +} + +- (NSString *) resourcePath +{ + return super.resourcePath ?: self.bundlePath; +} + #pragma mark - Launchers - (void) _populateLaunchers diff --git a/Boxer/BXKeyboardEventTap.m b/Boxer/BXKeyboardEventTap.m index c514fd3f..adcbdcc7 100644 --- a/Boxer/BXKeyboardEventTap.m +++ b/Boxer/BXKeyboardEventTap.m @@ -104,7 +104,7 @@ - (void) setUsesDedicatedThread: (BOOL)usesDedicatedThread + (BOOL) canCaptureKeyEvents { - return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)@{(__bridge NSString*)kAXTrustedCheckOptionPrompt: @YES}); + return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)@{(__bridge NSString*)kAXTrustedCheckOptionPrompt: @NO}); } - (BXKeyboardEventTapStatus) _reportedStatusOfEventTap @@ -156,16 +156,22 @@ - (BOOL) _installEventTapOnCurrentThread { @synchronized(self) { - //Captures keyup and keydown events. We use this for intercepting OS X hotkeys. - CGEventMask keyEvents = CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventKeyDown); - //Captures system-defined events. We use this for intercepting media keys. CGEventMask systemEvents = CGEventMaskBit(NX_SYSDEFINED); + CGEventMask eventsToCapture = systemEvents; + + //Captures keyup and keydown events. We use this for intercepting OS X hotkeys. + //Only ask for these when Accessibility has already been granted: on recent macOS + //versions, repeatedly attempting a privileged tap can repeatedly trigger TCC prompts. + if (self.class.canCaptureKeyEvents) + { + eventsToCapture |= CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventKeyDown); + } _tap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, - keyEvents | systemEvents, + eventsToCapture, _handleEventFromTap, (__bridge void *)self); @@ -241,6 +247,13 @@ - (void) _startTapping { if (self.status == BXKeyboardEventTapNotTapping) { + if (!self.class.canCaptureKeyEvents) + { + self.status = BXKeyboardEventTapNotTapping; + [self.delegate eventTapDidFinishAttaching: self]; + return; + } + self.status = BXKeyboardEventTapInstalling; if (self.usesDedicatedThread) { diff --git a/Boxer/BXMIDIConfig.cpp b/Boxer/BXMIDIConfig.cpp index 2ef48f66..f65fae08 100644 --- a/Boxer/BXMIDIConfig.cpp +++ b/Boxer/BXMIDIConfig.cpp @@ -10,7 +10,7 @@ #include "string_utils.h" -static void mt32_init(MAYBE_UNUSED Section *secprop) +static void mt32_init(Section * /*secprop*/) {} static void init_mt32_dosbox_settings(Section_prop &sec_prop) @@ -61,7 +61,7 @@ static void init_mt32_dosbox_settings(Section_prop &sec_prop) Pint->Set_help("MT-32 reverb level"); } -void BXMIDIMT32_AddConfigSection(Config *conf) +void BXMIDIMT32_AddConfigSection(const config_ptr_t &conf) { assert(conf); Section_prop *sec_prop = conf->AddSection_prop("mt32", &mt32_init); diff --git a/Boxer/BXMIDIConfig.hpp b/Boxer/BXMIDIConfig.hpp index 47891312..2815b20f 100644 --- a/Boxer/BXMIDIConfig.hpp +++ b/Boxer/BXMIDIConfig.hpp @@ -12,6 +12,6 @@ #include "control.h" #include "midi.h" -extern void BXMIDIMT32_AddConfigSection(Config *conf); +extern void BXMIDIMT32_AddConfigSection(const config_ptr_t &conf); #endif /* BXMIDIConfig_hpp */ diff --git a/Boxer/BXSession.m b/Boxer/BXSession.m index 2198d670..51b9d87c 100644 --- a/Boxer/BXSession.m +++ b/Boxer/BXSession.m @@ -72,6 +72,39 @@ //How many recently-launched programs the session should track before it discards older ones. #define BXRecentProgramsLimit 10 + +static NSString *BXSessionCrashDumpDriveTypeDescription(BXDriveType type) +{ + switch (type) + { + case BXDriveHardDisk: return @"hard disk"; + case BXDriveFloppyDisk: return @"floppy disk"; + case BXDriveCDROM: return @"CD-ROM"; + case BXDriveVirtual: return @"virtual"; + case BXDriveAutodetect: return @"autodetect"; + } + + return @"unknown"; +} + +static NSString *BXSessionCrashDumpSafeFilenameComponent(NSString *string) +{ + NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; + [allowedCharacters addCharactersInString: @"-_. "]; + + NSMutableString *result = [NSMutableString stringWithCapacity: string.length]; + for (NSUInteger index = 0; index < string.length; index++) + { + unichar character = [string characterAtIndex: index]; + if ([allowedCharacters characterIsMember: character]) + [result appendFormat: @"%C", character]; + else + [result appendString: @"-"]; + } + + return result; +} + #pragma mark - #pragma mark Gamebox settings keys @@ -1828,7 +1861,7 @@ - (void) _startEmulator //Start up the emulator itself. if ([[NSUserDefaults standardUserDefaults] boolForKey: @"useMultithreadedEmulation"]) { - [self.emulator performSelectorInBackground: @selector(start) withObject: nil]; + [self performSelectorInBackground: @selector(_startEmulatorInBackground) withObject: nil]; } else { @@ -1852,11 +1885,38 @@ - (void) _startEmulator } } +- (void) _startEmulatorInBackground +{ + @autoreleasepool + { + @try + { + [self.emulator start]; + } + @catch (NSException *exception) + { + //Convert unrecoverable exceptions into errors and display them to the user. + if ([exception.name isEqualToString: BXEmulatorUnrecoverableException]) + { + [self performSelectorOnMainThread: @selector(_reportEmulatorException:) + withObject: exception + waitUntilDone: NO]; + } + //Throw all other exceptions upstairs. + else + { + @throw exception; + } + } + } +} + - (void) _reportEmulatorException: (NSException *)exception { //Ensure it gets logged to the console, if nothing else [NSApp reportException: exception]; //NSLog(@"Uncaught emulation exception: %@ (%@)", exception.debugDescription, exception.callStackSymbols); + NSURL *crashDumpURL = [self _writeCrashDumpForEmulatorException: exception]; NSString *errorMessage; NSString *currentProcessName = self.processDisplayName; @@ -1874,24 +1934,49 @@ - (void) _reportEmulatorException: (NSException *)exception } NSString *suggestion = NSLocalizedString(@"If this continues to occur after relaunching, please send us an error report.", @"Suggestion text shown in alert when an unrecoverable emulation error is encountered."); + if (crashDumpURL) + { + NSString *dumpSuggestionFormat = NSLocalizedString(@"%@\n\nA crash dump has been saved to:\n%@", + @"Suggestion text shown in alert when an unrecoverable emulation error is encountered and a crash dump was saved. The first placeholder is the normal suggestion and the second is the crash dump path."); + suggestion = [NSString stringWithFormat: dumpSuggestionFormat, suggestion, crashDumpURL.path]; + } NSArray *options = @[NSLocalizedString(@"Relaunch", @"Button to restart the current session, shown in alert when Boxer encounters an unrecoverable emulation error."), NSLocalizedString(@"Close", @"Button to close the current session, shown in alert when Boxer encounters an unrecoverable emulation error."), - NSLocalizedString(@"Send Report…", @"Button to open the issue tracker, shown in alert when Boxer encounters an unrecoverable emulation error."), + NSLocalizedString(@"Save Report…", @"Button to save a local error report, shown in alert when Boxer encounters an unrecoverable emulation error."), ]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage, - NSLocalizedRecoverySuggestionErrorKey: suggestion, - NSLocalizedRecoveryOptionsErrorKey: options, - NSRecoveryAttempterErrorKey: self, - @"exception": exception, - }; + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary: @{NSLocalizedDescriptionKey: errorMessage, + NSLocalizedRecoverySuggestionErrorKey: suggestion, + NSLocalizedRecoveryOptionsErrorKey: options, + NSRecoveryAttempterErrorKey: self, + @"exception": exception, + }]; + if (crashDumpURL) + { + [userInfo setObject: crashDumpURL forKey: @"crashDumpURL"]; + } NSError *userError = [NSError errorWithDomain: BXEmulatorErrorDomain code: BXEmulatorUnrecoverableError userInfo: userInfo]; + NSURL *reportURL = [(BXBaseAppController *)[NSApp delegate] writeIssueForError: userError + inSession: self + revealInFinder: NO]; + if (reportURL) + { + [userInfo setObject: reportURL forKey: @"reportURL"]; + + NSString *reportSuggestionFormat = NSLocalizedString(@"%@\n\nA local error report has been saved to:\n%@", + @"Suggestion text shown in alert when an unrecoverable emulation error is encountered and a local report was saved. The first placeholder is the normal suggestion and the second is the report path."); + [userInfo setObject: [NSString stringWithFormat: reportSuggestionFormat, suggestion, reportURL.path] + forKey: NSLocalizedRecoverySuggestionErrorKey]; + userError = [NSError errorWithDomain: BXEmulatorErrorDomain + code: BXEmulatorUnrecoverableError + userInfo: userInfo]; + } [self presentError: userError modalForWindow: self.windowForSheet @@ -1900,13 +1985,142 @@ - (void) _reportEmulatorException: (NSException *)exception contextInfo: NULL]; } +- (NSURL *) _writeCrashDumpForEmulatorException: (NSException *)exception +{ + NSFileManager *manager = [NSFileManager defaultManager]; + NSURL *applicationSupportURL = [manager URLsForDirectory: NSApplicationSupportDirectory + inDomains: NSUserDomainMask].firstObject; + if (!applicationSupportURL) + return nil; + + NSURL *dumpFolderURL = [[applicationSupportURL URLByAppendingPathComponent: @"Boxer" + isDirectory: YES] URLByAppendingPathComponent: @"Crash Dumps" + isDirectory: YES]; + + NSError *folderError = nil; + if (![manager createDirectoryAtURL: dumpFolderURL + withIntermediateDirectories: YES + attributes: nil + error: &folderError]) + { + NSLog(@"Could not create Boxer crash dump folder at %@: %@", dumpFolderURL.path, folderError); + return nil; + } + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier: @"en_US_POSIX"]; + dateFormatter.dateFormat = @"yyyy-MM-dd HH.mm.ss"; + + NSString *processName = self.processDisplayName ?: @"MS-DOS Prompt"; + NSString *safeProcessName = BXSessionCrashDumpSafeFilenameComponent(processName); + NSString *fileName = [NSString stringWithFormat: @"Boxer Emulator Crash %@ - %@.txt", + [dateFormatter stringFromDate: [NSDate date]], + safeProcessName]; + NSURL *dumpURL = [dumpFolderURL URLByAppendingPathComponent: fileName]; + + NSMutableString *dump = [NSMutableString string]; + [dump appendString: @"Boxer Emulator Crash Dump\n"]; + [dump appendString: @"=========================\n\n"]; + [dump appendFormat: @"Created: %@\n", [NSDate date]]; + [dump appendFormat: @"Boxer version: %@ (%@)\n", + [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleShortVersionString"] ?: @"unknown", + [[NSBundle mainBundle] objectForInfoDictionaryKey: (NSString *)kCFBundleVersionKey] ?: @"unknown"]; + [dump appendFormat: @"Process: %@\n", processName]; + [dump appendFormat: @"Session: %@\n", self.displayName ?: @"unknown"]; + [dump appendFormat: @"Session file URL: %@\n", self.fileURL.path ?: @"none"]; + [dump appendFormat: @"Target URL: %@\n", self.targetURL.path ?: @"none"]; + [dump appendFormat: @"Launched program URL: %@\n", self.launchedProgramURL.path ?: @"none"]; + [dump appendFormat: @"Current DOS directory: %@\n", self.emulator.currentDirectory ?: @"unknown"]; + [dump appendFormat: @"Current DOS directory URL: %@\n", self.emulator.currentDirectoryURL.path ?: @"unknown"]; + [dump appendString: @"\nException\n"]; + [dump appendString: @"---------\n"]; + [dump appendFormat: @"Name: %@\n", exception.name]; + [dump appendFormat: @"Reason: %@\n", exception.reason ?: @"none"]; + [dump appendFormat: @"Source file: %@\n", [exception.userInfo objectForKey: @"file"] ?: @"unknown"]; + [dump appendFormat: @"Function: %@\n", [exception.userInfo objectForKey: @"function"] ?: @"unknown"]; + [dump appendFormat: @"Line: %@\n", [exception.userInfo objectForKey: @"line"] ?: @"unknown"]; + + [dump appendString: @"\nRunning DOS processes\n"]; + [dump appendString: @"---------------------\n"]; + NSArray *> *runningProcesses = self.emulator.runningProcesses; + if (runningProcesses.count) + { + for (NSDictionary *processInfo in runningProcesses) + { + NSString *dosPath = [processInfo objectForKey: BXEmulatorDOSPathKey] ?: @"unknown"; + NSString *args = [processInfo objectForKey: BXEmulatorLaunchArgumentsKey] ?: @""; + [dump appendFormat: @"%@ %@\n", dosPath, args]; + } + } + else + { + [dump appendString: @"none\n"]; + } + + [dump appendString: @"\nMounted DOS drives\n"]; + [dump appendString: @"------------------\n"]; + NSArray *mountedDrives = self.emulator.mountedDrives; + if (mountedDrives.count) + { + for (BXDrive *drive in mountedDrives) + { + [dump appendFormat: @"%@ (%@): title=%@ source=%@ mountPoint=%@ shadow=%@ readOnly=%@\n", + drive.letter ?: @"?", + BXSessionCrashDumpDriveTypeDescription(drive.type), + drive.title ?: @"unknown", + drive.sourceURL.path ?: @"none", + drive.mountPointURL.path ?: @"none", + drive.shadowURL.path ?: @"none", + drive.isReadOnly ? @"YES" : @"NO"]; + } + } + else + { + [dump appendString: @"none\n"]; + } + + [dump appendString: @"\nException call stack symbols\n"]; + [dump appendString: @"----------------------------\n"]; + NSArray *exceptionStack = exception.callStackSymbols; + if (exceptionStack.count) + [dump appendFormat: @"%@\n", [exceptionStack componentsJoinedByString: @"\n"]]; + else + [dump appendString: @"none\n"]; + + [dump appendString: @"\nCurrent thread call stack symbols\n"]; + [dump appendString: @"---------------------------------\n"]; + [dump appendFormat: @"%@\n", [NSThread.callStackSymbols componentsJoinedByString: @"\n"]]; + + NSError *writeError = nil; + if (![dump writeToURL: dumpURL + atomically: YES + encoding: NSUTF8StringEncoding + error: &writeError]) + { + NSLog(@"Could not write Boxer crash dump to %@: %@", dumpURL.path, writeError); + return nil; + } + + NSLog(@"Saved Boxer emulator crash dump to %@", dumpURL.path); + return dumpURL; +} + - (void) attemptRecoveryFromError: (NSError *)error optionIndex: (NSUInteger)recoveryOptionIndex delegate: (id)delegate didRecoverSelector: (SEL)didRecoverSelector contextInfo: (void *)contextInfo { - BOOL didRecover; + BOOL didRecover = [self attemptRecoveryFromError: error optionIndex: recoveryOptionIndex]; + if (delegate && didRecoverSelector) + { + [delegate performSelector: didRecoverSelector withValues: &didRecover, &contextInfo]; + } +} + +- (BOOL) attemptRecoveryFromError: (NSError *)error + optionIndex: (NSUInteger)recoveryOptionIndex +{ if ([error matchesDomain: BXEmulatorErrorDomain code: BXEmulatorUnrecoverableError]) { switch(recoveryOptionIndex) @@ -1919,16 +2133,23 @@ - (void) attemptRecoveryFromError: (NSError *)error [self close]; break; case 2: //Report - [(BXBaseAppController *)[NSApp delegate] reportIssueForError: error inSession: self]; - [self close]; + { + NSURL *reportURL = [error.userInfo objectForKey: @"reportURL"]; + if (reportURL) + { + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: @[reportURL]]; + } + else + { + [(BXBaseAppController *)[NSApp delegate] reportIssueForError: error inSession: self]; + } break; + } } + return YES; } - else - { - didRecover = NO; - } - [delegate performSelector: didRecoverSelector withValues: &didRecover, &contextInfo]; + + return NO; } - (void) _mountDrivesForSession diff --git a/Boxer/BXSessionPrivate.h b/Boxer/BXSessionPrivate.h index 439ece98..ec252ae5 100644 --- a/Boxer/BXSessionPrivate.h +++ b/Boxer/BXSessionPrivate.h @@ -122,6 +122,9 @@ extern ADBUserNotificationType const BXGameImportedNotificationType NS_SWIFT_NAM /// Called if DOSBox encounters an unrecoverable error and throws an exception. - (void) _reportEmulatorException: (NSException *)exception; +/// Starts the emulator on a background thread and reports unrecoverable emulator exceptions on the main thread. +- (void) _startEmulatorInBackground; + /// Callback for close alert. Confirms document close when window is closed or application is shut down. - (void) _closeAlertDidEnd: (BXCloseAlert *)alert diff --git a/DOSBox-Staging b/DOSBox-Staging index 0ae2d171..5152357a 160000 --- a/DOSBox-Staging +++ b/DOSBox-Staging @@ -1 +1 @@ -Subproject commit 0ae2d171a2ce18054fed5cc73930dbe96db9c352 +Subproject commit 5152357a7dc6c3e12acec35662eb376429621e2a