From 2c88b5124ee082e03340f10117868434859b9f5c Mon Sep 17 00:00:00 2001 From: David Seto <67933197+dtseto@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:34:57 -0700 Subject: [PATCH 1/7] Update Boxer signing settings --- Boxer.xcodeproj/project.pbxproj | 66 ++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/Boxer.xcodeproj/project.pbxproj b/Boxer.xcodeproj/project.pbxproj index d73a1f93..943b9f3e 100644 --- a/Boxer.xcodeproj/project.pbxproj +++ b/Boxer.xcodeproj/project.pbxproj @@ -5138,11 +5138,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 +5173,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 +5190,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\"", @@ -5213,9 +5221,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 +5241,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 +5266,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 +5284,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 +5303,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 +5337,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 +5357,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; @@ -5352,12 +5374,14 @@ 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"; 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,6 +5403,7 @@ "\"$(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"; @@ -5386,9 +5411,11 @@ MARKETING_VERSION = "2.0.0-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; @@ -5403,13 +5430,15 @@ 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"; 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\"", @@ -5428,6 +5457,7 @@ "\"$(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"; @@ -5435,9 +5465,11 @@ MARKETING_VERSION = "2.0.0-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; @@ -5469,8 +5501,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 +5520,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; From 6d0a177f1eef9ba148dda479ddcacad38994c074 Mon Sep 17 00:00:00 2001 From: David Seto <67933197+dtseto@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:59:00 -0700 Subject: [PATCH 2/7] fixesdosboxstaging781 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update DOSBox-Staging core toward v0.78.1 for Boxer Merge official DOSBox-Staging v0.78.1 into Boxer’s boxer-compat core while preserving Boxer’s Cocoa/Metal frontend and host integration hooks. Core merge: - Merged upstream DOSBox-Staging v0.78.1 into the existing MaddTheSane boxer-compat submodule instead of replacing the source tree wholesale. - Resolved conflicts in integration-sensitive DOSBox files: - include/dosbox.h - src/dos/dos.cpp - src/dos/dos_devices.cpp - src/dos/dos_keyboard_layout.cpp - src/hardware/joystick.cpp - src/hardware/keyboard.cpp - src/hardware/mixer.cpp - src/hardware/ne2000.cpp - src/libs/decoders/stb_vorbis.h - Kept upstream emulator behavior and API changes while reapplying Boxer-specific hooks where needed. Preserved Boxer integrations: - Kept Boxer’s BXCoalface/BXCoalfaceAudio hooks available to DOSBox core code. - Preserved Boxer joystick/gameport integration: - gameport timing can still be toggled by Boxer at runtime - Boxer is still notified when DOS software probes joystick port 0x201 - upstream 0.78.1 joystick visibility model is retained - Preserved Boxer keyboard layout integration: - Boxer can still provide the preferred host keyboard layout for DOSBox auto keyboard layout selection - upstream code-page parsing safety checks are retained - Preserved Boxer mixer integration: - DOSBox mixer volume calculations use Boxer’s master volume callbacks - Boxer’s volume update hook now iterates upstream’s map/shared_ptr mixer channel storage - added a name-based MIXER_DelChannel helper for Boxer’s MIDI channel cleanup - Preserved Boxer MIDI/MT-32 routing: - updated BXMIDIMT32_AddConfigSection to match upstream’s config_ptr_t API - did not modify Boxer-MT32Emu.xcodeproj - Preserved Boxer DOS filesystem integration: - updated FAT image mounting for the new fatDrive read-only flag - retained writable behavior for Boxer-created floppy image drives Boxer app integration fixes: - Updated BXEmulator to work with upstream’s std::unique_ptr-owned global Config object. - Updated joystick support detection for upstream’s renamed/split joystick enum: JOY_DISABLED, JOY_NONE_FOUND, and JOY_ONLY_FOR_MAPPING. - Updated BXEmulator+BXAudio for upstream mixer_channel_t shared_ptr ownership. - Updated BXEmulator+BXDOSFileSystem for the new fatDrive constructor. - Updated BXMIDIConfig callback signatures for upstream config ownership. Xcode project updates: - Added new DOSBox-Staging 0.78.1 source files to both Boxer source build phases: - DOSBox-Staging/src/hardware/ide.cpp - DOSBox-Staging/src/dos/program_serial.cpp - DOSBox-Staging/src/dos/program_placeholder.cpp - DOSBox-Staging/src/libs/ghc/fs_std_impl.cpp - DOSBox-Staging/src/misc/ethernet.cpp - DOSBox-Staging/src/libs/whereami/whereami.c - Added upstream GHC filesystem and whereami groups/files to the project. - Added upstream license files for the new vendored libraries to resources. Compatibility/build fixes: - Adjusted new upstream includes for Boxer’s Xcode/header-map layout. - Added GHC filesystem forwarding headers under DOSBox-Staging/include/ghc. - Replaced a few C++17-only conveniences with forms compatible with the current Boxer build setup where needed: - std::is_same_v / std::is_unsigned_v -> ::value - std::pair CTAD -> std::make_pair - lock_guard/unique_lock CTAD -> explicit std::mutex template arguments - atomic copy-style initialization -> brace initialization - structured bindings in mixer loops -> explicit map iteration Validation: - Built Boxer for arm64 after adding the missing upstream source files and header paths. - Build now compiles with the updated DOSBox-Staging v0.78.1 core integrated into Boxer’s existing macOS frontend. Notes: - Vendor/DDHidLib, Vendor/MT32Emu, and Vendor/OpenEmuShaders already had unrelated modified submodule state and were not part of this DOSBox merge. - Boxer-MT32Emu.xcodeproj was intentionally left unchanged. --- Boxer.xcodeproj/project.pbxproj | 100 ++++++++++++++++++++++++++++ Boxer/BXEmulator+BXAudio.mm | 8 +-- Boxer/BXEmulator+BXDOSFileSystem.mm | 2 +- Boxer/BXEmulator.mm | 13 ++-- Boxer/BXMIDIConfig.cpp | 4 +- Boxer/BXMIDIConfig.hpp | 2 +- 6 files changed, 115 insertions(+), 14 deletions(-) diff --git a/Boxer.xcodeproj/project.pbxproj b/Boxer.xcodeproj/project.pbxproj index 943b9f3e..6ca1a1b5 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 */, @@ -5213,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; @@ -5370,6 +5466,7 @@ 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; @@ -5426,6 +5523,7 @@ 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; @@ -5455,6 +5553,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 = Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; 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..6cac2d6f 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. @@ -1055,10 +1057,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/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 */ From 39995d5a664dd582d8a6d91d60367bdc8d01e118 Mon Sep 17 00:00:00 2001 From: David Seto <67933197+dtseto@users.noreply.github.com> Date: Sun, 16 Aug 2026 15:59:52 -0700 Subject: [PATCH 3/7] fixes partial fixes to accessability spam --- Boxer/BXKeyboardEventTap.m | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) 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) { From 3eb5394ad66e55629c2f4cc1d9d298df838417ae Mon Sep 17 00:00:00 2001 From: David Seto <67933197+dtseto@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:26:56 -0700 Subject: [PATCH 4/7] Fix crash: re-introduced double gameport IO handler registration The 0.78.1 DOSBox-Staging merge accidentally left both the old Boxer early-install block and the new upstream is_visible block in the JOYSTICK constructor, causing ReadHandler.Install(0x201) to fire twice. The second call hits the already-installed guard and E_Exit()s with: io_read_f already installed port 513 Update submodule to remove the now-redundant early install block. Co-Authored-By: Claude Sonnet 4.6 --- DOSBox-Staging | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DOSBox-Staging b/DOSBox-Staging index 0ae2d171..072b6c76 160000 --- a/DOSBox-Staging +++ b/DOSBox-Staging @@ -1 +1 @@ -Subproject commit 0ae2d171a2ce18054fed5cc73930dbe96db9c352 +Subproject commit 072b6c764e7cbf6aec153bee7df6bedca11577f5 From c27a3c912bc4cfa969457535b936c70d908c6136 Mon Sep 17 00:00:00 2001 From: David Seto <67933197+dtseto@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:02:25 -0700 Subject: [PATCH 5/7] Fix DOSBox state leak when emulator session crashes ObjC exceptions don't trigger C++ stack unwinding, so _startDOSBox's cleanup block (SDL_Quit, control.reset, etc.) was skipped whenever a boxer_emulatorException or char* error was re-raised as an ObjC exception. This left the old Config and its IO state alive, which could interfere with the next session's initialization. Run the cleanup explicitly inside each error catch block before raising. Also null the JOYSTICK test pointer after delete in JOYSTICK_Destroy, and guard JOYSTICK_Init against double-init, as complementary hardening. Co-Authored-By: Claude Sonnet 4.6 --- Boxer/BXEmulator.mm | 22 ++++++++++++++++++++-- DOSBox-Staging | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Boxer/BXEmulator.mm b/Boxer/BXEmulator.mm index 6cac2d6f..9bf4d245 100644 --- a/Boxer/BXEmulator.mm +++ b/Boxer/BXEmulator.mm @@ -1037,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]; @@ -1045,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) diff --git a/DOSBox-Staging b/DOSBox-Staging index 072b6c76..5152357a 160000 --- a/DOSBox-Staging +++ b/DOSBox-Staging @@ -1 +1 @@ -Subproject commit 072b6c764e7cbf6aec153bee7df6bedca11577f5 +Subproject commit 5152357a7dc6c3e12acec35662eb376429621e2a From dcaa4c60d7e06a54bb61f7b625ae5f9af99febb3 Mon Sep 17 00:00:00 2001 From: David Seto <67933197+dtseto@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:18:20 -0700 Subject: [PATCH 6/7] fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit logging fixes and thread check Improve emulator crash reporting and gamebox launch compatibility Save unrecoverable emulator reports locally instead of opening the legacy boxerapp.com report URL, including session details, mounted drives, running DOS processes, exception metadata, and stack traces. Intercept old report URLs and convert them into local report files. Run multithreaded emulator startup through BXSession so unrecoverable emulator exceptions are caught on the background thread and reported back on the main thread. Add a legacy gamebox fallback for packages without a detected explicit C drive, using the default launcher’s top-level folder or the single visible top-level folder before falling back to the package root. Update project signing/version settings for the alpha build. --- Boxer.xcodeproj/project.pbxproj | 10 +- Boxer/Application Delegate/BXApplication.m | 160 +++++++++++ .../BXBaseAppController.h | 19 +- .../BXBaseAppController.m | 129 +++++++-- Boxer/BXGamebox.m | 52 +++- Boxer/BXSession.m | 253 ++++++++++++++++-- Boxer/BXSessionPrivate.h | 3 + 7 files changed, 575 insertions(+), 51 deletions(-) diff --git a/Boxer.xcodeproj/project.pbxproj b/Boxer.xcodeproj/project.pbxproj index 6ca1a1b5..8ae38718 100644 --- a/Boxer.xcodeproj/project.pbxproj +++ b/Boxer.xcodeproj/project.pbxproj @@ -5472,7 +5472,7 @@ CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO; CODE_SIGN_ENTITLEMENTS = Boxer/Boxer.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application: David Seto (GHTQCRGWTR)"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: David Seto (GHTQCRGWTR)"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; @@ -5505,7 +5505,7 @@ "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 = com.dtseto.boxer; @@ -5529,7 +5529,7 @@ CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO; CODE_SIGN_ENTITLEMENTS = Boxer/Boxer.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application: David Seto (GHTQCRGWTR)"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application: David Seto (GHTQCRGWTR)"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; @@ -5562,14 +5562,14 @@ "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 = com.dtseto.boxer; PRODUCT_NAME = Boxer; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; - SKIP_INSTALL = YES; + SKIP_INSTALL = NO; STRINGS_FILE_OUTPUT_ENCODING = binary; SWIFT_OBJC_BRIDGING_HEADER = "Boxer/Boxer-Bridging-Header.h"; SWIFT_VERSION = 5.0; 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/BXGamebox.m b/Boxer/BXGamebox.m index b9d5c066..8768e65d 100644 --- a/Boxer/BXGamebox.m +++ b/Boxer/BXGamebox.m @@ -80,6 +80,10 @@ - (void) _persistLaunchers; /// Save the game info back to the gamebox. - (void) _persistGameInfo; +/// Returns a plain top-level folder to use as the C drive for legacy gameboxes +/// that predate explicit C.harddisk drive folders. +- (NSURL *) _legacyRootDriveURL; + @end @@ -575,11 +579,12 @@ - (NSArray *) bundledDrives //In this case, use the base folder of the gamebox itself as drive C. if (!hasProperDriveC) { - NSLog(@"[BXGamebox] No explicit C drive found, using gamebox root as C:"); - BXDrive *drive = [BXDrive driveWithContentsOfURL: self.resourceURL + NSURL *rootDriveURL = self._legacyRootDriveURL ?: self.resourceURL; + NSLog(@"[BXGamebox] No explicit C drive found, using %@ as C:", rootDriveURL.lastPathComponent); + BXDrive *drive = [BXDrive driveWithContentsOfURL: rootDriveURL letter: @"C" type: BXDriveHardDisk]; - NSLog(@"[BXGamebox] → Created fallback C drive from %@", self.resourceURL.lastPathComponent); + NSLog(@"[BXGamebox] → Created fallback C drive from %@", rootDriveURL.lastPathComponent); [drives addObject: drive]; } else @@ -595,6 +600,47 @@ - (NSArray *) bundledDrives return [drives copy]; } +- (NSURL *) _legacyRootDriveURL +{ + NSURL *targetURL = self.legacyTargetURL; + if (targetURL && [targetURL isBasedInURL: self.resourceURL]) + { + NSString *relativePath = [targetURL pathRelativeToURL: self.resourceURL]; + NSString *topLevelName = relativePath.pathComponents.firstObject; + if (topLevelName.length && ![topLevelName isEqualToString: @"."]) + { + NSURL *topLevelURL = [self.resourceURL URLByAppendingPathComponent: topLevelName]; + if (topLevelURL.isDirectory) + return topLevelURL; + } + } + + NSMutableArray *candidateURLs = [NSMutableArray arrayWithCapacity: 1]; + NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL: self.resourceURL + includingPropertiesForKeys: @[NSURLIsDirectoryKey] + options: NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles + errorHandler: NULL]; + + NSSet *supportFilenames = [NSSet setWithObjects: + [BXConfigurationFileName stringByAppendingPathExtension: BXConfigurationFileExtension], + [BXGameInfoFileName stringByAppendingPathExtension: BXGameInfoFileExtension], + BXDocumentationFolderName, + BXTargetSymlinkName, + @"Icon\r", + nil]; + + for (NSURL *URL in enumerator) + { + if ([supportFilenames containsObject: URL.lastPathComponent]) + continue; + + if (URL.isDirectory) + [candidateURLs addObject: URL]; + } + + return (candidateURLs.count == 1) ? candidateURLs.firstObject : nil; +} + - (NSURL *) configurationFileURL { NSString *fileName = [BXConfigurationFileName stringByAppendingPathExtension: BXConfigurationFileExtension]; 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 From 5ca68dd93b025da6db1425dc961ef39fab72151e Mon Sep 17 00:00:00 2001 From: David Seto <67933197+dtseto@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:53:36 -0700 Subject: [PATCH 7/7] fixes --- Boxer/BXGamebox.m | 62 ++++++++++------------------------------------- 1 file changed, 13 insertions(+), 49 deletions(-) diff --git a/Boxer/BXGamebox.m b/Boxer/BXGamebox.m index 8768e65d..3d0b80da 100644 --- a/Boxer/BXGamebox.m +++ b/Boxer/BXGamebox.m @@ -80,10 +80,6 @@ - (void) _persistLaunchers; /// Save the game info back to the gamebox. - (void) _persistGameInfo; -/// Returns a plain top-level folder to use as the C drive for legacy gameboxes -/// that predate explicit C.harddisk drive folders. -- (NSURL *) _legacyRootDriveURL; - @end @@ -120,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 @@ -579,12 +585,11 @@ - (NSArray *) bundledDrives //In this case, use the base folder of the gamebox itself as drive C. if (!hasProperDriveC) { - NSURL *rootDriveURL = self._legacyRootDriveURL ?: self.resourceURL; - NSLog(@"[BXGamebox] No explicit C drive found, using %@ as C:", rootDriveURL.lastPathComponent); - BXDrive *drive = [BXDrive driveWithContentsOfURL: rootDriveURL + NSLog(@"[BXGamebox] No explicit C drive found, using gamebox root as C:"); + BXDrive *drive = [BXDrive driveWithContentsOfURL: self.resourceURL letter: @"C" type: BXDriveHardDisk]; - NSLog(@"[BXGamebox] → Created fallback C drive from %@", rootDriveURL.lastPathComponent); + NSLog(@"[BXGamebox] → Created fallback C drive from %@", self.resourceURL.lastPathComponent); [drives addObject: drive]; } else @@ -600,47 +605,6 @@ - (NSArray *) bundledDrives return [drives copy]; } -- (NSURL *) _legacyRootDriveURL -{ - NSURL *targetURL = self.legacyTargetURL; - if (targetURL && [targetURL isBasedInURL: self.resourceURL]) - { - NSString *relativePath = [targetURL pathRelativeToURL: self.resourceURL]; - NSString *topLevelName = relativePath.pathComponents.firstObject; - if (topLevelName.length && ![topLevelName isEqualToString: @"."]) - { - NSURL *topLevelURL = [self.resourceURL URLByAppendingPathComponent: topLevelName]; - if (topLevelURL.isDirectory) - return topLevelURL; - } - } - - NSMutableArray *candidateURLs = [NSMutableArray arrayWithCapacity: 1]; - NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL: self.resourceURL - includingPropertiesForKeys: @[NSURLIsDirectoryKey] - options: NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles - errorHandler: NULL]; - - NSSet *supportFilenames = [NSSet setWithObjects: - [BXConfigurationFileName stringByAppendingPathExtension: BXConfigurationFileExtension], - [BXGameInfoFileName stringByAppendingPathExtension: BXGameInfoFileExtension], - BXDocumentationFolderName, - BXTargetSymlinkName, - @"Icon\r", - nil]; - - for (NSURL *URL in enumerator) - { - if ([supportFilenames containsObject: URL.lastPathComponent]) - continue; - - if (URL.isDirectory) - [candidateURLs addObject: URL]; - } - - return (candidateURLs.count == 1) ? candidateURLs.firstObject : nil; -} - - (NSURL *) configurationFileURL { NSString *fileName = [BXConfigurationFileName stringByAppendingPathExtension: BXConfigurationFileExtension];