From ca55c3151c27b0710bf568568dd2f39e2cbb9372 Mon Sep 17 00:00:00 2001 From: mysterypaint <83567173+mysterypaintwo@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:14:16 -0500 Subject: [PATCH 1/5] fix: UB in Decode::RLE size header caused save-state load crashes --- nall/decode/rle.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nall/decode/rle.hpp b/nall/decode/rle.hpp index 4a6c67ca9..9bfb07cce 100644 --- a/nall/decode/rle.hpp +++ b/nall/decode/rle.hpp @@ -12,7 +12,7 @@ inline auto RLE(array_view input) -> vector { uint base = 0; uint64_t size = 0; - for(uint byte : range(8)) size |= load() << byte * 8; + for(uint byte : range(8)) size |= (uint64_t)load() << byte * 8; output.resize(size); auto read = [&]() -> uint64_t { From 2eac748653a09193d9a247b15a252b9a6c7129cb Mon Sep 17 00:00:00 2001 From: mysterypaint <83567173+mysterypaintwo@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:15:09 -0500 Subject: [PATCH 2/5] fix: Blank Input/Hotkeys mapping list on Settings open --- bsnes/target-bsnes/settings/settings.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bsnes/target-bsnes/settings/settings.cpp b/bsnes/target-bsnes/settings/settings.cpp index f45134494..ecee0296a 100644 --- a/bsnes/target-bsnes/settings/settings.cpp +++ b/bsnes/target-bsnes/settings/settings.cpp @@ -249,6 +249,9 @@ auto SettingsWindow::show(int index) -> void { if(index == 7) compatibilitySettings.setVisible(true); if(index == 8) driverSettings.setVisible(true); panelContainer.resize(); + inputSettings.refreshMappings(); + hotkeySettings.refreshMappings(); + Application::processEvents(); setVisibleAndActivate(); setFocused(); panelList.setFocused(); From a232430d4f2585c3269c912e0c73e5639b2f0d02 Mon Sep 17 00:00:00 2001 From: mysterypaint <83567173+mysterypaintwo@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:16:37 -0500 Subject: [PATCH 3/5] fix: [macOS] Handle double-click / "Open With" ROM launches --- .../presentation/presentation.cpp | 1 + bsnes/target-bsnes/resource/bsnes.plist | 130 ++++++++++++++++++ hiro/cocoa/application.cpp | 24 ++++ hiro/cocoa/application.hpp | 1 + hiro/core/application.cpp | 17 +++ hiro/core/application.hpp | 4 + 6 files changed, 177 insertions(+) diff --git a/bsnes/target-bsnes/presentation/presentation.cpp b/bsnes/target-bsnes/presentation/presentation.cpp index 5da54dddb..0b8ba65c8 100644 --- a/bsnes/target-bsnes/presentation/presentation.cpp +++ b/bsnes/target-bsnes/presentation/presentation.cpp @@ -279,6 +279,7 @@ auto Presentation::create() -> void { #if defined(PLATFORM_MACOS) Application::Cocoa::onAbout([&] { about.doActivate(); }); Application::Cocoa::onActivate([&] { setFocused(); }); + Application::Cocoa::onOpen([&](string location) { onDrop({location}); }); Application::Cocoa::onPreferences([&] { settingsWindow.show(2); }); Application::Cocoa::onQuit([&] { doClose(); }); #endif diff --git a/bsnes/target-bsnes/resource/bsnes.plist b/bsnes/target-bsnes/resource/bsnes.plist index e9565eebc..ef5b9562f 100644 --- a/bsnes/target-bsnes/resource/bsnes.plist +++ b/bsnes/target-bsnes/resource/bsnes.plist @@ -14,5 +14,135 @@ NSSupportsAutomaticGraphicsSwitching + CFBundlePackageType + APPL + CFBundleDocumentTypes + + + CFBundleTypeName + Super Famicom ROM Image + CFBundleTypeRole + Viewer + CFBundleTypeExtensions + + sfc + smc + + LSItemContentTypes + + org.byuu.bsnes-as.sfc-rom + + LSHandlerRank + Alternate + + + CFBundleTypeName + Game Boy ROM Image + CFBundleTypeRole + Viewer + CFBundleTypeExtensions + + gb + gbc + + LSItemContentTypes + + org.byuu.bsnes-as.gb-rom + + LSHandlerRank + Alternate + + + CFBundleTypeName + BS-X / Sufami Turbo ROM Image + CFBundleTypeRole + Viewer + CFBundleTypeExtensions + + bs + st + + LSItemContentTypes + + org.byuu.bsnes-as.bs-rom + org.byuu.bsnes-as.st-rom + + LSHandlerRank + Alternate + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + org.byuu.bsnes-as.sfc-rom + UTTypeDescription + Super Famicom ROM Image + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + sfc + smc + + + + + UTTypeIdentifier + org.byuu.bsnes-as.gb-rom + UTTypeDescription + Game Boy ROM Image + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + gb + gbc + + + + + UTTypeIdentifier + org.byuu.bsnes-as.bs-rom + UTTypeDescription + BS-X ROM Image + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + bs + + + + + UTTypeIdentifier + org.byuu.bsnes-as.st-rom + UTTypeDescription + Sufami Turbo ROM Image + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + st + + + + diff --git a/hiro/cocoa/application.cpp b/hiro/cocoa/application.cpp index 2afaeb9e8..1ca41a494 100755 --- a/hiro/cocoa/application.cpp +++ b/hiro/cocoa/application.cpp @@ -15,6 +15,17 @@ return NO; } +-(void) handleOpenDocumentsEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent { + using hiro::Application; + NSAppleEventDescriptor* fileList = [event paramDescriptorForKeyword:keyDirectObject]; + for(NSInteger i = 1; i <= [fileList numberOfItems]; i++) { + NSAppleEventDescriptor* item = [fileList descriptorAtIndex:i]; + NSURL* url = [NSURL URLWithString:[item stringValue]]; + string path = url && [url isFileURL] ? [[url path] UTF8String] : [[item stringValue] UTF8String]; + if(path) Application::Cocoa::doOpen(path); + } +} + -(void) run:(NSTimer*)timer { using hiro::Application; if(Application::state().onMain) Application::doMain(); @@ -100,6 +111,19 @@ auto pApplication::initialize() -> void { [NSApplication sharedApplication]; cocoaDelegate = [[CocoaDelegate alloc] init]; [NSApp setDelegate:cocoaDelegate]; + + //register as early as possible (before any of the application's own, potentially slow, + //startup work runs) so a cold-launch "open document" Apple Event isn't missed: on a cold + //launch (double-click / Finder "Open With"), macOS can deliver this event before the app + //has finished constructing its UI, and it will not be redelivered if missed + [[NSAppleEventManager sharedAppleEventManager] setEventHandler:cocoaDelegate + andSelector:@selector(handleOpenDocumentsEvent:withReplyEvent:) + forEventClass:kCoreEventClass + andEventID:kAEOpenDocuments]; + + //give the just-registered handler a chance to receive any open-document event that is + //already queued for this process before proceeding into the application's slow startup path + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; } } diff --git a/hiro/cocoa/application.hpp b/hiro/cocoa/application.hpp index 4a65a8813..3c5f7604b 100755 --- a/hiro/cocoa/application.hpp +++ b/hiro/cocoa/application.hpp @@ -4,6 +4,7 @@ } -(NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*)sender; -(BOOL) applicationShouldHandleReopen:(NSApplication*)application hasVisibleWindows:(BOOL)flag; +-(void) handleOpenDocumentsEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent; -(void) run:(NSTimer*)timer; -(void) updateInDock:(NSTimer*)timer; @end diff --git a/hiro/core/application.cpp b/hiro/core/application.cpp index 4d5f2ebc4..c31892b32 100755 --- a/hiro/core/application.cpp +++ b/hiro/core/application.cpp @@ -109,6 +109,14 @@ auto Application::Cocoa::doActivate() -> void { if(state().cocoa.onActivate) return state().cocoa.onActivate(); } +auto Application::Cocoa::doOpen(const string& location) -> void { + //the open-document event can arrive (via a raw Apple Event handler registered at the + //earliest possible moment) before the application has finished constructing its UI and + //registered a real onOpen callback: buffer it so it isn't silently dropped on a cold launch + if(state().cocoa.onOpen) return state().cocoa.onOpen(location); + state().cocoa.pendingOpen.append(location); +} + auto Application::Cocoa::doPreferences() -> void { if(state().cocoa.onPreferences) return state().cocoa.onPreferences(); } @@ -125,6 +133,15 @@ auto Application::Cocoa::onActivate(const function& callback) -> void { state().cocoa.onActivate = callback; } +auto Application::Cocoa::onOpen(const function& callback) -> void { + state().cocoa.onOpen = callback; + if(callback) { + auto pending = move(state().cocoa.pendingOpen); + state().cocoa.pendingOpen = {}; + for(auto& location : pending) callback(location); + } +} + auto Application::Cocoa::onPreferences(const function& callback) -> void { state().cocoa.onPreferences = callback; } diff --git a/hiro/core/application.hpp b/hiro/core/application.hpp index daae3ccca..65c49065d 100644 --- a/hiro/core/application.hpp +++ b/hiro/core/application.hpp @@ -28,10 +28,12 @@ struct Application { struct Cocoa { static auto doAbout() -> void; static auto doActivate() -> void; + static auto doOpen(const string& location) -> void; static auto doPreferences() -> void; static auto doQuit() -> void; static auto onAbout(const function& callback = {}) -> void; static auto onActivate(const function& callback = {}) -> void; + static auto onOpen(const function& callback = {}) -> void; static auto onPreferences(const function& callback = {}) -> void; static auto onQuit(const function& callback = {}) -> void; }; @@ -56,6 +58,8 @@ struct Application { struct Cocoa { function onAbout; function onActivate; + function onOpen; + vector pendingOpen; function onPreferences; function onQuit; } cocoa; From 3333133de3905d17955aef94f40483fcacf5c299 Mon Sep 17 00:00:00 2001 From: mysterypaint <83567173+mysterypaintwo@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:17:54 -0500 Subject: [PATCH 4/5] docs: add macOS build instructions --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/README.md b/README.md index 3cb6cea4d..d23931b44 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,55 @@ some rudimentary script function bindings between the bsnes emulator and AngelSc Download nightly binary builds here: https://dev.azure.com/ALttPO/alttpo/_build?definitionId=3&_a=summary&repositoryFilter=3 +Building on macOS +------------------ + +This repo builds natively on both Apple Silicon (arm64) and Intel (x86_64) Macs — the `GNUmakefile` +build system auto-detects your platform and architecture, and macOS needs no extra dependencies +beyond Xcode's command line tools (the UI is built directly against Cocoa; there's no GTK/Qt/Homebrew +requirement like the Linux build has). + +1. Install the Xcode command line tools if you haven't already: + ``` + $ xcode-select --install + ``` +2. Clone this repository: + ``` + $ git clone https://github.com/alttpo/bsnes-as.git + $ cd bsnes-as + ``` +3. Build it: + ``` + $ make -C bsnes build=performance -j$(sysctl -n hw.ncpu) + ``` + This produces `bsnes/out/bsnes.app`. `build=performance` matches what the official CI builds use + (`-O3`); the default `local=true` adds `-march=native`, which is fine as long as you run the + binary on the same Mac you built it on (drop it, e.g. `make -C bsnes build=performance local=false`, + if you intend to copy the binary to a different machine). +4. Run it directly from `bsnes/out/bsnes.app`, or install it system-wide: + ``` + $ make -C bsnes install + ``` + which copies it to `/Applications/bsnes.app` and sets up `~/Library/Application Support/bsnes/`. + +Notes: +- Discord Rich Presence integration is only available on `x86_64` (there's no arm64 build of the + Discord Game SDK bundled in `lib/`) — the build system detects this automatically via `arch` and + disables it (`-DDISCORD_DISABLE=1`) rather than failing, so this doesn't block an Apple Silicon build. +- A build you compile yourself isn't downloaded from the internet, so it never picks up the + `com.apple.quarantine` extended attribute and won't trigger Gatekeeper's "app is damaged" dialog — + that dialog is a downloaded-and-then-extracted-by-Archive-Utility artifact, not a build issue. If + you hit that dialog with a **pre-built** binary (see the MacOS Catalina section below for a version + of this), the fix is to extract with `tar`/a third-party archiver instead of Archive Utility.app, or + strip the attribute directly: `xattr -cr /path/to/bsnes.app`. +- If you're testing multiple downloaded/rebuilt copies of this app over time, be aware that macOS's + LaunchServices tracks them all under the same bundle identifier (`org.byuu.bsnes`). Stale duplicate + registrations (e.g. old copies still sitting in `~/Downloads` or `~/.Trash`) can cause file-open + requests (double-clicking a ROM, "Open With") to route unpredictably. Clean up old copies you no + longer need, or run `lsregister -gc` (found under + `/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/`) + to garbage-collect stale entries. + Screenshots --- From 03ceec281eafc736cc95e28c29dadf6967c9ac19 Mon Sep 17 00:00:00 2001 From: mysterypaint <83567173+mysterypaintwo@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:31:25 -0500 Subject: [PATCH 5/5] Update README.md --- README.md | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index d23931b44..1566a2cb0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ https://dev.azure.com/ALttPO/alttpo/_build?definitionId=3&_a=summary&repositoryF Building on macOS ------------------ -This repo builds natively on both Apple Silicon (arm64) and Intel (x86_64) Macs — the `GNUmakefile` +This repo builds natively on both Apple Silicon (arm64) and Intel (x86_64) Macs. The `GNUmakefile` build system auto-detects your platform and architecture, and macOS needs no extra dependencies beyond Xcode's command line tools (the UI is built directly against Cocoa; there's no GTK/Qt/Homebrew requirement like the Linux build has). @@ -37,23 +37,14 @@ requirement like the Linux build has). ``` which copies it to `/Applications/bsnes.app` and sets up `~/Library/Application Support/bsnes/`. -Notes: -- Discord Rich Presence integration is only available on `x86_64` (there's no arm64 build of the - Discord Game SDK bundled in `lib/`) — the build system detects this automatically via `arch` and - disables it (`-DDISCORD_DISABLE=1`) rather than failing, so this doesn't block an Apple Silicon build. -- A build you compile yourself isn't downloaded from the internet, so it never picks up the - `com.apple.quarantine` extended attribute and won't trigger Gatekeeper's "app is damaged" dialog — - that dialog is a downloaded-and-then-extracted-by-Archive-Utility artifact, not a build issue. If - you hit that dialog with a **pre-built** binary (see the MacOS Catalina section below for a version - of this), the fix is to extract with `tar`/a third-party archiver instead of Archive Utility.app, or - strip the attribute directly: `xattr -cr /path/to/bsnes.app`. -- If you're testing multiple downloaded/rebuilt copies of this app over time, be aware that macOS's - LaunchServices tracks them all under the same bundle identifier (`org.byuu.bsnes`). Stale duplicate - registrations (e.g. old copies still sitting in `~/Downloads` or `~/.Trash`) can cause file-open - requests (double-clicking a ROM, "Open With") to route unpredictably. Clean up old copies you no - longer need, or run `lsregister -gc` (found under - `/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/`) - to garbage-collect stale entries. +Note: Discord Rich Presence integration is only available on `x86_64` (there's no arm64 build of the +Discord Game SDK bundled in `lib/`). The build system detects this automatically via `arch` and +disables it (`-DDISCORD_DISABLE=1`) rather than failing, so this doesn't block an Apple Silicon build. + +A build you compile yourself is never downloaded from the internet, so it never picks up the +`com.apple.quarantine` extended attribute and won't trigger Gatekeeper's "app is damaged" dialog. If +you're working with a **pre-built** binary instead and hit that dialog, or ROMs won't open when +double-clicked, see [Troubleshooting](#troubleshooting) below. Screenshots --- @@ -139,5 +130,11 @@ Refer to [this document](angelscript.md) for details on the AngelScript interfac Troubleshooting === -For users of MacOS Catalina (10.15), be sure to extract the download archive using https://www.keka.io/en/ file -archiver. DO NOT use the built-in "Archive Utility.app". +**"bsnes is damaged and can't be opened"**: extract the downloaded archive with `tar` from Terminal, +or a third-party archiver like [Keka](https://www.keka.io/en/), not the built-in "Archive +Utility.app". If you've already extracted it, just run `xattr -cr /path/to/bsnes.app` instead of +re-extracting. + +**Double-clicking a ROM doesn't open it, or opens the wrong copy of bsnes**: this usually means +macOS has multiple old copies of bsnes registered from previous downloads. Delete old copies you no +longer need and re-launch the one you want to keep once.