Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,46 @@ 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/`.

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
---

Expand Down Expand Up @@ -90,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.
1 change: 1 addition & 0 deletions bsnes/target-bsnes/presentation/presentation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
130 changes: 130 additions & 0 deletions bsnes/target-bsnes/resource/bsnes.plist
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,135 @@
<true/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Super Famicom ROM Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>sfc</string>
<string>smc</string>
</array>
<key>LSItemContentTypes</key>
<array>
<string>org.byuu.bsnes-as.sfc-rom</string>
</array>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Game Boy ROM Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>gb</string>
<string>gbc</string>
</array>
<key>LSItemContentTypes</key>
<array>
<string>org.byuu.bsnes-as.gb-rom</string>
</array>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>BS-X / Sufami Turbo ROM Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>bs</string>
<string>st</string>
</array>
<key>LSItemContentTypes</key>
<array>
<string>org.byuu.bsnes-as.bs-rom</string>
<string>org.byuu.bsnes-as.st-rom</string>
</array>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.byuu.bsnes-as.sfc-rom</string>
<key>UTTypeDescription</key>
<string>Super Famicom ROM Image</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>sfc</string>
<string>smc</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.byuu.bsnes-as.gb-rom</string>
<key>UTTypeDescription</key>
<string>Game Boy ROM Image</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gb</string>
<string>gbc</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.byuu.bsnes-as.bs-rom</string>
<key>UTTypeDescription</key>
<string>BS-X ROM Image</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>bs</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.byuu.bsnes-as.st-rom</string>
<key>UTTypeDescription</key>
<string>Sufami Turbo ROM Image</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>st</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>
3 changes: 3 additions & 0 deletions bsnes/target-bsnes/settings/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
24 changes: 24 additions & 0 deletions hiro/cocoa/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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]];
}
}

Expand Down
1 change: 1 addition & 0 deletions hiro/cocoa/application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions hiro/core/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -125,6 +133,15 @@ auto Application::Cocoa::onActivate(const function<void ()>& callback) -> void {
state().cocoa.onActivate = callback;
}

auto Application::Cocoa::onOpen(const function<void (string)>& 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<void ()>& callback) -> void {
state().cocoa.onPreferences = callback;
}
Expand Down
4 changes: 4 additions & 0 deletions hiro/core/application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void ()>& callback = {}) -> void;
static auto onActivate(const function<void ()>& callback = {}) -> void;
static auto onOpen(const function<void (string)>& callback = {}) -> void;
static auto onPreferences(const function<void ()>& callback = {}) -> void;
static auto onQuit(const function<void ()>& callback = {}) -> void;
};
Expand All @@ -56,6 +58,8 @@ struct Application {
struct Cocoa {
function<void ()> onAbout;
function<void ()> onActivate;
function<void (string)> onOpen;
vector<string> pendingOpen;
function<void ()> onPreferences;
function<void ()> onQuit;
} cocoa;
Expand Down
2 changes: 1 addition & 1 deletion nall/decode/rle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ inline auto RLE(array_view<uint8_t> input) -> vector<uint8_t> {

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 {
Expand Down