Compare commits

...

92 Commits

Author SHA1 Message Date
Lior Halphon 8a234a25a3 Add silence detection to eliminate rounding artifacts when playing nothing but frequencies above Nyquist. Fixes #394 2025-08-18 21:27:01 +03:00
Lior Halphon 5b88346537 Work around an MSVCRT limitation, fixes cheat import on SDL Windows, fixes #716 2025-08-03 16:54:04 +03:00
Lior Halphon 20e5e18122 Update version to v1.0.2 2025-08-01 00:49:49 +03:00
Lior Halphon b948a1f3fd Fix Solarium beta 4 regressions 2025-08-01 00:49:41 +03:00
Lior Halphon 8215c03d62 Update assets.car, screw this format 2025-07-29 22:49:27 +03:00
Lior Halphon 8b2af8adf1 Replace non-car iOS icons with the correct versions 2025-07-27 19:09:35 +03:00
Lior Halphon 00f772b29d Merge branch 'master' of github.com:LIJI32/SameBoy 2025-07-27 13:23:58 +03:00
Lior Halphon 4107198548
Merge pull request #715 from MaddTheSane/patch-2
Update GBViewController.m
2025-07-27 12:26:34 +03:00
C.W. Betts e997ce0ce7
Update GBViewController.m
-[GBViewController didRotateFromInterfaceOrientation:] seems to want [super ...] called to it.
2025-07-26 23:14:45 -06:00
Lior Halphon 58c946f249 Fix accelerometer auto-rotating on iOS 16 and newer 2025-07-27 03:10:17 +03:00
Lior Halphon 0b57886491 Add an icon to hotswap 2025-07-23 23:42:34 +03:00
Lior Halphon 2db60c2b3f Add the missing camera entitlement even on unnotarized releases that don't use the hardened runtime 2025-07-23 23:42:19 +03:00
Lior Halphon f54bfae01f Make headers a target 2025-07-23 00:18:51 +03:00
Lior Halphon c40221abb6 Allow using the DISABLE_* flags when building specific subsets of the lib target 2025-07-22 23:32:00 +03:00
Lior Halphon eb38034b76 Ask for confirmation before reset 2025-07-22 00:10:19 +03:00
Lior Halphon c6a968ed74 Make sure to display an alert if saving a state failed 2025-07-22 00:03:37 +03:00
Lior Halphon e69f6b8579 Bug fix: GB_save_state always returned success 2025-07-21 23:53:54 +03:00
Lior Halphon cfbc7b481a An attempt to mitigate #703, don't reload the ROM or save states if not required; don't assume a new ROM if state failed to load 2025-07-21 23:45:06 +03:00
Lior Halphon 1dfcdffa71 Properly handled Inbox and Boot ROMs in GBROMManager 2025-07-20 22:44:09 +03:00
Lior Halphon 5b983bc7ad Minor optimization 2025-07-20 21:58:36 +03:00
Lior Halphon 634b90e4fc Fix rounding errors in calls to band_limited_update, tweak BL synth parameters. Should greatly improve audio quality, fixes #713 2025-07-18 13:53:02 +03:00
Lior Halphon b31cca77be Use a sinc filter 2025-07-18 13:32:25 +03:00
Lior Halphon 5b17b41e07 Escape translocation on launch so we can update ourselves. 2025-07-12 04:16:31 +03:00
Lior Halphon 239e0462c3 Use proper hooks 2025-07-12 01:25:40 +03:00
Lior Halphon 8505f00cdf Fix BL-synth master wave generation, that wasn't a square step... affects #713 2025-07-12 01:17:47 +03:00
Lior Halphon 08d58aa992 Fix a bug where controller-triggered rewind doesn't resume after reaching the buffer end 2025-07-11 23:14:03 +03:00
Lior Halphon 8cce6f7b13 Another macOS Regressionville™ bugfix 2025-07-11 18:19:19 +03:00
Lior Halphon ec8baa6329 Allow specifying a custom ibtool path 2025-07-11 18:03:36 +03:00
Lior Halphon 9bd84978cf Work around more Solarium bugs 2025-07-11 17:59:32 +03:00
Lior Halphon aa0fe30d5c Don't update the icon after an update on macOS 26, it's broken 2025-07-11 17:59:13 +03:00
Lior Halphon 6d6aafe887 Fix scrolling view in newer macOS versions 2025-07-11 14:46:16 +03:00
Lior Halphon 003e8914b1
Merge pull request #711 from MaddTheSane/patch-2
Update GBViewController.m
2025-07-08 00:06:08 +03:00
Lior Halphon d7e1672ae7 Always define typeof to __typeof__, unless targeting C23. Fixes #710 2025-07-08 00:04:07 +03:00
C.W. Betts 1157ff0f36
Update GBViewController.m
Use CGDataProviderCreateWithCFData instead of CGDataProviderCreateWithData.
2025-07-07 13:52:02 -06:00
Lior Halphon bdd4522ca9 Migrate the Windows build from GNUWin to Git Bash and ezwinports's Make 2025-07-07 22:34:45 +03:00
Lior Halphon 4abb5f3539 Fix AGB mixing – the bias should only be applied if the channel is connected to a terminal 2025-07-05 16:37:48 +03:00
Lior Halphon aff7f1706c Add turbo cap options to the core and frontends, improve frame skipping, replace iOS' turbo speed option with the new turbo cap. Addresses #708. 2025-07-04 14:41:34 +03:00
Lior Halphon 33d237706e
Merge pull request #709 from joshrad-dev/master
Add ability to save to photos on iOS
2025-06-29 23:05:20 +03:00
Lior Halphon a39efd31cf Change the default macOS palette to be the same as the other frontends 2025-06-29 22:42:45 +03:00
Lior Halphon 69bb4c3e95 Fix various iPadOS bugs 2025-06-29 20:17:05 +03:00
Lior Halphon ae2d68aaf3 iPad menu support 2025-06-29 18:48:47 +03:00
Lior Halphon e381ebd1ea Refine the tip animation 2025-06-28 21:29:26 +03:00
Lior Halphon 34c29b052e UI fixes in the cheats screen 2025-06-28 20:55:30 +03:00
Lior Halphon 4ebb108ae1 Fix it harder, remove UIGlassEffect as it's still very buggy 2025-06-28 15:58:39 +03:00
Lior Halphon d1da91da7c Fix tip rotation 2025-06-28 15:46:02 +03:00
Lior Halphon 490d63b26f Fix build break 2025-06-28 15:44:16 +03:00
Lior Halphon be63d7eaa3 Revert this hack, broken on iPhones. Maybe Apple will fix this regression eventually (lulz no) 2025-06-28 15:13:05 +03:00
Lior Halphon 15588a065f Fixed a bug where ROMs were moved instead of copied and vice versa, fixes #701 2025-06-28 15:04:14 +03:00
Lior Halphon d52a50353d Update the tips visuals 2025-06-28 14:40:42 +03:00
Lior Halphon 5d70f93920 Very basic iPad menu support 2025-06-28 13:58:43 +03:00
Lior Halphon f3cbc1990e Allow building on older SDKs 2025-06-28 13:06:38 +03:00
Lior Halphon 583c234953 Various iOS Ui improvements, especially on iOS 26 2025-06-28 00:11:46 +03:00
Lior Halphon 3f744254fd Update iOS settings icons, add liquid glass overlay 2025-06-27 17:02:55 +03:00
Lior Halphon 7abedaed4c Fix a bug where cheat search remains open after closing a ROM 2025-06-26 21:36:26 +03:00
Lior Halphon 42ffbd18d0 Fix a bug where a GBS file will not correctly play the first track unless explicitly switching to it. Reloading a GBS file is disabled because it's not supported. 2025-06-26 20:57:47 +03:00
Lior Halphon 8508eb7b7c Fix Mavericks regressions 2025-06-24 22:44:39 +03:00
Lior Halphon 00000971d7 Describe addresses correctly in unbanked portions of RAM and ROM 2025-06-24 21:53:18 +03:00
Lior Halphon 58bd40b833 Work around gazillion Solarium bugs 2025-06-24 21:29:58 +03:00
Lior Halphon cbaf5c4c4a Avoid non-main-thread calls 2025-06-24 21:29:05 +03:00
Lior Halphon 67d338164b Make text field insets work on macOS 26 2025-06-22 23:34:41 +03:00
Jawshoeadan 7bf8145a91 Add save to photos key in info.plist for GB camera pictures 2025-06-21 13:22:31 -07:00
Lior Halphon e043279500 Update the Cocoa UI to support Solarium (Memory viewer still not updated due to AppKit regression, let's see what the next beta has to say) 2025-06-21 14:50:12 +03:00
Lior Halphon 9d6f378d21 Add Icon Composer based icon for macOS 26 2025-06-13 22:54:10 +03:00
Lior Halphon bfb1092cbb Fix cheat search crashing on carts without RAM 2025-06-10 01:19:29 +03:00
Lior Halphon 282140822e Fix compatibility issues with macOS 26 NIB loading 2025-06-10 00:22:14 +03:00
Lior Halphon 19a1e3ec1a Add a vsync as an option to SDL, fixes #335 2025-06-08 20:19:09 +03:00
Lior Halphon 1ad8bad18c Add iOS rapid buttons, closes #702 2025-06-08 19:28:42 +03:00
Lior Halphon 9577cbce85 Added the option to force integer scaling to the Cocoa port (SDL had it for ages), closes #699 2025-06-08 11:51:34 +03:00
Lior Halphon 6dd2f609f2 Make the debugger console larger by default, fix misaligned line 2025-06-08 01:43:17 +03:00
Lior Halphon 976fe7a337 The sideview shouldn't automatically scroll 2025-06-08 01:30:17 +03:00
Lior Halphon 1400bd40e8 Slightly alter the behavior of the print command so values don't get zero-padding by default, fixes #687 2025-06-08 01:25:30 +03:00
Lior Halphon 6a97192e8c Slightly refine the last fix 2025-06-07 18:58:31 +03:00
Lior Halphon 42732b20eb Restore Alt+Zoom behavior 2025-06-07 18:49:45 +03:00
Lior Halphon f0a672c39e Make sure the CPU graph advanced correctly even in turbo mode 2025-06-07 14:24:35 +03:00
Lior Halphon d211120312 Make 100% CPU frames appear red 2025-06-07 14:11:10 +03:00
Lior Halphon 6ab1be654b Add CPU load graph to Cocoa, closes #654 2025-06-06 23:10:57 +03:00
Lior Halphon f706988171 Add CPU usage command (#654) 2025-06-06 19:26:25 +03:00
Lior Halphon bed9f8220c
Merge pull request #706 from Estus-Dev/Specify-size-of-block-header's-length-field
Specify size of block header's length field
2025-06-04 20:33:23 +03:00
Estus 750112f6dd
docs: specify size of block header's length field 2025-06-04 11:21:48 -06:00
Lior Halphon 63a02d90bc
Merge pull request #568 from hitomi-nakayama/warn-sdl-init-error
Warn user about SDL initialization failure
2025-06-02 23:40:54 +03:00
Lior Halphon 795fba1320
Merge branch 'master' into warn-sdl-init-error 2025-06-02 23:40:40 +03:00
Lior Halphon 1923c324d9 Slightly alter iOS behavior 2025-06-02 21:21:39 +03:00
Lior Halphon 6a24b9206f Remove the navigation string since it doesn't fit 2025-06-02 21:21:23 +03:00
Marcus Ziadé 30a8c4bf42 Add Vim menu navigation 2025-05-26 21:46:31 +03:00
Lior Halphon 152e242485 Fixed incorrect processing of GameShark codes 2025-05-26 00:51:16 +03:00
Lior Halphon c4e6161959
Merge pull request #681 from Jan200101/PR/mime-location
correct mimetype location to follow the shared mime info specification
2025-05-25 12:33:20 +03:00
Jan200101 1951df3476
correct mimetype location to follow the shared mime info specification 2025-04-27 16:16:13 +02:00
Lior Halphon 1cf84a5436 Ubuntu 20.04 is dead soon, replace with 22.04 2025-04-08 20:43:21 +03:00
Lior Halphon 81c29fa371 Make gb.h compatible with ANSI C++, fixes #698 2025-04-08 20:39:12 +03:00
Lior Halphon 8b27952680
Merge pull request #697 from bentley/libdl
Don’t look for libdl on OpenBSD
2025-04-05 13:26:43 +03:00
Anthony J. Bentley 3c58deb46f Don’t look for libdl on OpenBSD 2025-04-05 03:18:25 -06:00
Hitomi Nakayama d267d83cec Added warning for SDL init failure 2023-10-12 17:57:33 -07:00
130 changed files with 2585 additions and 769 deletions

View File

@ -11,7 +11,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, ubuntu-latest, ubuntu-20.04] os: [macos-latest, ubuntu-latest, ubuntu-22.04]
cc: [gcc, clang] cc: [gcc, clang]
include: include:
- os: macos-latest - os: macos-latest

View File

@ -22,9 +22,9 @@ BESS works by appending a detectable footer at the end of an existing save state
BESS uses a block format where each block contains the following header: BESS uses a block format where each block contains the following header:
| Offset | Content | | Offset | Content |
|--------|---------------------------------------| |--------|-----------------------------------------------------------|
| 0 | A four-letter ASCII identifier | | 0 | A four-letter ASCII identifier |
| 4 | Length of the block, excluding header | | 4 | Length of the block as a 32-bit integer, excluding header |
Every block is followed by another block, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure). Every block is followed by another block, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure).
@ -256,4 +256,4 @@ Other than previously specified required fail conditions, an implementation is f
* An invalid length of MBC (not a multiple of 3) * An invalid length of MBC (not a multiple of 3)
* A write outside the $0000-$7FFF and $A000-$BFFF ranges in the MBC block * A write outside the $0000-$7FFF and $A000-$BFFF ranges in the MBC block
* An SGB block on a save state targeting another model * An SGB block on a save state targeting another model
* An END block with non-zero length * An END block with non-zero length

BIN
Cocoa/Assets.car Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 420 B

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -103,7 +103,7 @@
</tableHeaderView> </tableHeaderView>
</scrollView> </scrollView>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KdM-lW-WbP"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KdM-lW-WbP">
<rect key="frame" x="371" y="25" width="96" height="32"/> <rect key="frame" x="371" y="24" width="96" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Search" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="t4Y-Ud-mJm"> <buttonCell key="cell" type="push" title="Search" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="t4Y-Ud-mJm">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -113,30 +113,30 @@
<action selector="search:" target="-2" id="7pG-JY-vEF"/> <action selector="search:" target="-2" id="7pG-JY-vEF"/>
</connections> </connections>
</button> </button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fTv-nr-5FT"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fTv-nr-5FT">
<rect key="frame" x="6" y="96" width="124" height="16"/> <rect key="frame" x="6" y="95" width="124" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Search Condition:" id="9C2-Xp-JIA"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" alignment="right" title="Search Condition:" id="9C2-Xp-JIA">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vMd-zb-8jT"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vMd-zb-8jT">
<rect key="frame" x="6" y="67" width="124" height="16"/> <rect key="frame" x="6" y="64" width="124" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Expression:" id="Jgg-sA-jjs"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" alignment="right" title="Expression:" id="Jgg-sA-jjs">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5Db-vP-S60"> <popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5Db-vP-S60">
<rect key="frame" x="133" y="119" width="197" height="25"/> <rect key="frame" x="133" y="120" width="197" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<popUpButtonCell key="cell" type="push" title="8-Bit" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="al4-Jb-OJB" id="dkg-V5-wsX"> <popUpButtonCell key="cell" type="push" title="8-Bit" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="al4-Jb-OJB" id="dkg-V5-wsX">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="message"/>
<menu key="menu" id="e5r-qR-pg7"> <menu key="menu" id="e5r-qR-pg7">
<items> <items>
<menuItem title="8-Bit" state="on" id="al4-Jb-OJB"/> <menuItem title="8-Bit" state="on" id="al4-Jb-OJB"/>
@ -151,7 +151,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<popUpButtonCell key="cell" type="push" title="Is Equal To…" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Vfb-Dg-Jkb" id="DPm-QO-c64"> <popUpButtonCell key="cell" type="push" title="Is Equal To…" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Vfb-Dg-Jkb" id="DPm-QO-c64">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="message"/>
<menu key="menu" id="Kg9-Rd-1GQ"> <menu key="menu" id="Kg9-Rd-1GQ">
<items> <items>
<menuItem title="Any" id="cEg-eI-4hb"/> <menuItem title="Any" id="cEg-eI-4hb"/>
@ -173,7 +173,7 @@
<action selector="conditionChanged:" target="-2" id="KF9-vz-yNC"/> <action selector="conditionChanged:" target="-2" id="KF9-vz-yNC"/>
</connections> </connections>
</popUpButton> </popUpButton>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q42-Vu-TJW"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q42-Vu-TJW">
<rect key="frame" x="334" y="93" width="126" height="21"/> <rect key="frame" x="334" y="93" width="126" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" continuous="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" title="$0" drawsBackground="YES" usesSingleLineMode="YES" id="WDs-GG-7Lc"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" continuous="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" title="$0" drawsBackground="YES" usesSingleLineMode="YES" id="WDs-GG-7Lc">
@ -189,8 +189,8 @@
<outlet property="delegate" destination="-2" id="1bO-hp-igc"/> <outlet property="delegate" destination="-2" id="1bO-hp-igc"/>
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XN7-BO-THS"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XN7-BO-THS">
<rect key="frame" x="136" y="64" width="324" height="21"/> <rect key="frame" x="136" y="62" width="324" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" enabled="NO" sendsActionOnEndEditing="YES" borderStyle="bezel" title="new == ($0)" drawsBackground="YES" usesSingleLineMode="YES" id="Krh-8w-4ug"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" enabled="NO" sendsActionOnEndEditing="YES" borderStyle="bezel" title="new == ($0)" drawsBackground="YES" usesSingleLineMode="YES" id="Krh-8w-4ug">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -205,7 +205,7 @@
</connections> </connections>
</textField> </textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tBa-6c-9AY"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tBa-6c-9AY">
<rect key="frame" x="13" y="25" width="96" height="32"/> <rect key="frame" x="13" y="24" width="96" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Reset" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Pge-SU-Y1n"> <buttonCell key="cell" type="push" title="Reset" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Pge-SU-Y1n">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -215,7 +215,7 @@
<action selector="reset:" target="-2" id="KCy-Ob-tlg"/> <action selector="reset:" target="-2" id="KCy-Ob-tlg"/>
</connections> </connections>
</button> </button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wbN-MX-lEy"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wbN-MX-lEy">
<rect key="frame" x="-3" y="4" width="486" height="14"/> <rect key="frame" x="-3" y="4" width="486" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Status" id="CM3-4U-qao"> <textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Status" id="CM3-4U-qao">
@ -225,7 +225,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o1I-5D-V4k"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o1I-5D-V4k">
<rect key="frame" x="264" y="25" width="110" height="32"/> <rect key="frame" x="264" y="24" width="110" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="push" title="Add Cheat" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GGV-nm-ASn"> <buttonCell key="cell" type="push" title="Add Cheat" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GGV-nm-ASn">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -235,10 +235,10 @@
<action selector="addCheat:" target="-2" id="7ax-kM-TeV"/> <action selector="addCheat:" target="-2" id="7ax-kM-TeV"/>
</connections> </connections>
</button> </button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="veO-Qn-0Sz"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="veO-Qn-0Sz">
<rect key="frame" x="6" y="126" width="124" height="16"/> <rect key="frame" x="6" y="126" width="124" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Data Type:" id="KuT-rz-eHm"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Data Type:" id="KuT-rz-eHm">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 448 B

View File

@ -3,6 +3,7 @@
#import "GBImageView.h" #import "GBImageView.h"
#import "GBSplitView.h" #import "GBSplitView.h"
#import "GBVisualizerView.h" #import "GBVisualizerView.h"
#import "GBCPUView.h"
#import "GBOSDView.h" #import "GBOSDView.h"
#import "GBDebuggerButton.h" #import "GBDebuggerButton.h"
@ -84,6 +85,8 @@ enum model {
@property IBOutlet NSScrollView *debuggerScrollView; @property IBOutlet NSScrollView *debuggerScrollView;
@property IBOutlet NSView *debugBar; @property IBOutlet NSView *debugBar;
@property IBOutlet GBCPUView *cpuView;
@property IBOutlet NSTextField *cpuCounter;
+ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale; + (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale;
- (void) performAtomicBlock: (void (^)())block; - (void) performAtomicBlock: (void (^)())block;

View File

@ -288,6 +288,7 @@ static void debuggerReloadCallback(GB_gameboy_t *gb)
GB_set_user_data(&_gb, (__bridge void *)(self)); GB_set_user_data(&_gb, (__bridge void *)(self));
GB_set_boot_rom_load_callback(&_gb, (GB_boot_rom_load_callback_t)boot_rom_load); GB_set_boot_rom_load_callback(&_gb, (GB_boot_rom_load_callback_t)boot_rom_load);
GB_set_vblank_callback(&_gb, (GB_vblank_callback_t) vblank); GB_set_vblank_callback(&_gb, (GB_vblank_callback_t) vblank);
GB_set_enable_skipped_frame_vblank_callbacks(&_gb, true);
GB_set_log_callback(&_gb, (GB_log_callback_t) consoleLog); GB_set_log_callback(&_gb, (GB_log_callback_t) consoleLog);
GB_set_input_callback(&_gb, (GB_input_callback_t) consoleInput); GB_set_input_callback(&_gb, (GB_input_callback_t) consoleInput);
GB_set_async_input_callback(&_gb, (GB_input_callback_t) asyncConsoleInput); GB_set_async_input_callback(&_gb, (GB_input_callback_t) asyncConsoleInput);
@ -345,6 +346,12 @@ static void debuggerReloadCallback(GB_gameboy_t *gb)
[self observeStandardDefaultsKey:@"GBDebuggerFontSize" withBlock:^(NSString *value) { [self observeStandardDefaultsKey:@"GBDebuggerFontSize" withBlock:^(NSString *value) {
[weakSelf updateFonts]; [weakSelf updateFonts];
}]; }];
[self observeStandardDefaultsKey:@"GBTurboCap" withBlock:^(NSNumber *value) {
if (!_master) {
GB_set_turbo_cap(gb, value.doubleValue);
}
}];
} }
- (void)updateMinSize - (void)updateMinSize
@ -359,11 +366,29 @@ static void debuggerReloadCallback(GB_gameboy_t *gb)
- (void)vblankWithType:(GB_vblank_type_t)type - (void)vblankWithType:(GB_vblank_type_t)type
{ {
if (type == GB_VBLANK_TYPE_SKIPPED_FRAME) {
double frameUsage = GB_debugger_get_frame_cpu_usage(&_gb);
[_cpuView addSample:frameUsage];
return;
}
if (_gbsVisualizer) { if (_gbsVisualizer) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[_gbsVisualizer setNeedsDisplay:true]; [_gbsVisualizer setNeedsDisplay:true];
}); });
} }
double frameUsage = GB_debugger_get_frame_cpu_usage(&_gb);
[_cpuView addSample:frameUsage];
if (self.consoleWindow.visible) {
double secondUsage = GB_debugger_get_second_cpu_usage(&_gb);
dispatch_async(dispatch_get_main_queue(), ^{
[_cpuView setNeedsDisplay:true];
_cpuCounter.stringValue = [NSString stringWithFormat:@"%.2f%%", secondUsage * 100];
});
}
if (type != GB_VBLANK_TYPE_REPEAT) { if (type != GB_VBLANK_TYPE_REPEAT) {
[self.view flip]; [self.view flip];
if (_borderModeChanged) { if (_borderModeChanged) {
@ -730,11 +755,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
if (old_width != GB_get_screen_width(&_gb)) { if (old_width != GB_get_screen_width(&_gb)) {
[self.view screenSizeChanged]; [self.view screenSizeChanged];
} }
[self updateMinSize]; [self updateMinSize];
[self start]; [self start];
if (_gbsTracks) {
[self changeGBSTrack:sender];
}
if (_hexController) { if (_hexController) {
/* Verify bank sanity, especially when switching models. */ /* Verify bank sanity, especially when switching models. */
@ -859,7 +885,7 @@ again:;
{ {
[super windowControllerDidLoadNib:aController]; [super windowControllerDidLoadNib:aController];
// Interface Builder bug? // Interface Builder bug?
[self.consoleWindow setContentSize:self.consoleWindow.minSize]; [self.consoleWindow setContentSize:self.consoleWindow.frame.size];
/* Close Open Panels, if any */ /* Close Open Panels, if any */
for (NSWindow *window in [[NSApplication sharedApplication] windows]) { for (NSWindow *window in [[NSApplication sharedApplication] windows]) {
if ([window isKindOfClass:[NSOpenPanel class]]) { if ([window isKindOfClass:[NSOpenPanel class]]) {
@ -897,9 +923,16 @@ again:;
[self.vramWindow setFrame:vram_window_rect display:true animate:false]; [self.vramWindow setFrame:vram_window_rect display:true animate:false];
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [self.fileURL.path lastPathComponent]]; if (@available(macOS 11.0, *)) {
self.memoryWindow.title = [NSString stringWithFormat:@"Memory %@", [self.fileURL.path lastPathComponent]]; self.consoleWindow.subtitle = [self.fileURL.path lastPathComponent];
self.vramWindow.title = [NSString stringWithFormat:@"VRAM Viewer %@", [self.fileURL.path lastPathComponent]]; self.memoryWindow.subtitle = [self.fileURL.path lastPathComponent];
self.vramWindow.subtitle = [self.fileURL.path lastPathComponent];
}
else {
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [self.fileURL.path lastPathComponent]];
self.memoryWindow.title = [NSString stringWithFormat:@"Memory %@", [self.fileURL.path lastPathComponent]];
self.vramWindow.title = [NSString stringWithFormat:@"VRAM Viewer %@", [self.fileURL.path lastPathComponent]];
}
self.consoleWindow.level = NSNormalWindowLevel; self.consoleWindow.level = NSNormalWindowLevel;
@ -1152,6 +1185,17 @@ again:;
if (@available(macOS 10.10, *)) { if (@available(macOS 10.10, *)) {
_mainWindow.titlebarAppearsTransparent = true; _mainWindow.titlebarAppearsTransparent = true;
} }
if (@available(macOS 26.0, *)) {
// There's a new minimum width for segmented controls in Solarium
NSRect frame = _gbsNextPrevButton.frame;
frame.origin.x -= 16;
_gbsNextPrevButton.frame = frame;
frame = _gbsTracks.frame;
frame.size.width -= 16;
_gbsTracks.frame = frame;
}
} }
- (bool)isCartContainer - (bool)isCartContainer
@ -1316,6 +1360,7 @@ static bool is_path_writeable(const char *path)
[self.vramWindow close]; [self.vramWindow close];
[self.printerFeedWindow close]; [self.printerFeedWindow close];
[self.cheatsWindow close]; [self.cheatsWindow close];
[_cheatSearchController.window close];
[super close]; [super close];
} }
@ -1325,6 +1370,8 @@ static bool is_path_writeable(const char *path)
GB_debugger_break(&_gb); GB_debugger_break(&_gb);
[self start]; [self start];
[self.consoleWindow makeKeyAndOrderFront:nil]; [self.consoleWindow makeKeyAndOrderFront:nil];
double secondUsage = GB_debugger_get_second_cpu_usage(&_gb);
_cpuCounter.stringValue = [NSString stringWithFormat:@"%.2f%%", secondUsage * 100];
[self.consoleInput becomeFirstResponder]; [self.consoleInput becomeFirstResponder];
} }
@ -1406,6 +1453,9 @@ static bool is_path_writeable(const char *path)
else if ([anItem action] == @selector(decreaseWindowSize:)) { else if ([anItem action] == @selector(decreaseWindowSize:)) {
return [self newRect:NULL forWindow:_mainWindow action:GBWindowResizeActionDecrease]; return [self newRect:NULL forWindow:_mainWindow action:GBWindowResizeActionDecrease];
} }
else if ([anItem action] == @selector(reloadROM:)) {
return !_gbsTracks;
}
return [super validateUserInterfaceItem:anItem]; return [super validateUserInterfaceItem:anItem];
} }
@ -1547,7 +1597,9 @@ enum GBWindowResizeAction
[self reloadVRAMData: nil]; [self reloadVRAMData: nil];
[textView.textStorage appendAttributedString:_pendingConsoleOutput]; [textView.textStorage appendAttributedString:_pendingConsoleOutput];
[textView scrollToEndOfDocument:nil]; if (!_logToSideView) {
[textView scrollToEndOfDocument:nil];
}
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) {
[self.consoleWindow orderFront:nil]; [self.consoleWindow orderFront:nil];
} }
@ -1605,7 +1657,9 @@ enum GBWindowResizeAction
- (IBAction)showConsoleWindow:(id)sender - (IBAction)showConsoleWindow:(id)sender
{ {
[self.consoleWindow orderBack:nil]; [self.consoleWindow orderFront:nil];
double secondUsage = GB_debugger_get_second_cpu_usage(&_gb);
_cpuCounter.stringValue = [NSString stringWithFormat:@"%.2f%%", secondUsage * 100];
} }
- (void)queueDebuggerCommand:(NSString *)command - (void)queueDebuggerCommand:(NSString *)command
@ -1955,124 +2009,130 @@ enum GBWindowResizeAction
- (IBAction)hexGoTo:(id)sender - (IBAction)hexGoTo:(id)sender
{ {
NSString *expression = [sender stringValue];
__block uint16_t addr = 0;
__block uint16_t bank = 0;
__block bool fail = false;
NSString *error = [self captureOutputForBlock:^{ NSString *error = [self captureOutputForBlock:^{
uint16_t addr; if (GB_debugger_evaluate(&_gb, [expression UTF8String], &addr, &bank)) {
uint16_t bank; fail = true;
if (GB_debugger_evaluate(&_gb, [[sender stringValue] UTF8String], &addr, &bank)) {
return;
} }
if (bank != (typeof(bank))-1) {
GB_memory_mode_t mode = [(GBMemoryByteArray *)(_hexController.byteArray) mode];
if (addr < 0x4000) {
if (bank == 0) {
if (mode != GBMemoryROM && mode != GBMemoryEntireSpace) {
mode = GBMemoryEntireSpace;
}
}
else {
addr |= 0x4000;
mode = GBMemoryROM;
}
}
else if (addr < 0x8000) {
mode = GBMemoryROM;
}
else if (addr < 0xA000) {
mode = GBMemoryVRAM;
}
else if (addr < 0xC000) {
mode = GBMemoryExternalRAM;
}
else if (addr < 0xD000) {
if (mode != GBMemoryRAM && mode != GBMemoryEntireSpace) {
mode = GBMemoryEntireSpace;
}
}
else if (addr < 0xE000) {
mode = GBMemoryRAM;
}
else {
mode = GBMemoryEntireSpace;
}
[_memorySpaceButton selectItemAtIndex:mode];
[self hexUpdateSpace:_memorySpaceButton.cell];
[_memoryBankInput setStringValue:[NSString stringWithFormat:@"$%02x", bank]];
[self hexUpdateBank:_memoryBankInput];
}
addr -= _lineRep.valueOffset;
if (addr >= _hexController.byteArray.length) {
GB_log(&_gb, "Value $%04x is out of range.\n", addr);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[_hexController setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]];
[_hexController _ensureVisibilityOfLocation:addr];
for (HFRepresenter *representer in _hexController.representers) {
if ([representer isKindOfClass:[HFHexTextRepresenter class]]) {
[self.memoryWindow makeFirstResponder:representer.view];
break;
}
}
});
}]; }];
if (error) { if (error) {
NSBeep(); NSBeep();
[GBWarningPopover popoverWithContents:error onView:sender]; [GBWarningPopover popoverWithContents:error onView:sender];
} }
if (fail) return;
if (bank != (typeof(bank))-1) {
GB_memory_mode_t mode = [(GBMemoryByteArray *)(_hexController.byteArray) mode];
if (addr < 0x4000) {
if (bank == 0) {
if (mode != GBMemoryROM && mode != GBMemoryEntireSpace) {
mode = GBMemoryEntireSpace;
}
}
else {
addr |= 0x4000;
mode = GBMemoryROM;
}
}
else if (addr < 0x8000) {
mode = GBMemoryROM;
}
else if (addr < 0xA000) {
mode = GBMemoryVRAM;
}
else if (addr < 0xC000) {
mode = GBMemoryExternalRAM;
}
else if (addr < 0xD000) {
if (mode != GBMemoryRAM && mode != GBMemoryEntireSpace) {
mode = GBMemoryEntireSpace;
}
}
else if (addr < 0xE000) {
mode = GBMemoryRAM;
}
else {
mode = GBMemoryEntireSpace;
}
[_memorySpaceButton selectItemAtIndex:mode];
[self hexUpdateSpace:_memorySpaceButton.cell];
[_memoryBankInput setStringValue:[NSString stringWithFormat:@"$%02x", bank]];
[self hexUpdateBank:_memoryBankInput];
}
addr -= _lineRep.valueOffset;
if (addr >= _hexController.byteArray.length) {
GB_log(&_gb, "Value $%04x is out of range.\n", addr);
return;
}
[_hexController setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]];
[_hexController _ensureVisibilityOfLocation:addr];
for (HFRepresenter *representer in _hexController.representers) {
if ([representer isKindOfClass:[HFHexTextRepresenter class]]) {
[self.memoryWindow makeFirstResponder:representer.view];
break;
}
}
} }
- (void)hexUpdateBank:(NSControl *)sender ignoreErrors: (bool)ignore_errors - (void)hexUpdateBank:(NSControl *)sender ignoreErrors: (bool)ignore_errors
{ {
NSString *expression = [sender stringValue];
__block uint16_t addr, bank;
__block bool fail = false;
NSString *error = [self captureOutputForBlock:^{ NSString *error = [self captureOutputForBlock:^{
uint16_t addr, bank; if (GB_debugger_evaluate(&_gb, [expression UTF8String], &addr, &bank)) {
if (GB_debugger_evaluate(&_gb, [[sender stringValue] UTF8String], &addr, &bank)) { fail = true;
return; return;
} }
if (bank == (uint16_t) -1) {
bank = addr;
}
uint16_t n_banks = 1;
switch ([(GBMemoryByteArray *)(_hexController.byteArray) mode]) {
case GBMemoryROM: {
size_t rom_size;
GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, &rom_size, NULL);
n_banks = rom_size / 0x4000;
break;
}
case GBMemoryVRAM:
n_banks = GB_is_cgb(&_gb) ? 2 : 1;
break;
case GBMemoryExternalRAM: {
size_t ram_size;
GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_CART_RAM, &ram_size, NULL);
n_banks = (ram_size + 0x1FFF) / 0x2000;
break;
}
case GBMemoryRAM:
n_banks = GB_is_cgb(&_gb) ? 8 : 1;
break;
case GBMemoryEntireSpace:
break;
}
bank %= n_banks;
[sender setStringValue:[NSString stringWithFormat:@"$%x", bank]];
[(GBMemoryByteArray *)(_hexController.byteArray) setSelectedBank:bank];
_statusRep.bankForDescription = bank;
dispatch_async(dispatch_get_main_queue(), ^{
[_hexController reloadData];
});
}]; }];
if (error && !ignore_errors) { if (error && !ignore_errors) {
NSBeep(); NSBeep();
[GBWarningPopover popoverWithContents:error onView:sender]; [GBWarningPopover popoverWithContents:error onView:sender];
} }
if (fail) return;
if (bank == (uint16_t) -1) {
bank = addr;
}
uint16_t n_banks = 1;
switch ([(GBMemoryByteArray *)(_hexController.byteArray) mode]) {
case GBMemoryROM: {
size_t rom_size;
GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, &rom_size, NULL);
n_banks = rom_size / 0x4000;
break;
}
case GBMemoryVRAM:
n_banks = GB_is_cgb(&_gb) ? 2 : 1;
break;
case GBMemoryExternalRAM: {
size_t ram_size;
GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_CART_RAM, &ram_size, NULL);
n_banks = (ram_size + 0x1FFF) / 0x2000;
break;
}
case GBMemoryRAM:
n_banks = GB_is_cgb(&_gb) ? 8 : 1;
break;
case GBMemoryEntireSpace:
break;
}
bank %= n_banks;
[(GBMemoryByteArray *)(_hexController.byteArray) setSelectedBank:bank];
_statusRep.bankForDescription = bank;
[sender setStringValue:[NSString stringWithFormat:@"$%x", bank]];
[_hexController reloadData];
} }
- (IBAction)hexUpdateBank:(NSControl *)sender - (IBAction)hexUpdateBank:(NSControl *)sender
@ -2110,9 +2170,8 @@ enum GBWindowResizeAction
} }
byteArray.selectedBank = bank; byteArray.selectedBank = bank;
_statusRep.bankForDescription = bank; _statusRep.bankForDescription = bank;
if (bank != (uint16_t)-1) { [self.memoryBankInput setStringValue:(bank == (uint16_t)-1)? @"" :
[self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]]; [NSString stringWithFormat:@"$%x", byteArray.selectedBank]];
}
[_hexController reloadData]; [_hexController reloadData];
for (NSView *view in self.memoryView.subviews) { for (NSView *view in self.memoryView.subviews) {
@ -2452,9 +2511,16 @@ enum GBWindowResizeAction
- (void)setFileURL:(NSURL *)fileURL - (void)setFileURL:(NSURL *)fileURL
{ {
[super setFileURL:fileURL]; [super setFileURL:fileURL];
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [[fileURL path] lastPathComponent]]; if (@available(macOS 11.0, *)) {
self.memoryWindow.title = [NSString stringWithFormat:@"Memory %@", [[fileURL path] lastPathComponent]]; self.consoleWindow.subtitle = [self.fileURL.path lastPathComponent];
self.vramWindow.title = [NSString stringWithFormat:@"VRAM Viewer %@", [[fileURL path] lastPathComponent]]; self.memoryWindow.subtitle = [self.fileURL.path lastPathComponent];
self.vramWindow.subtitle = [self.fileURL.path lastPathComponent];
}
else {
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console %@", [self.fileURL.path lastPathComponent]];
self.memoryWindow.title = [NSString stringWithFormat:@"Memory %@", [self.fileURL.path lastPathComponent]];
self.vramWindow.title = [NSString stringWithFormat:@"VRAM Viewer %@", [self.fileURL.path lastPathComponent]];
}
} }
- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview; - (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview;
@ -2526,6 +2592,8 @@ enum GBWindowResizeAction
} }
GB_set_turbo_mode(&_gb, false, false); GB_set_turbo_mode(&_gb, false, false);
GB_set_turbo_mode(&partner->_gb, false, false); GB_set_turbo_mode(&partner->_gb, false, false);
GB_set_turbo_cap(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"]);
GB_set_turbo_cap(&partner->_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"]);
} }
} }
@ -2541,6 +2609,7 @@ enum GBWindowResizeAction
GB_set_turbo_mode(&partner->_gb, true, true); GB_set_turbo_mode(&partner->_gb, true, true);
_slave = partner; _slave = partner;
partner->_master = self; partner->_master = self;
GB_set_turbo_cap(&partner->_gb, 0);
_linkOffset = 0; _linkOffset = 0;
GB_set_serial_transfer_bit_start_callback(&_gb, _linkCableBitStart); GB_set_serial_transfer_bit_start_callback(&_gb, _linkCableBitStart);
GB_set_serial_transfer_bit_start_callback(&partner->_gb, _linkCableBitStart); GB_set_serial_transfer_bit_start_callback(&partner->_gb, _linkCableBitStart);

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
<capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/> <capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -15,6 +15,8 @@
<outlet property="consoleInput" destination="l22-S8-uji" id="Heu-am-YgB"/> <outlet property="consoleInput" destination="l22-S8-uji" id="Heu-am-YgB"/>
<outlet property="consoleOutput" destination="doS-dM-hnl" id="Gn5-ju-Wb0"/> <outlet property="consoleOutput" destination="doS-dM-hnl" id="Gn5-ju-Wb0"/>
<outlet property="consoleWindow" destination="21F-Ah-yHX" id="eQ4-ug-LsT"/> <outlet property="consoleWindow" destination="21F-Ah-yHX" id="eQ4-ug-LsT"/>
<outlet property="cpuCounter" destination="xdx-GC-Tb8" id="oSn-U3-qvg"/>
<outlet property="cpuView" destination="aBf-yU-8M0" id="RIb-Fj-lvt"/>
<outlet property="debugBar" destination="sah-kv-6KJ" id="24d-aM-rs5"/> <outlet property="debugBar" destination="sah-kv-6KJ" id="24d-aM-rs5"/>
<outlet property="debuggerBackstepButton" destination="E87-Uq-f2l" id="PI7-Wu-f0v"/> <outlet property="debuggerBackstepButton" destination="E87-Uq-f2l" id="PI7-Wu-f0v"/>
<outlet property="debuggerContinueButton" destination="ybQ-jy-NgI" id="fRF-S3-xSh"/> <outlet property="debuggerContinueButton" destination="ybQ-jy-NgI" id="fRF-S3-xSh"/>
@ -54,7 +56,7 @@
</customObject> </customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/> <customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" tabbingMode="disallowed" id="xOd-HO-29H" userLabel="Window"> <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" tabbingMode="disallowed" id="xOd-HO-29H" userLabel="Window" customClass="GBWindow">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/> <windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="160" height="144"/> <rect key="contentRect" x="0.0" y="0.0" width="160" height="144"/>
@ -91,38 +93,38 @@
<window title="Debug Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="21F-Ah-yHX" customClass="GBPanel"> <window title="Debug Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="21F-Ah-yHX" customClass="GBPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" HUD="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" HUD="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/> <windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="921" height="400"/> <rect key="contentRect" x="0.0" y="0.0" width="921" height="480"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/> <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<value key="minSize" type="size" width="921" height="400"/> <value key="minSize" type="size" width="921" height="400"/>
<view key="contentView" id="dCP-E5-7Fi" customClass="GBOptionalVisualEffectView"> <view key="contentView" id="dCP-E5-7Fi" customClass="GBOptionalVisualEffectView">
<rect key="frame" x="0.0" y="0.0" width="921" height="400"/> <rect key="frame" x="0.0" y="0.0" width="921" height="480"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<box horizontalHuggingPriority="750" boxType="separator" id="7bR-gM-1At"> <box horizontalHuggingPriority="750" boxType="separator" id="7bR-gM-1At">
<rect key="frame" x="590" y="0.0" width="5" height="401"/> <rect key="frame" x="590" y="0.0" width="5" height="481"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" heightSizable="YES"/>
</box> </box>
<splitView dividerStyle="thin" vertical="YES" id="pUc-ZN-vl5" customClass="GBSplitView"> <splitView dividerStyle="thin" vertical="YES" id="pUc-ZN-vl5" customClass="GBSplitView">
<rect key="frame" x="0.0" y="0.0" width="921" height="401"/> <rect key="frame" x="0.0" y="0.0" width="921" height="481"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<customView fixedFrame="YES" id="2rj-7i-kxc"> <customView fixedFrame="YES" id="2rj-7i-kxc">
<rect key="frame" x="0.0" y="0.0" width="591" height="401"/> <rect key="frame" x="0.0" y="0.0" width="591" height="481"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oTo-zx-o6N"> <scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oTo-zx-o6N">
<rect key="frame" x="0.0" y="52" width="591" height="349"/> <rect key="frame" x="0.0" y="52" width="591" height="429"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S"> <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S">
<rect key="frame" x="0.0" y="0.0" width="591" height="349"/> <rect key="frame" x="0.0" y="0.0" width="591" height="429"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textView editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl"> <textView editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl">
<rect key="frame" x="0.0" y="0.0" width="591" height="349"/> <rect key="frame" x="0.0" y="0.0" width="591" height="429"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.25" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.25" colorSpace="custom" customColorSpace="sRGB"/>
<size key="minSize" width="591" height="349"/> <size key="minSize" width="591" height="429"/>
<size key="maxSize" width="1160" height="10000000"/> <size key="maxSize" width="1160" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales> <allowedInputSourceLocales>
@ -136,7 +138,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="cwi-6E-rbh"> <scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="cwi-6E-rbh">
<rect key="frame" x="575" y="0.0" width="16" height="349"/> <rect key="frame" x="575" y="0.0" width="16" height="429"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
</scrollView> </scrollView>
@ -160,7 +162,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ybQ-jy-NgI" customClass="GBDebuggerButton"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ybQ-jy-NgI" customClass="GBDebuggerButton">
<rect key="frame" x="0.0" y="0.0" width="26" height="26"/> <rect key="frame" x="4" y="0.0" width="26" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="bevel" title="Interrupt" alternateTitle="interrupt" bezelStyle="rounded" image="InterruptTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="Yd7-kY-21r"> <buttonCell key="cell" type="bevel" title="Interrupt" alternateTitle="interrupt" bezelStyle="rounded" image="InterruptTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="Yd7-kY-21r">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -173,7 +175,7 @@
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jdD-yP-Nr6" customClass="GBDebuggerButton"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jdD-yP-Nr6" customClass="GBDebuggerButton">
<rect key="frame" x="76" y="0.0" width="26" height="26"/> <rect key="frame" x="80" y="0.0" width="26" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="bevel" title="Step Out" alternateTitle="finish" bezelStyle="rounded" image="FinishTemplate" imagePosition="only" alignment="center" enabled="NO" imageScaling="proportionallyDown" inset="2" id="16t-ix-lOh"> <buttonCell key="cell" type="bevel" title="Step Out" alternateTitle="finish" bezelStyle="rounded" image="FinishTemplate" imagePosition="only" alignment="center" enabled="NO" imageScaling="proportionallyDown" inset="2" id="16t-ix-lOh">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -185,7 +187,7 @@
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tTP-Zs-Ohu" customClass="GBDebuggerButton"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tTP-Zs-Ohu" customClass="GBDebuggerButton">
<rect key="frame" x="26" y="0.0" width="26" height="26"/> <rect key="frame" x="30" y="0.0" width="26" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="bevel" title="Step Over" alternateTitle="next" bezelStyle="rounded" image="NextTemplate" imagePosition="only" alignment="center" enabled="NO" imageScaling="proportionallyDown" inset="2" id="835-qy-CNq"> <buttonCell key="cell" type="bevel" title="Step Over" alternateTitle="next" bezelStyle="rounded" image="NextTemplate" imagePosition="only" alignment="center" enabled="NO" imageScaling="proportionallyDown" inset="2" id="835-qy-CNq">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -197,7 +199,7 @@
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="E87-Uq-f2l" customClass="GBDebuggerButton"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="E87-Uq-f2l" customClass="GBDebuggerButton">
<rect key="frame" x="100" y="0.0" width="26" height="26"/> <rect key="frame" x="104" y="0.0" width="26" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="bevel" title="Step Backward" alternateTitle="backstep" bezelStyle="rounded" image="BackstepTemplate" imagePosition="only" alignment="center" enabled="NO" imageScaling="proportionallyDown" inset="2" id="yr5-aU-Fli"> <buttonCell key="cell" type="bevel" title="Step Backward" alternateTitle="backstep" bezelStyle="rounded" image="BackstepTemplate" imagePosition="only" alignment="center" enabled="NO" imageScaling="proportionallyDown" inset="2" id="yr5-aU-Fli">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -209,7 +211,7 @@
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fsQ-dD-A8C" customClass="GBDebuggerButton"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fsQ-dD-A8C" customClass="GBDebuggerButton">
<rect key="frame" x="52" y="0.0" width="26" height="26"/> <rect key="frame" x="56" y="0.0" width="26" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="bevel" title="Step Into" alternateTitle="step" bezelStyle="rounded" image="StepTemplate" imagePosition="only" alignment="center" enabled="NO" imageScaling="proportionallyDown" inset="2" id="lau-41-TYH"> <buttonCell key="cell" type="bevel" title="Step Into" alternateTitle="step" bezelStyle="rounded" image="StepTemplate" imagePosition="only" alignment="center" enabled="NO" imageScaling="proportionallyDown" inset="2" id="lau-41-TYH">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -229,7 +231,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</box> </box>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Jt-TO-8CM" customClass="GBDebuggerButton"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Jt-TO-8CM" customClass="GBDebuggerButton">
<rect key="frame" x="565" y="0.0" width="26" height="26"/> <rect key="frame" x="561" y="0.0" width="26" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<buttonCell key="cell" type="bevel" title="Help" alternateTitle="help" bezelStyle="rounded" image="HelpTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="fVh-bT-eYs"> <buttonCell key="cell" type="bevel" title="Help" alternateTitle="help" bezelStyle="rounded" image="HelpTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="fVh-bT-eYs">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -245,11 +247,11 @@
</subviews> </subviews>
</customView> </customView>
<customView fixedFrame="YES" id="4Z2-33-dYY" customClass="GBOptionalVisualEffectView"> <customView fixedFrame="YES" id="4Z2-33-dYY" customClass="GBOptionalVisualEffectView">
<rect key="frame" x="592" y="0.0" width="329" height="401"/> <rect key="frame" x="592" y="0.0" width="329" height="481"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" scrollerKnobStyle="dark" translatesAutoresizingMaskIntoConstraints="NO" id="V9U-Ua-F4z"> <scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" scrollerKnobStyle="dark" translatesAutoresizingMaskIntoConstraints="NO" id="V9U-Ua-F4z">
<rect key="frame" x="0.0" y="338" width="329" height="63"/> <rect key="frame" x="0.0" y="418" width="329" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC"> <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC">
<rect key="frame" x="0.0" y="0.0" width="329" height="63"/> <rect key="frame" x="0.0" y="0.0" width="329" height="63"/>
@ -279,22 +281,22 @@
</scroller> </scroller>
</scrollView> </scrollView>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="5qI-qZ-nkh"> <box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="5qI-qZ-nkh">
<rect key="frame" x="0.0" y="336" width="329" height="5"/> <rect key="frame" x="0.0" y="415" width="329" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</box> </box>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vts-CC-ZjQ"> <scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vts-CC-ZjQ">
<rect key="frame" x="0.0" y="0.0" width="329" height="338"/> <rect key="frame" x="0.0" y="78" width="329" height="339"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg"> <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg">
<rect key="frame" x="0.0" y="0.0" width="329" height="338"/> <rect key="frame" x="0.0" y="0.0" width="329" height="339"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textView editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp"> <textView editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp">
<rect key="frame" x="0.0" y="0.0" width="329" height="338"/> <rect key="frame" x="0.0" y="0.0" width="329" height="339"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="0.14901960780000001" green="0.14901960780000001" blue="0.14901960780000001" alpha="1" colorSpace="calibratedRGB"/> <color key="backgroundColor" red="0.14901960780000001" green="0.14901960780000001" blue="0.14901960780000001" alpha="1" colorSpace="calibratedRGB"/>
<size key="minSize" width="329" height="338"/> <size key="minSize" width="329" height="339"/>
<size key="maxSize" width="1160" height="10000000"/> <size key="maxSize" width="1160" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales> <allowedInputSourceLocales>
@ -309,10 +311,38 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="4jm-Gm-D2R"> <scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="4jm-Gm-D2R">
<rect key="frame" x="313" y="0.0" width="16" height="338"/> <rect key="frame" x="313" y="0.0" width="16" height="339"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
</scrollView> </scrollView>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="wC1-WG-WRf">
<rect key="frame" x="0.0" y="75" width="329" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</box>
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="aBf-yU-8M0" customClass="GBCPUView">
<rect key="frame" x="0.0" y="0.0" width="329" height="77"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F9b-AT-CdX">
<rect key="frame" x="2" y="59" width="100" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="CPU Load" id="ai6-8E-Let">
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xdx-GC-Tb8">
<rect key="frame" x="2" y="2" width="100" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" title="100%" id="set-AM-fVX">
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</customView>
</subviews> </subviews>
</customView> </customView>
</subviews> </subviews>
@ -340,20 +370,20 @@
<rect key="frame" x="0.0" y="0.0" width="528" height="320"/> <rect key="frame" x="0.0" y="0.0" width="528" height="320"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view> </view>
<toolbar key="toolbar" implicitIdentifier="D857E961-E523-4295-83F8-0849316E827C" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="82v-uB-RPi"> <toolbar key="toolbar" implicitIdentifier="D857E961-E523-4295-83F8-0849316E827C" autosavesConfiguration="NO" allowsUserCustomization="NO" showsBaselineSeparator="NO" displayMode="iconAndLabel" sizeMode="regular" id="82v-uB-RPi">
<allowedToolbarItems> <allowedToolbarItems>
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="WUk-8p-S6B"/> <toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="WUk-8p-S6B"/>
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="E3z-um-6KG"/> <toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="E3z-um-6KG"/>
<toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H"> <toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H">
<nil key="toolTip"/> <nil key="toolTip"/>
<size key="minSize" width="100" height="25"/> <size key="minSize" width="160" height="25"/>
<size key="maxSize" width="130" height="25"/> <size key="maxSize" width="160" height="25"/>
<popUpButton key="view" verticalHuggingPriority="750" id="vfJ-vu-gqJ"> <popUpButton key="view" verticalHuggingPriority="750" id="vfJ-vu-gqJ">
<rect key="frame" x="0.0" y="14" width="128" height="25"/> <rect key="frame" x="0.0" y="14" width="160" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bpD-j9-omo"> <popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bpD-j9-omo" customClass="GBToolbarPopUpButtonCell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="message"/>
<menu key="menu" id="gTX-6Z-mOH"> <menu key="menu" id="gTX-6Z-mOH">
<items> <items>
<menuItem title="Entire Space" id="Zp5-J9-dd3"/> <menuItem title="Entire Space" id="Zp5-J9-dd3"/>
@ -372,31 +402,29 @@
<toolbarItem implicitItemIdentifier="D16C64D2-2F0D-4033-A1EC-A1E699522ECE" label="Bank" paletteLabel="Bank" id="bWC-FW-IYP"> <toolbarItem implicitItemIdentifier="D16C64D2-2F0D-4033-A1EC-A1E699522ECE" label="Bank" paletteLabel="Bank" id="bWC-FW-IYP">
<nil key="toolTip"/> <nil key="toolTip"/>
<size key="minSize" width="64" height="22"/> <size key="minSize" width="64" height="22"/>
<size key="maxSize" width="64" height="22"/> <size key="maxSize" width="80" height="22"/>
<textField key="view" verticalHuggingPriority="750" id="rdV-q6-hc6"> <textField key="view" focusRingType="none" verticalHuggingPriority="750" id="rdV-q6-hc6">
<rect key="frame" x="0.0" y="14" width="64" height="22"/> <rect key="frame" x="0.0" y="14" width="64" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" id="JCn-Y1-eHS"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" placeholderString="Bank" id="JCn-Y1-eHS" customClass="GBToolbarFieldCell">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<connections> <connections>
<action selector="hexUpdateBank:" target="-2" id="Mx9-WI-wgO"/> <action selector="hexUpdateBank:" target="-2" id="Mx9-WI-wgO"/>
</connections> </connections>
</textField> </textField>
</toolbarItem> </toolbarItem>
<toolbarItem implicitItemIdentifier="F9723DA8-D79F-43AB-876B-783DD0204AA6" label="Go to" paletteLabel="Go to" id="rLO-D7-zRG"> <toolbarItem implicitItemIdentifier="F9723DA8-D79F-43AB-876B-783DD0204AA6" label="Go To" paletteLabel="Go To" id="rLO-D7-zRG">
<nil key="toolTip"/> <nil key="toolTip"/>
<size key="minSize" width="96" height="22"/> <size key="minSize" width="160" height="22"/>
<size key="maxSize" width="128" height="22"/> <size key="maxSize" width="160" height="22"/>
<textField key="view" verticalHuggingPriority="750" id="EJd-jG-hmH"> <textField key="view" focusRingType="none" verticalHuggingPriority="750" id="EJd-jG-hmH">
<rect key="frame" x="0.0" y="14" width="96" height="22"/> <rect key="frame" x="0.0" y="14" width="160" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" bezelStyle="round" id="vg5-Nn-abb"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" placeholderString="Address" bezelStyle="round" id="vg5-Nn-abb" customClass="GBToolbarFieldCell">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<connections> <connections>
<action selector="hexGoTo:" target="-2" id="7WG-8C-SK8"/> <action selector="hexGoTo:" target="-2" id="7WG-8C-SK8"/>
@ -422,7 +450,7 @@
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/> <windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="512" height="432"/> <rect key="contentRect" x="0.0" y="0.0" width="512" height="432"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/> <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" id="GYW-dv-Um1"> <view key="contentView" misplaced="YES" id="GYW-dv-Um1">
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/> <rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
@ -430,7 +458,7 @@
<rect key="frame" x="0.0" y="406" width="512" height="5"/> <rect key="frame" x="0.0" y="406" width="512" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box> </box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6vK-IP-PmP"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6vK-IP-PmP">
<rect key="frame" x="-2" y="4" width="516" height="14"/> <rect key="frame" x="-2" y="4" width="516" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" id="umk-4r-VNg"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" id="umk-4r-VNg">
@ -563,7 +591,7 @@
</connections> </connections>
</popUpButton> </popUpButton>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YIJ-Qc-SIZ"> <popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YIJ-Qc-SIZ">
<rect key="frame" x="135" y="412" width="96" height="17"/> <rect key="frame" x="135" y="412" width="100" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="Effective Tilemap" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="XRF-Vj-3gs" id="3W1-Db-wDn"> <popUpButtonCell key="cell" type="roundRect" title="Effective Tilemap" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="XRF-Vj-3gs" id="3W1-Db-wDn">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -581,7 +609,7 @@
</connections> </connections>
</popUpButton> </popUpButton>
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k4c-Vg-MBu"> <popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k4c-Vg-MBu">
<rect key="frame" x="235" y="412" width="96" height="17"/> <rect key="frame" x="239" y="412" width="100" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundRect" title="Effective Tileset" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="CRe-dX-rzY" id="h53-sb-Odg"> <popUpButtonCell key="cell" type="roundRect" title="Effective Tileset" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="CRe-dX-rzY" id="h53-sb-Odg">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -649,21 +677,19 @@
</tabView> </tabView>
</subviews> </subviews>
</view> </view>
<toolbar key="toolbar" implicitIdentifier="7FF8B5E5-F61B-408C-9634-A5D496FE5E70" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconOnly" sizeMode="regular" id="SVR-To-n7n"> <toolbar key="toolbar" implicitIdentifier="7FF8B5E5-F61B-408C-9634-A5D496FE5E70" autosavesConfiguration="NO" allowsUserCustomization="NO" showsBaselineSeparator="NO" displayMode="iconOnly" sizeMode="regular" id="SVR-To-n7n">
<allowedToolbarItems> <allowedToolbarItems>
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="hnI-48-dYt"/> <toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="hnI-48-dYt"/>
<toolbarItem implicitItemIdentifier="B9D3CF98-8020-4389-9372-F99361440794" label="" paletteLabel="" id="fmK-Jl-Koj"> <toolbarItem implicitItemIdentifier="B9D3CF98-8020-4389-9372-F99361440794" label="" paletteLabel="" sizingBehavior="auto" id="fmK-Jl-Koj">
<nil key="toolTip"/> <nil key="toolTip"/>
<size key="minSize" width="100" height="25"/>
<size key="maxSize" width="268" height="25"/>
<segmentedControl key="view" verticalHuggingPriority="750" id="Aul-vO-dCK"> <segmentedControl key="view" verticalHuggingPriority="750" id="Aul-vO-dCK">
<rect key="frame" x="0.0" y="14" width="268" height="25"/> <rect key="frame" x="0.0" y="14" width="268" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedSquare" trackingMode="selectOne" id="HhR-ky-5NN"> <segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedSquare" trackingMode="selectOne" id="HhR-ky-5NN">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<segments> <segments>
<segment label="Tileset" selected="YES"/> <segment label="Tileset" selected="YES"/>
<segment label="Tilemap" tag="1"/> <segment label="Tilemap"/>
<segment label="Objects"/> <segment label="Objects"/>
<segment label="Palettes"/> <segment label="Palettes"/>
</segments> </segments>
@ -686,7 +712,7 @@
</connections> </connections>
<point key="canvasLocation" x="182" y="760"/> <point key="canvasLocation" x="182" y="760"/>
</window> </window>
<window title="Printer Feed" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="NdE-0B-WCf" customClass="GBPanel"> <window title="Printer" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="NdE-0B-WCf" customClass="GBPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="320" height="288"/> <rect key="contentRect" x="0.0" y="0.0" width="320" height="288"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/> <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
@ -702,7 +728,7 @@
</imageView> </imageView>
</subviews> </subviews>
</view> </view>
<toolbar key="toolbar" implicitIdentifier="1FF86A2B-6637-4EE6-A25A-7298D79AE84E" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="gH3-SH-7il"> <toolbar key="toolbar" implicitIdentifier="1FF86A2B-6637-4EE6-A25A-7298D79AE84E" autosavesConfiguration="NO" allowsUserCustomization="NO" showsBaselineSeparator="NO" displayMode="iconAndLabel" sizeMode="regular" id="gH3-SH-7il">
<allowedToolbarItems> <allowedToolbarItems>
<toolbarItem implicitItemIdentifier="15EB8D49-8C6E-42F2-9F7F-F7D7A0BBDAAF" label="Save" paletteLabel="Save" tag="-1" image="NSFolder" id="CBz-1N-o0Q"> <toolbarItem implicitItemIdentifier="15EB8D49-8C6E-42F2-9F7F-F7D7A0BBDAAF" label="Save" paletteLabel="Save" tag="-1" image="NSFolder" id="CBz-1N-o0Q">
<size key="minSize" width="22" height="22"/> <size key="minSize" width="22" height="22"/>
@ -754,7 +780,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" id="mzf-yu-RID"> <clipView key="contentView" id="mzf-yu-RID">
<rect key="frame" x="1" y="1" width="604" height="257"/> <rect key="frame" x="1" y="1" width="604" height="257"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="24" headerView="pvX-uJ-qK5" id="tA3-8T-bxb"> <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="24" headerView="pvX-uJ-qK5" id="tA3-8T-bxb">
<rect key="frame" x="0.0" y="0.0" width="604" height="240"/> <rect key="frame" x="0.0" y="0.0" width="604" height="240"/>
@ -834,8 +860,8 @@
<rect key="frame" x="-1" y="0.0" width="308" height="135"/> <rect key="frame" x="-1" y="0.0" width="308" height="135"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="hqi-ob-NW9"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="hqi-ob-NW9">
<rect key="frame" x="20" y="51" width="176" height="19"/> <rect key="frame" x="20" y="52" width="176" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="To value:" id="Ycx-oE-aA4"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="To value:" id="Ycx-oE-aA4">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -844,7 +870,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kq8-6F-9GK"> <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kq8-6F-9GK">
<rect key="frame" x="42" y="21" width="152" height="19"/> <rect key="frame" x="42" y="22" width="152" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/>
<buttonCell key="cell" type="check" title="Only if old value was: " bezelStyle="regularSquare" imagePosition="left" alignment="right" state="on" inset="2" id="LkB-WQ-9Qd"> <buttonCell key="cell" type="check" title="Only if old value was: " bezelStyle="regularSquare" imagePosition="left" alignment="right" state="on" inset="2" id="LkB-WQ-9Qd">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
@ -854,8 +880,8 @@
<action selector="updateCheat:" target="v7q-gT-jHT" id="kNc-cj-bmF"/> <action selector="updateCheat:" target="v7q-gT-jHT" id="kNc-cj-bmF"/>
</connections> </connections>
</button> </button>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C6E-oI-hDC"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C6E-oI-hDC">
<rect key="frame" x="22" y="112" width="276" height="21"/> <rect key="frame" x="22" y="113" width="276" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Description" drawsBackground="YES" usesSingleLineMode="YES" id="2uR-9N-hBb"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Description" drawsBackground="YES" usesSingleLineMode="YES" id="2uR-9N-hBb">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -866,7 +892,7 @@
<outlet property="delegate" destination="v7q-gT-jHT" id="zyw-h0-hRP"/> <outlet property="delegate" destination="v7q-gT-jHT" id="zyw-h0-hRP"/>
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="qHx-1z-daR"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="qHx-1z-daR">
<rect key="frame" x="202" y="82" width="96" height="21"/> <rect key="frame" x="202" y="82" width="96" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="edq-46-JeP" customClass="GBCheatTextFieldCell"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="edq-46-JeP" customClass="GBCheatTextFieldCell">
@ -881,7 +907,7 @@
<outlet property="delegate" destination="v7q-gT-jHT" id="79v-33-R1X"/> <outlet property="delegate" destination="v7q-gT-jHT" id="79v-33-R1X"/>
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="N3I-PP-X85"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="N3I-PP-X85">
<rect key="frame" x="202" y="51" width="96" height="21"/> <rect key="frame" x="202" y="51" width="96" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="CV2-D9-WsB" customClass="GBCheatTextFieldCell"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="CV2-D9-WsB" customClass="GBCheatTextFieldCell">
@ -896,7 +922,7 @@
<outlet property="delegate" destination="v7q-gT-jHT" id="P69-nT-oOt"/> <outlet property="delegate" destination="v7q-gT-jHT" id="P69-nT-oOt"/>
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="S6O-LB-gSj"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" mirrorLayoutDirectionWhenInternationalizing="never" translatesAutoresizingMaskIntoConstraints="NO" id="S6O-LB-gSj">
<rect key="frame" x="202" y="20" width="96" height="21"/> <rect key="frame" x="202" y="20" width="96" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="tpM-ys-MEO" customClass="GBCheatTextFieldCell"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="tpM-ys-MEO" customClass="GBCheatTextFieldCell">
@ -911,8 +937,8 @@
<outlet property="delegate" destination="v7q-gT-jHT" id="6RH-dg-SL7"/> <outlet property="delegate" destination="v7q-gT-jHT" id="6RH-dg-SL7"/>
</connections> </connections>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="uFo-ly-Veq"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="uFo-ly-Veq">
<rect key="frame" x="18" y="82" width="178" height="19"/> <rect key="frame" x="18" y="83" width="178" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Change byte at address:" id="xwa-TF-eY1"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Change byte at address:" id="xwa-TF-eY1">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -922,8 +948,8 @@
</textField> </textField>
</subviews> </subviews>
</customView> </customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r5T-ol-Dod"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r5T-ol-Dod">
<rect key="frame" x="316" y="115" width="270" height="16"/> <rect key="frame" x="316" y="116" width="270" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Import GameShark or Game Genie cheat:" id="0mf-EN-cKc"> <textFieldCell key="cell" lineBreakMode="clipping" title="Import GameShark or Game Genie cheat:" id="0mf-EN-cKc">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -931,7 +957,7 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X7K-nJ-alF"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X7K-nJ-alF">
<rect key="frame" x="351" y="82" width="233" height="21"/> <rect key="frame" x="351" y="82" width="233" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" placeholderString="Code" drawsBackground="YES" usesSingleLineMode="YES" id="2bz-dT-7Fi"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" placeholderString="Code" drawsBackground="YES" usesSingleLineMode="YES" id="2bz-dT-7Fi">
@ -946,7 +972,7 @@
<action selector="selectText:" target="KHj-uX-Wbk" id="11z-0U-tMA"/> <action selector="selectText:" target="KHj-uX-Wbk" id="11z-0U-tMA"/>
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KHj-uX-Wbk"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KHj-uX-Wbk">
<rect key="frame" x="351" y="51" width="233" height="21"/> <rect key="frame" x="351" y="51" width="233" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" placeholderString="Description" drawsBackground="YES" usesSingleLineMode="YES" id="50d-va-Cen"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" placeholderString="Description" drawsBackground="YES" usesSingleLineMode="YES" id="50d-va-Cen">
@ -959,7 +985,7 @@
</connections> </connections>
</textField> </textField>
<button verticalHuggingPriority="750" id="C3V-Ep-bMj"> <button verticalHuggingPriority="750" id="C3V-Ep-bMj">
<rect key="frame" x="508" y="12" width="83" height="32"/> <rect key="frame" x="508" y="13" width="83" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
<buttonCell key="cell" type="push" title="Import" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="mMP-KW-YNy"> <buttonCell key="cell" type="push" title="Import" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="mMP-KW-YNy">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -970,7 +996,7 @@
</connections> </connections>
</button> </button>
<box horizontalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="P90-u5-8ko"> <box horizontalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="P90-u5-8ko">
<rect key="frame" x="304" y="12" width="5" height="123"/> <rect key="frame" x="302" y="12" width="5" height="123"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</box> </box>
</subviews> </subviews>
@ -1000,7 +1026,7 @@
<image name="HelpTemplate" width="14" height="14"/> <image name="HelpTemplate" width="14" height="14"/>
<image name="InterruptTemplate" width="14" height="14"/> <image name="InterruptTemplate" width="14" height="14"/>
<image name="NSFolder" width="32" height="32"/> <image name="NSFolder" width="32" height="32"/>
<image name="NSStopProgressFreestandingTemplate" width="15" height="15"/> <image name="NSStopProgressFreestandingTemplate" width="20" height="20"/>
<image name="NextTemplate" width="14" height="14"/> <image name="NextTemplate" width="14" height="14"/>
<image name="StepTemplate" width="14" height="14"/> <image name="StepTemplate" width="14" height="14"/>
<image name="printer" catalog="system" width="18" height="16"/> <image name="printer" catalog="system" width="18" height="16"/>

View File

@ -8,9 +8,15 @@
#import <JoyKit/JoyKit.h> #import <JoyKit/JoyKit.h>
#import <WebKit/WebKit.h> #import <WebKit/WebKit.h>
#import <mach-o/dyld.h> #import <mach-o/dyld.h>
#include <sys/mount.h>
#include <sys/xattr.h>
#define UPDATE_SERVER "https://sameboy.github.io" #define UPDATE_SERVER "https://sameboy.github.io"
@interface NSToolbarItem(private)
- (NSButton *)_view;
@end
static uint32_t color_to_int(NSColor *color) static uint32_t color_to_int(NSColor *color)
{ {
color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
@ -40,7 +46,14 @@ static uint32_t color_to_int(NSColor *color)
- (void) applicationDidFinishLaunching:(NSNotification *)notification - (void) applicationDidFinishLaunching:(NSNotification *)notification
{ {
// Refresh icon if launched via a software update // Refresh icon if launched via a software update
[NSApplication sharedApplication].applicationIconImage = [NSImage imageNamed:@"AppIcon"]; if (@available(macOS 26.0, *)) {
// Severely broken on macOS 26
}
else {
NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile:[[NSBundle mainBundle] bundlePath]];
icon.size = [NSApplication sharedApplication].applicationIconImage.size;
[NSApplication sharedApplication].applicationIconImage = icon;
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
for (unsigned i = 0; i < GBKeyboardButtonCount; i++) { for (unsigned i = 0; i < GBKeyboardButtonCount; i++) {
@ -93,6 +106,9 @@ static uint32_t color_to_int(NSColor *color)
@"GBDebuggerFont": hasSFMono? @"SF Mono" : @"Menlo", @"GBDebuggerFont": hasSFMono? @"SF Mono" : @"Menlo",
@"GBDebuggerFontSize": @12, @"GBDebuggerFontSize": @12,
@"GBColorPalette": @1,
@"GBTurboCap": @0,
// Default themes // Default themes
@"GBThemes": @{ @"GBThemes": @{
@"Canyon": @{ @"Canyon": @{
@ -326,9 +342,8 @@ static uint32_t color_to_int(NSColor *color)
#ifndef UPDATE_SUPPORT #ifndef UPDATE_SUPPORT
[_preferencesWindow.toolbar removeItemAtIndex:4]; [_preferencesWindow.toolbar removeItemAtIndex:4];
#endif #endif
if (@available(macOS 11.0, *)) { for (unsigned i = _preferencesWindow.toolbar.items.count; i--;) {
[_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:0]; [_preferencesWindow.toolbar.items[i] _view].imageScaling = NSImageScaleNone;
[_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:_preferencesWindow.toolbar.items.count];
} }
} }
[_preferencesWindow makeKeyAndOrderFront:self]; [_preferencesWindow makeKeyAndOrderFront:self];
@ -388,7 +403,12 @@ static uint32_t color_to_int(NSColor *color)
else { else {
self.updateChanges.preferences.standardFontFamily = @"Lucida Grande"; self.updateChanges.preferences.standardFontFamily = @"Lucida Grande";
} }
self.updateChanges.preferences.fixedFontFamily = @"Menlo"; if (@available(macOS 10.15, *)) {
self.updateChanges.preferences.fixedFontFamily = [NSFont monospacedSystemFontOfSize:12 weight:NSFontWeightRegular].displayName;
}
else {
self.updateChanges.preferences.fixedFontFamily = @"Menlo";
}
self.updateChanges.drawsBackground = false; self.updateChanges.drawsBackground = false;
[self.updateChanges.mainFrame loadHTMLString:html baseURL:nil]; [self.updateChanges.mainFrame loadHTMLString:html baseURL:nil];
}); });
@ -644,6 +664,14 @@ static uint32_t color_to_int(NSColor *color)
} }
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil]; [[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
[[NSFileManager defaultManager] removeItemAtPath:contentsTempPath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:contentsTempPath error:nil];
// Remove the quarantine flag so we don't have to escape translocation
NSString *bundlePath = [NSBundle mainBundle].bundlePath;
removexattr(bundlePath.UTF8String, "com.apple.quarantine", 0);
for (NSString *path in [[NSFileManager defaultManager] enumeratorAtPath:bundlePath]) {
removexattr([bundlePath stringByAppendingPathComponent:path].UTF8String, "com.apple.quarantine", 0);
};
_downloadDirectory = nil; _downloadDirectory = nil;
atexit_b(^{ atexit_b(^{
execl(executablePath.UTF8String, executablePath.UTF8String, "--update-launch", NULL); execl(executablePath.UTF8String, executablePath.UTF8String, "--update-launch", NULL);
@ -763,4 +791,27 @@ static uint32_t color_to_int(NSColor *color)
- (IBAction)nop:(id)sender - (IBAction)nop:(id)sender
{ {
} }
/* This runs before C constructors. If we need to escape translocation, we should
do it ASAP to minimize our launch time. */
+ (void)load
{
if (@available(macOS 10.12, *)) {
/* Detect and escape translocation so we can safely update ourselves */
if ([[[NSBundle mainBundle] bundlePath] containsString:@"/AppTranslocation/"]) {
const char *mountPath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent].UTF8String;
struct statfs *mntbuf;
int mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
for (unsigned i = 0; i < mntsize; i++) {
if (strcmp(mntbuf[i].f_mntonname, mountPath) == 0) {
NSBundle *origBundle = [NSBundle bundleWithPath:@(mntbuf[i].f_mntfromname)];
execl(origBundle.executablePath.UTF8String, origBundle.executablePath.UTF8String, NULL);
break;
}
}
}
}
}
@end @end

5
Cocoa/GBCPUView.h Normal file
View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBCPUView : NSView
- (void)addSample:(double)sample;
@end

126
Cocoa/GBCPUView.m Normal file
View File

@ -0,0 +1,126 @@
#import "GBCPUView.h"
#define SAMPLE_COUNT 0x100 // ~4 seconds
@implementation GBCPUView
{
double _samples[SAMPLE_COUNT];
size_t _position;
}
- (void)drawRect:(NSRect)dirtyRect
{
CGRect bounds = self.bounds;
NSSize size = bounds.size;
unsigned factor = [[self.window screen] backingScaleFactor];
NSBitmapImageRep *maskBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:(unsigned)size.width * factor
pixelsHigh:(unsigned)size.height * factor
bitsPerSample:8
samplesPerPixel:2
hasAlpha:true
isPlanar:false
colorSpaceName:NSDeviceWhiteColorSpace
bytesPerRow:size.width * 2 * factor
bitsPerPixel:16];
NSGraphicsContext *mainContext = [NSGraphicsContext currentContext];
NSColor *greenColor, *redColor;
if (@available(macOS 10.10, *)) {
greenColor = [NSColor systemGreenColor];
redColor = [NSColor systemRedColor];
}
else {
greenColor = [NSColor colorWithRed:3.0 / 16 green:0.5 blue:5.0 / 16 alpha:1.0];
redColor = [NSColor colorWithRed:13.0 / 16 green:0.25 blue:0.25 alpha:1.0];
}
NSBitmapImageRep *colorBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:SAMPLE_COUNT
pixelsHigh:1
bitsPerSample:8
samplesPerPixel:3
hasAlpha:false
isPlanar:false
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:SAMPLE_COUNT * 4
bitsPerPixel:32];
unsigned lastFill = 0;
NSBezierPath *line = [NSBezierPath bezierPath];
bool isRed = false;
{
double sample = _samples[_position % SAMPLE_COUNT];
[line moveToPoint:NSMakePoint(0,
(sample * (size.height - 1) + 0.5) * factor)];
isRed = sample == 1;
}
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:colorBitmap]];
for (unsigned i = 1; i < SAMPLE_COUNT; i++) {
double sample = _samples[(i + _position) % SAMPLE_COUNT];
[line lineToPoint:NSMakePoint(size.width * i * factor / (SAMPLE_COUNT - 1),
(sample * (size.height - 1) + 0.5) * factor)];
if (isRed != (sample == 1)) {
// Color changed
[(isRed? redColor : greenColor) setFill];
NSRectFill(CGRectMake(lastFill, 0, i - lastFill, 1));
lastFill = i;
isRed ^= true;
}
}
[(isRed? redColor : greenColor) setFill];
NSRectFill(CGRectMake(lastFill, 0, SAMPLE_COUNT - lastFill, 1));
NSBezierPath *fill = [line copy];
[fill lineToPoint:NSMakePoint(size.width * factor, 0)];
[fill lineToPoint:NSMakePoint(0, 0)];
NSColor *strokeColor = [NSColor whiteColor];
NSColor *fillColor = [strokeColor colorWithAlphaComponent:1 / 3.0];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:maskBitmap]];
[fillColor setFill];
[fill fill];
[strokeColor setStroke];
[line setLineWidth:factor];
[line stroke];
CGContextRef maskContext = CGContextRetain([NSGraphicsContext currentContext].graphicsPort);
[NSGraphicsContext setCurrentContext:mainContext];
CGContextSaveGState(mainContext.graphicsPort);
CGImageRef maskImage = CGBitmapContextCreateImage(maskContext);
CGContextClipToMask(mainContext.graphicsPort, bounds, maskImage);
CGImageRelease(maskImage);
NSImage *colors = [[NSImage alloc] initWithSize:NSMakeSize(SAMPLE_COUNT, 1)];
[colors addRepresentation:colorBitmap];
[colors drawInRect:bounds];
CGContextRestoreGState(mainContext.graphicsPort);
CGContextRelease(maskContext);
[super drawRect:dirtyRect];
}
- (void)addSample:(double)sample
{
_samples[_position++] = sample;
if (_position == SAMPLE_COUNT) {
_position = 0;
}
}
@end

5
Cocoa/GBColorTextCell.h Normal file
View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBColorTextCell : NSTextFieldCell
@end

19
Cocoa/GBColorTextCell.m Normal file
View File

@ -0,0 +1,19 @@
#import "GBColorTextCell.h"
@implementation GBColorTextCell
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
[self.backgroundColor set];
NSRectFill(cellFrame);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:cellFrame];
path.lineWidth = 2;
[[NSColor colorWithWhite:0 alpha:0.25] setStroke];
[path addClip];
[path stroke];
[self drawInteriorWithFrame:cellFrame inView:controlView];
}
@end

5
Cocoa/GBDisabledButton.h Normal file
View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBDisabledButton : NSButton
@end

8
Cocoa/GBDisabledButton.m Normal file
View File

@ -0,0 +1,8 @@
#import "GBDisabledButton.h"
@implementation GBDisabledButton
- (void)mouseDown:(NSEvent *)event
{
}
@end

View File

@ -136,7 +136,7 @@
if (!_gb) { if (!_gb) {
return [NSString stringWithFormat:@"$%llX", offset]; return [NSString stringWithFormat:@"$%llX", offset];
} }
return @(GB_debugger_describe_address(_gb, offset + _baseAddress, _bankForDescription, false, isRangeEnd)); return @(GB_debugger_describe_address(_gb, offset + _baseAddress, offset < 0x4000? -1 :_bankForDescription, false, isRangeEnd));
} }

View File

@ -46,17 +46,29 @@
-(void)drawKnob:(NSRect)knobRect -(void)drawKnob:(NSRect)knobRect
{ {
[super drawKnob:knobRect]; [super drawKnob:knobRect];
NSRect peekRect = knobRect; NSBezierPath *path = nil;
peekRect.size.width /= 2; if (@available(macos 26.0, *)) {
peekRect.size.height = peekRect.size.width; NSRect peekRect = knobRect;
peekRect.origin.x += peekRect.size.width / 2; peekRect.size.height /= 2;
peekRect.origin.y += peekRect.size.height / 2; peekRect.size.width -= peekRect.size.height;
peekRect.origin.x += peekRect.size.height / 2;
peekRect.origin.y += peekRect.size.height / 2;
path = [NSBezierPath bezierPathWithRoundedRect:peekRect xRadius:peekRect.size.height / 2 yRadius:peekRect.size.height / 2];
}
else {
NSRect peekRect = knobRect;
peekRect.size.width /= 2;
peekRect.size.height = peekRect.size.width;
peekRect.origin.x += peekRect.size.width / 2;
peekRect.origin.y += peekRect.size.height / 2;
path = [NSBezierPath bezierPathWithOvalInRect:peekRect];
}
NSColor *color = self.colorValue; NSColor *color = self.colorValue;
if (!self.enabled) { if (!self.enabled) {
color = [color colorWithAlphaComponent:0.5]; color = [color colorWithAlphaComponent:0.5];
} }
[color setFill]; [color setFill];
NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:peekRect];
[path fill]; [path fill];
[[NSColor colorWithWhite:0 alpha:0.25] setStroke]; [[NSColor colorWithWhite:0 alpha:0.25] setStroke];
[path setLineWidth:0.5]; [path setLineWidth:0.5];

View File

@ -44,7 +44,8 @@
} }
if (parent.displayScrollRect) { if (parent.displayScrollRect) {
NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite]; // CGRectInfinite in NSBezierPath is broken in newer macOS versions
NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectMake(-0x4000, -0x4000, 0x8000, 0x8000)];
for (unsigned x = 0; x < 2; x++) { for (unsigned x = 0; x < 2; x++) {
for (unsigned y = 0; y < 2; y++) { for (unsigned y = 0; y < 2; y++) {
NSRect rect = parent.scrollRect; NSRect rect = parent.scrollRect;

View File

@ -16,7 +16,7 @@
static GBJoyConManager *manager = nil; static GBJoyConManager *manager = nil;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
manager = [[self alloc] _init]; manager = [[super allocWithZone:nil] _init];
}); });
return manager; return manager;
} }
@ -32,6 +32,16 @@
return ret; return ret;
} }
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
return [self sharedInstance];
}
+ (instancetype)alloc
{
return [self sharedInstance];
}
- (instancetype)init - (instancetype)init
{ {
return [self.class sharedInstance]; return [self.class sharedInstance];

View File

@ -399,10 +399,16 @@ static double blend(double from, double to, double position)
[sender.window.sheetParent endSheet:sender.window]; [sender.window.sheetParent endSheet:sender.window];
} }
- (instancetype)init + (instancetype)alloc
{ {
static id singleton = nil; static id singleton = nil;
if (singleton) return singleton; if (singleton) return singleton;
return (singleton = [super init]); return (singleton = [super allocWithZone:nil]);
} }
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
return [self alloc];
}
@end @end

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -22,44 +22,44 @@
<rect key="frame" x="0.0" y="0.0" width="512" height="24"/> <rect key="frame" x="0.0" y="0.0" width="512" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ypt-t4-Mf3"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ypt-t4-Mf3">
<rect key="frame" x="131" y="0.0" width="96" height="24"/> <rect key="frame" x="131" y="0.0" width="95" height="24"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="sKu-Uy-2LG"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="sKu-Uy-2LG" customClass="GBColorTextCell">
<font key="font" size="13" name="Menlo-Regular"/> <font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KkX-Z8-Sqi"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KkX-Z8-Sqi">
<rect key="frame" x="226" y="0.0" width="96" height="24"/> <rect key="frame" x="226" y="0.0" width="95" height="24"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="9LH-TF-W1L"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="9LH-TF-W1L" customClass="GBColorTextCell">
<font key="font" size="13" name="Menlo-Regular"/> <font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jDk-Ej-4yI"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jDk-Ej-4yI">
<rect key="frame" x="321" y="0.0" width="96" height="24"/> <rect key="frame" x="321" y="0.0" width="95" height="24"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="arE-i5-zCC"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="arE-i5-zCC" customClass="GBColorTextCell">
<font key="font" size="13" name="Menlo-Regular"/> <font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7PI-YE-fTk"> <textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7PI-YE-fTk">
<rect key="frame" x="416" y="0.0" width="96" height="24"/> <rect key="frame" x="416" y="0.0" width="95" height="24"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="ZbU-nE-FsE"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="ZbU-nE-FsE" customClass="GBColorTextCell">
<font key="font" size="13" name="Menlo-Regular"/> <font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NOK-yI-LKh"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NOK-yI-LKh">
<rect key="frame" x="4" y="0.0" width="121" height="20"/> <rect key="frame" x="8" y="0.0" width="121" height="20"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Background 0" id="qM4-cY-SDE"> <textFieldCell key="cell" lineBreakMode="clipping" title="Background 0" id="qM4-cY-SDE">
<font key="font" usesAppearanceFont="YES"/> <font key="font" usesAppearanceFont="YES"/>

View File

@ -19,6 +19,9 @@
@property IBOutlet NSPopUpButton *colorPalettePopupButton; @property IBOutlet NSPopUpButton *colorPalettePopupButton;
@property IBOutlet NSPopUpButton *hotkey1PopupButton; @property IBOutlet NSPopUpButton *hotkey1PopupButton;
@property IBOutlet NSPopUpButton *hotkey2PopupButton; @property IBOutlet NSPopUpButton *hotkey2PopupButton;
@property IBOutlet NSButton *turboCapButton;
@property IBOutlet NSSlider *turboCapSlider;
@property IBOutlet NSTextField *turboCapLabel;
@property IBOutlet GBTitledPopUpButton *fontPopupButton; @property IBOutlet GBTitledPopUpButton *fontPopupButton;
@property IBOutlet NSStepper *fontSizeStepper; @property IBOutlet NSStepper *fontSizeStepper;

View File

@ -20,7 +20,7 @@
- (NSWindowToolbarStyle)toolbarStyle - (NSWindowToolbarStyle)toolbarStyle
{ {
return NSWindowToolbarStyleExpanded; return NSWindowToolbarStylePreference;
} }
- (void)close - (void)close
@ -342,6 +342,13 @@ static inline NSString *keyEquivalentString(NSMenuItem *item)
_fontSizeStepper.intValue = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"]; _fontSizeStepper.intValue = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"];
[self updateFonts]; [self updateFonts];
double cap = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"];
if (cap) {
_turboCapSlider.intValue = round(cap * 100);
_turboCapButton.state = NSOnState;
}
[self turboCapToggled:_turboCapButton];
} }
- (IBAction)fontSizeChanged:(id)sender - (IBAction)fontSizeChanged:(id)sender
@ -555,4 +562,35 @@ static inline NSString *keyEquivalentString(NSMenuItem *item)
[GBJoyConManager sharedInstance].arrangementMode = false; [GBJoyConManager sharedInstance].arrangementMode = false;
} }
- (IBAction)turboCapToggled:(NSButton *)sender
{
if (sender.state) {
_turboCapSlider.enabled = true;
[self turboCapChanged:_turboCapSlider];
if (@available(macOS 10.10, *)) {
_turboCapLabel.textColor = [NSColor labelColor];
}
else {
_turboCapLabel.textColor = [NSColor blackColor];
}
}
else {
_turboCapSlider.enabled = false;
_turboCapLabel.enabled = false;
[[NSUserDefaults standardUserDefaults] setDouble:0 forKey:@"GBTurboCap"];
if (@available(macOS 10.10, *)) {
_turboCapLabel.textColor = [NSColor disabledControlTextColor];
}
else {
_turboCapLabel.textColor = [NSColor colorWithWhite:0 alpha:0.25];
}
}
}
- (IBAction)turboCapChanged:(NSSlider *)sender
{
_turboCapLabel.stringValue = [NSString stringWithFormat:@"%d%%", sender.intValue];
[[NSUserDefaults standardUserDefaults] setDouble:sender.doubleValue / 100.0 forKey:@"GBTurboCap"];
}
@end @end

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -25,7 +25,7 @@
<rect key="frame" x="0.0" y="0.0" width="332" height="221"/> <rect key="frame" x="0.0" y="0.0" width="332" height="221"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="H3v-X3-48q"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="H3v-X3-48q">
<rect key="frame" x="18" y="192" width="296" height="19"/> <rect key="frame" x="18" y="192" width="296" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Title" id="BwZ-Zj-sP6"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Title" id="BwZ-Zj-sP6">
@ -34,7 +34,7 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="gaD-ZH-Beh"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="gaD-ZH-Beh">
<rect key="frame" x="18" y="166" width="296" height="16"/> <rect key="frame" x="18" y="166" width="296" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Author" id="IgT-r1-T38"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Author" id="IgT-r1-T38">
@ -59,7 +59,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="YJh-dI-A5D"> <popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="YJh-dI-A5D">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/> <font key="font" metaFont="message"/>
<menu key="menu" id="Knp-Ok-Pb4"/> <menu key="menu" id="Knp-Ok-Pb4"/>
</popUpButtonCell> </popUpButtonCell>
<connections> <connections>
@ -67,7 +67,7 @@
</connections> </connections>
</popUpButton> </popUpButton>
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SRS-M5-VVL"> <segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SRS-M5-VVL">
<rect key="frame" x="247" y="129" width="68" height="24"/> <rect key="frame" x="247" y="128" width="68" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="momentary" id="cmq-I8-cFL"> <segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="momentary" id="cmq-I8-cFL">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -94,7 +94,7 @@
</customView> </customView>
</subviews> </subviews>
</customView> </customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="2dl-dH-E3J"> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="2dl-dH-E3J">
<rect key="frame" x="18" y="5" width="296" height="14"/> <rect key="frame" x="18" y="5" width="296" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Copyright" id="nM9-oF-OV9"> <textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Copyright" id="nM9-oF-OV9">

View File

@ -11,23 +11,23 @@
@implementation GBTerminalTextFieldCell @implementation GBTerminalTextFieldCell
{ {
GBTerminalTextView *field_editor; GBTerminalTextView *_fieldEditor;
} }
- (NSTextView *)fieldEditorForView:(NSTextField *)controlView - (NSTextView *)fieldEditorForView:(NSTextField *)controlView
{ {
if (field_editor) { if (_fieldEditor) {
field_editor.gb = self.gb; _fieldEditor.gb = self.gb;
return field_editor; return _fieldEditor;
} }
field_editor = [[GBTerminalTextView alloc] init]; _fieldEditor = [[GBTerminalTextView alloc] init];
[field_editor setFieldEditor:true]; [_fieldEditor setFieldEditor:true];
field_editor.gb = self.gb; _fieldEditor.gb = self.gb;
field_editor->_field = (NSTextField *)controlView; _fieldEditor->_field = (NSTextField *)controlView;
((NSTextFieldCell *)controlView.cell).textInset = ((NSTextFieldCell *)controlView.cell).textInset =
field_editor.textContainerInset = _fieldEditor.textContainerInset =
NSMakeSize(0, 2); NSMakeSize(7, 2);
return field_editor; return _fieldEditor;
} }
@end @end

View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBToolbarFieldCell : NSSearchFieldCell
@end

View File

@ -0,0 +1,40 @@
#import "GBToolbarFieldCell.h"
#import <objc/runtime.h>
@interface NSTextFieldCell()
- (void)textDidChange:(id)sender;
@end
@implementation GBToolbarFieldCell
- (void)textDidChange:(id)sender
{
IMP imp = [NSTextFieldCell instanceMethodForSelector:_cmd];
method_setImplementation(class_getInstanceMethod([GBToolbarFieldCell class], _cmd), imp);
((void(*)(id, SEL, id))imp)(self, _cmd, sender);
}
- (void)endEditing:(NSText *)textObj
{
IMP imp = [NSTextFieldCell instanceMethodForSelector:_cmd];
method_setImplementation(class_getInstanceMethod([GBToolbarFieldCell class], _cmd), imp);
((void(*)(id, SEL, id))imp)(self, _cmd, textObj);
}
- (void)resetSearchButtonCell
{
}
- (void)resetCancelButtonCell
{
}
// We only need this hack on Solarium, avoid regressions
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
if (@available(macOS 26.0, *)) {
return [super allocWithZone:zone];
}
return (id)[NSTextFieldCell allocWithZone:zone];
}
@end

View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBToolbarPopUpButtonCell : NSPopUpButtonCell
@end

View File

@ -0,0 +1,17 @@
#import "GBToolbarPopUpButtonCell.h"
@implementation GBToolbarPopUpButtonCell
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
[super drawInteriorWithFrame:CGRectInset(cellFrame, 5, 0) inView:controlView];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
if (@available(macOS 26.0, *)) {
return [super allocWithZone:zone];
}
return (id)[NSPopUpButtonCell allocWithZone:zone];
}
@end

View File

@ -148,6 +148,9 @@ static const uint8_t workboy_vk_to_key[] = {
[self observeStandardDefaultsKey:@"GBAspectRatioUnkept" withBlock:^(id newValue) { [self observeStandardDefaultsKey:@"GBAspectRatioUnkept" withBlock:^(id newValue) {
[weakSelf setFrame:weakSelf.superview.frame]; [weakSelf setFrame:weakSelf.superview.frame];
}]; }];
[self observeStandardDefaultsKey:@"GBForceIntegerScale" withBlock:^(id newValue) {
[weakSelf setFrame:weakSelf.superview.frame];
}];
[self observeStandardDefaultsKey:@"JoyKitDefaultControllers" withBlock:^(id newValue) { [self observeStandardDefaultsKey:@"JoyKitDefaultControllers" withBlock:^(id newValue) {
[weakSelf reassignControllers]; [weakSelf reassignControllers];
}]; }];
@ -282,7 +285,9 @@ static const uint8_t workboy_vk_to_key[] = {
- (void)setFrame:(NSRect)frame - (void)setFrame:(NSRect)frame
{ {
frame = self.superview.frame; NSView *superview = self.superview;
if (GB_unlikely(!superview)) return;
frame = superview.frame;
if (_gb && ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) { if (_gb && ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]) {
double ratio = frame.size.width / frame.size.height; double ratio = frame.size.width / frame.size.height;
double width = GB_get_screen_width(_gb); double width = GB_get_screen_width(_gb);
@ -300,6 +305,19 @@ static const uint8_t workboy_vk_to_key[] = {
frame.origin.x = 0; frame.origin.x = 0;
} }
} }
if (_gb && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBForceIntegerScale"]) {
double factor = self.window.backingScaleFactor;
double width = GB_get_screen_width(_gb) / factor;
double height = GB_get_screen_height(_gb) / factor;
double new_width = floor(frame.size.width / width) * width;
double new_height = floor(frame.size.height / height) * height;
frame.origin.x += floor((frame.size.width - new_width) / 2);
frame.origin.y += floor((frame.size.height - new_height) / 2);
frame.size.width = new_width;
frame.size.height = new_height;
}
[super setFrame:frame]; [super setFrame:frame];
} }

5
Cocoa/GBWindow.h Normal file
View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
@interface GBWindow : NSWindow
@end

22
Cocoa/GBWindow.m Normal file
View File

@ -0,0 +1,22 @@
#import "GBWindow.h"
@interface NSWindow(private)
- (void)_zoomFill:(id)sender;
@end
/*
For some reason, Apple replaced the alt + zoom button behavior to be "fill" rather than zoom.
I don't like that. It prevents SameBoy's integer scaling from working. Let's restore it.
*/
@implementation GBWindow
- (void)_zoomFill:(id)sender
{
if (sender == [self standardWindowButton:NSWindowZoomButton] &&
((self.currentEvent.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagOption)) {
[self zoom:sender];
return;
}
[super _zoomFill:sender];
}
@end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -113,7 +113,9 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>SameBoy</string> <string>SameBoy</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>AppIcon.icns</string> <string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.github.liji32.sameboy</string> <string>com.github.liji32.sameboy</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 461 B

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.9" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.9"/>
</dependencies> </dependencies>
<objects> <objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -82,7 +82,7 @@
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/> <action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Hot Swap Cartridge…" keyEquivalent="o" id="Rik-p8-QCf"> <menuItem title="Hot Swap Cartridge…" secondaryImage="arrow.down.left.arrow.up.right" catalog="system" keyEquivalent="o" id="Rik-p8-QCf">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections> <connections>
<action selector="cartSwap:" target="-1" id="iXS-Ol-IS0"/> <action selector="cartSwap:" target="-1" id="iXS-Ol-IS0"/>
@ -102,7 +102,7 @@
</menu> </menu>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/> <menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="New Cartridge Instance…" keyEquivalent="n" id="Vld-be-NZu"> <menuItem title="New Cartridge Instance…" secondaryImage="plus.square.on.square" catalog="system" keyEquivalent="n" id="Vld-be-NZu">
<connections> <connections>
<action selector="newCartridgeInstance:" target="-1" id="GJc-xU-ZEr"/> <action selector="newCartridgeInstance:" target="-1" id="GJc-xU-ZEr"/>
</connections> </connections>
@ -158,7 +158,7 @@
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="Aru-vr-frG"/> <menuItem isSeparatorItem="YES" id="Aru-vr-frG"/>
<menuItem title="Find" id="efg-jw-GVP"> <menuItem title="Find" secondaryImage="text.page.badge.magnifyingglass" catalog="system" id="efg-jw-GVP">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="4R6-IU-Jq6"> <menu key="submenu" title="Find" id="4R6-IU-Jq6">
<items> <items>
@ -197,23 +197,23 @@
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Emulation" id="HyV-fh-RgO"> <menu key="submenu" title="Emulation" id="HyV-fh-RgO">
<items> <items>
<menuItem title="Reset" keyEquivalent="r" id="p0i-Lt-sTg"> <menuItem title="Reset" secondaryImage="arrow.trianglehead.2.counterclockwise.rotate.90" catalog="system" keyEquivalent="r" id="p0i-Lt-sTg">
<connections> <connections>
<action selector="reset:" target="-1" id="DKW-Bd-h3v"/> <action selector="reset:" target="-1" id="DKW-Bd-h3v"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Quick Reset" tag="-1" alternate="YES" keyEquivalent="r" id="uPG-01-49E"> <menuItem title="Quick Reset" secondaryImage="arrow.trianglehead.2.counterclockwise.rotate.90" catalog="system" tag="-1" alternate="YES" keyEquivalent="r" id="uPG-01-49E">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections> <connections>
<action selector="reset:" target="-1" id="VV1-VP-L7g"/> <action selector="reset:" target="-1" id="VV1-VP-L7g"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Reload ROM" alternate="YES" keyEquivalent="R" id="eQP-Fb-nkz"> <menuItem title="Reload ROM" secondaryImage="arrow.trianglehead.2.counterclockwise.rotate.90" catalog="system" alternate="YES" keyEquivalent="R" id="eQP-Fb-nkz">
<connections> <connections>
<action selector="reloadROM:" target="-1" id="BpN-8V-Csg"/> <action selector="reloadROM:" target="-1" id="BpN-8V-Csg"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Pause" keyEquivalent="p" id="4K4-hw-R7Q"> <menuItem title="Pause" secondaryImage="pause" catalog="system" keyEquivalent="p" id="4K4-hw-R7Q">
<connections> <connections>
<action selector="togglePause:" target="-1" id="osW-wt-QAa"/> <action selector="togglePause:" target="-1" id="osW-wt-QAa"/>
</connections> </connections>
@ -263,7 +263,7 @@
</menu> </menu>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="QIS-av-Byy"/> <menuItem isSeparatorItem="YES" id="QIS-av-Byy"/>
<menuItem title="Save State" id="Hdz-ut-okE"> <menuItem title="Save State" secondaryImage="square.and.arrow.down" catalog="system" id="Hdz-ut-okE">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Save State" id="Mxx-u1-M9D"> <menu key="submenu" title="Save State" id="Mxx-u1-M9D">
<items> <items>
@ -320,7 +320,7 @@
</items> </items>
</menu> </menu>
</menuItem> </menuItem>
<menuItem title="Load State" id="EXD-SL-cz4"> <menuItem title="Load State" secondaryImage="square.and.arrow.up" catalog="system" id="EXD-SL-cz4">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Load State" id="l9D-Ej-sh2"> <menu key="submenu" title="Load State" id="l9D-Ej-sh2">
<items> <items>
@ -388,7 +388,7 @@
</menu> </menu>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/> <menuItem isSeparatorItem="YES" id="5GS-tt-E0a"/>
<menuItem title="Save Screenshot" keyEquivalent="s" id="0J3-yf-iXs"> <menuItem title="Save Screenshot" secondaryImage="photo" catalog="system" keyEquivalent="s" id="0J3-yf-iXs">
<connections> <connections>
<action selector="saveScreenshot:" target="-1" id="gJd-ml-J8p"/> <action selector="saveScreenshot:" target="-1" id="gJd-ml-J8p"/>
</connections> </connections>
@ -405,12 +405,12 @@
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="DPb-Sh-5tg"/> <menuItem isSeparatorItem="YES" id="DPb-Sh-5tg"/>
<menuItem title="Start Audio Recording…" keyEquivalent="A" id="1UK-8n-QPP"> <menuItem title="Start Audio Recording…" secondaryImage="record.circle" catalog="system" keyEquivalent="A" id="1UK-8n-QPP">
<connections> <connections>
<action selector="toggleAudioRecording:" target="-1" id="YE5-mi-Yzd"/> <action selector="toggleAudioRecording:" target="-1" id="YE5-mi-Yzd"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Mute Sound" keyEquivalent="m" id="zo0-Rh-wTu"> <menuItem title="Mute Sound" secondaryImage="speaker.slash" catalog="system" keyEquivalent="m" id="zo0-Rh-wTu">
<connections> <connections>
<action selector="mute:" target="-1" id="eK3-ea-ExJ"/> <action selector="mute:" target="-1" id="eK3-ea-ExJ"/>
</connections> </connections>
@ -433,7 +433,7 @@
<action selector="showCheats:" target="-1" id="stn-Ei-aFE"/> <action selector="showCheats:" target="-1" id="stn-Ei-aFE"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Search Cheats" id="LZV-QK-YXi"> <menuItem title="Search Cheats" secondaryImage="text.page.badge.magnifyingglass" catalog="system" id="LZV-QK-YXi">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="showCheatSearch:" target="-1" id="eI1-x0-ykn"/> <action selector="showCheatSearch:" target="-1" id="eI1-x0-ykn"/>
@ -442,9 +442,9 @@
</items> </items>
</menu> </menu>
</menuItem> </menuItem>
<menuItem title="Connectivity" id="IcW-ZC-4wb"> <menuItem title="Connect" id="IcW-ZC-4wb">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Connectivity" id="BDM-Cv-BOm"> <menu key="submenu" title="Connect" id="BDM-Cv-BOm">
<items> <items>
<menuItem title="None" id="SiH-Q4-OBY"> <menuItem title="None" id="SiH-Q4-OBY">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
@ -452,7 +452,7 @@
<action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/> <action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Game Link Cable &amp; Infrared" id="V4S-Fo-xJK"> <menuItem title="Game Link Cable &amp; Infrared" secondaryImage="cable.connector.horizontal" catalog="system" id="V4S-Fo-xJK">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Game Link Cable &amp; Infrared" id="6sJ-Wz-QLj"> <menu key="submenu" title="Game Link Cable &amp; Infrared" id="6sJ-Wz-QLj">
<connections> <connections>
@ -463,13 +463,13 @@
<action selector="nop:" target="-3" id="Bpa-0C-lkN"/> <action selector="nop:" target="-3" id="Bpa-0C-lkN"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Game Boy Printer" id="zHR-Ha-pOR"> <menuItem title="Game Boy Printer" secondaryImage="printer" catalog="system" id="zHR-Ha-pOR">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/> <action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Workboy" id="lo9-CX-BJj"> <menuItem title="Workboy" secondaryImage="keyboard" catalog="system" id="lo9-CX-BJj">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="connectWorkboy:" target="-1" id="6vS-bq-wAX"/> <action selector="connectWorkboy:" target="-1" id="6vS-bq-wAX"/>
@ -482,14 +482,14 @@
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Develop" id="UVb-cc-at0"> <menu key="submenu" title="Develop" id="UVb-cc-at0">
<items> <items>
<menuItem title="Developer Mode" id="Qx6-Tt-zZR"> <menuItem title="Developer Mode" secondaryImage="wrench.and.screwdriver" catalog="system" id="Qx6-Tt-zZR">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="toggleDeveloperMode:" target="-3" id="PIc-o3-bzb"/> <action selector="toggleDeveloperMode:" target="-3" id="PIc-o3-bzb"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="66c-T0-8pW"/> <menuItem isSeparatorItem="YES" id="66c-T0-8pW"/>
<menuItem title="Show Console" id="Wse-UY-Y9l"> <menuItem title="Show Console" secondaryImage="apple.terminal" catalog="system" id="Wse-UY-Y9l">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="showConsoleWindow:" target="-1" id="mFf-4i-jLG"/> <action selector="showConsoleWindow:" target="-1" id="mFf-4i-jLG"/>
@ -501,14 +501,14 @@
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="3If-Yf-U7B"/> <menuItem isSeparatorItem="YES" id="3If-Yf-U7B"/>
<menuItem title="Break Debugger" keyEquivalent="c" id="uBD-GY-Doi"> <menuItem title="Break Debugger" secondaryImage="pause" catalog="system" keyEquivalent="c" id="uBD-GY-Doi">
<modifierMask key="keyEquivalentModifierMask" control="YES"/> <modifierMask key="keyEquivalentModifierMask" control="YES"/>
<connections> <connections>
<action selector="interrupt:" target="-1" id="ZmB-wG-fTs"/> <action selector="interrupt:" target="-1" id="ZmB-wG-fTs"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="M6n-8G-LZS"/> <menuItem isSeparatorItem="YES" id="M6n-8G-LZS"/>
<menuItem title="Audio Channels" id="Cib-LN-Y4o"> <menuItem title="Audio Channels" secondaryImage="speaker.wave.3" catalog="system" id="Cib-LN-Y4o">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Audio Channels" id="l22-4p-yTK"> <menu key="submenu" title="Audio Channels" id="l22-4p-yTK">
<items> <items>
@ -540,13 +540,13 @@
</menu> </menu>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="v5c-ri-BoZ"/> <menuItem isSeparatorItem="YES" id="v5c-ri-BoZ"/>
<menuItem title="Show Background and Window" state="on" id="yfD-Qd-zoz"> <menuItem title="Show Background and Window" state="on" secondaryImage="mountain.2" catalog="system" id="yfD-Qd-zoz">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="toggleDisplayBackground:" target="-1" id="p5b-1n-SPR"/> <action selector="toggleDisplayBackground:" target="-1" id="p5b-1n-SPR"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Show Objects" state="on" id="OWx-a0-vQk"> <menuItem title="Show Objects" state="on" secondaryImage="cube" catalog="system" id="OWx-a0-vQk">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="toggleDisplayObjects:" target="-1" id="8ie-ey-739"/> <action selector="toggleDisplayObjects:" target="-1" id="8ie-ey-739"/>
@ -620,4 +620,24 @@
<point key="canvasLocation" x="140" y="260"/> <point key="canvasLocation" x="140" y="260"/>
</menu> </menu>
</objects> </objects>
<resources>
<image name="apple.terminal" catalog="system" width="18" height="14"/>
<image name="arrow.down.left.arrow.up.right" catalog="system" width="18" height="17"/>
<image name="arrow.trianglehead.2.counterclockwise.rotate.90" catalog="system" width="16" height="15"/>
<image name="cable.connector.horizontal" catalog="system" width="19" height="7"/>
<image name="cube" catalog="system" width="16" height="17"/>
<image name="keyboard" catalog="system" width="19" height="13"/>
<image name="mountain.2" catalog="system" width="25" height="14"/>
<image name="pause" catalog="system" width="9" height="13"/>
<image name="photo" catalog="system" width="18" height="14"/>
<image name="plus.square.on.square" catalog="system" width="17" height="16"/>
<image name="printer" catalog="system" width="18" height="16"/>
<image name="record.circle" catalog="system" width="15" height="15"/>
<image name="speaker.slash" catalog="system" width="14" height="16"/>
<image name="speaker.wave.3" catalog="system" width="22" height="15"/>
<image name="square.and.arrow.down" catalog="system" width="15" height="17"/>
<image name="square.and.arrow.up" catalog="system" width="15" height="18"/>
<image name="text.page.badge.magnifyingglass" catalog="system" width="15" height="18"/>
<image name="wrench.and.screwdriver" catalog="system" width="19" height="18"/>
</resources>
</document> </document>

View File

@ -3,8 +3,8 @@
#import <objc/runtime.h> #import <objc/runtime.h>
@interface NSTextFieldCell () @interface NSTextFieldCell ()
- (CGRect)_textLayerDrawingRectForCellFrame:(CGRect)rect;
@property NSSize textInset; @property NSSize textInset;
- (bool)_isEditingInView:(NSView *)view;
@end @end
@implementation NSTextFieldCell (Inset) @implementation NSTextFieldCell (Inset)
@ -19,21 +19,59 @@
return [objc_getAssociatedObject(self, _cmd) sizeValue]; return [objc_getAssociatedObject(self, _cmd) sizeValue];
} }
- (CGRect)_textLayerDrawingRectForCellFrameHook:(CGRect)rect - (void)drawWithFrameHook:(NSRect)cellFrame inView:(NSView *)controlView
{ {
CGRect ret = [self _textLayerDrawingRectForCellFrameHook:rect];
NSSize inset = self.textInset; NSSize inset = self.textInset;
ret.origin.x += inset.width; if (self.drawsBackground) {
ret.origin.y += inset.height; [self.backgroundColor setFill];
ret.size.width -= inset.width; if ([self _isEditingInView:controlView]) {
ret.size.height -= inset.height; NSRectFill(cellFrame);
return ret; }
else {
NSRectFill(NSMakeRect(cellFrame.origin.x, cellFrame.origin.y,
cellFrame.size.width, inset.height));
NSRectFill(NSMakeRect(cellFrame.origin.x, cellFrame.origin.y + cellFrame.size.height - inset.height,
cellFrame.size.width, inset.height));
NSRectFill(NSMakeRect(cellFrame.origin.x, cellFrame.origin.y + inset.height,
inset.width, cellFrame.size.height - inset.height * 2));
NSRectFill(NSMakeRect(cellFrame.origin.x + cellFrame.size.width - inset.width, cellFrame.origin.y + inset.height,
inset.width, cellFrame.size.height - inset.height * 2));
}
}
cellFrame.origin.x += inset.width;
cellFrame.origin.y += inset.height;
cellFrame.size.width -= inset.width * 2;
cellFrame.size.height -= inset.height * 2;
[self drawWithFrameHook:cellFrame inView:controlView];
} }
+ (void)load + (void)load
{ {
method_exchangeImplementations(class_getInstanceMethod(self, @selector(_textLayerDrawingRectForCellFrame:)), method_exchangeImplementations(class_getInstanceMethod(self, @selector(drawWithFrame:inView:)),
class_getInstanceMethod(self, @selector(_textLayerDrawingRectForCellFrameHook:))); class_getInstanceMethod(self, @selector(drawWithFrameHook:inView:)));
}
@end
@implementation NSTextField (Inset)
- (bool)wantsUpdateLayerHook
{
CGSize inset = ((NSTextFieldCell *)self.cell).textInset;
if (inset.width || inset.height) return false;
return [self wantsUpdateLayerHook];
}
+ (void)load
{
Method method = class_getInstanceMethod(self, @selector(wantsUpdateLayer));
if (class_addMethod(self, @selector(wantsUpdateLayer), method_getImplementation(method), method_getTypeEncoding(method))) {
method = class_getInstanceMethod(self, @selector(wantsUpdateLayer));
}
method_exchangeImplementations(method,
class_getInstanceMethod(self, @selector(wantsUpdateLayerHook)));
} }
@end @end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>

168
Cocoa/SolariumFixer.m Normal file
View File

@ -0,0 +1,168 @@
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
// Comment out to debug
#define NSLog(...)
// Solarium has weird proportions, we need to fix them.
@implementation NSControl (SolariumFixer)
- (void)awakeFromNib
{
if (@available(macOS 26.0, *)) {
if ([self.superview isKindOfClass:objc_getClass("NSToolbarItemViewer")]) return;
if ([self isKindOfClass:[NSStepper class]]) {
NSLog(@"Stepper needs adjustment: %s in window %@", sel_getName(self.action), self.window.title);
CGRect frame = self.frame;
frame.origin.y += 1;
self.frame = frame;
return;
}
if (self.controlSize != NSControlSizeRegular) return;
if ([self isKindOfClass:[NSPopUpButton class]] ) {
NSLog(@"Popup button needs adjustment: %@ in window %@", ((NSButton *)self).title, self.window.title);
CGRect frame = self.frame;
if (frame.size.height != 25) {
NSLog(@"%@ in window %@ has the wrong height!", ((NSButton *)self).title, self.window.title);
return;
}
frame.size.width -= 2 + 5; // Remove 5 from the right and 2 from the left
frame.origin.x += 2;
frame.origin.y += 2;
self.frame = frame;
}
else if ([self isKindOfClass:[NSSegmentedControl class]] ) {
NSLog(@"Segmented button needs adjustment: %s in window %@", sel_getName(self.action), self.window.title);
CGRect frame = self.frame;
if (frame.size.height != 25) {
NSLog(@"%s in window %@ has the wrong height!", sel_getName(self.action), self.window.title);
return;
}
frame.origin.x += 8;
frame.origin.y += 1;
self.frame = frame;
}
else if ([self isKindOfClass:[NSTextField class]]) {
NSTextField *field = (id)self;
if (![field isBezeled]) return;
NSLog(@"Text field needs adjustment: %@ in window %@", ((NSTextField *)self).placeholderString, self.window.title);
CGRect frame = self.frame;
if (frame.size.height == 21) {
frame.size.height = 24;
}
else {
NSLog(@"%@ in window %@ has the wrong height!", ((NSTextField *)self).placeholderString, self.window.title);
return;
}
frame.size.width -= 1 + 1; // Remove 1 from the right and 1 from the left
frame.origin.x += 1;
frame.origin.y -= 1;
self.frame = frame;
}
else if ([self isKindOfClass:[NSButton class]]) {
NSLog(@"Button: %@ in window %@", @(sel_getName(self.action)), self.window.title);
NSButton *button = (id)self;
if (!button.isBordered) return;
if (button.bezelStyle == NSBezelStylePush) {
NSLog(@"Button needs adjustment: %@ in window %@", @(sel_getName(self.action)), self.window.title);
CGRect frame = self.frame;
frame.size.width -= 7 + 7; // Remove 7 from the right and 7 from the left
frame.origin.x += 7;
frame.origin.y += 5;
if (frame.size.height == 32) {
frame.size.height = 25;
}
else {
NSLog(@"%@ in window %@ has the wrong height!", @(sel_getName(self.action)), self.window.title);
} self.frame = frame;
}
else if (button.bezelStyle == NSBezelStyleRegularSquare) {
CGRect frame = self.frame;
if (frame.size.height != 18) return;
NSLog(@"Check/Radio needs adjustment: %@ in window %@", ((NSButton *)self).title, self.window.title);
frame.size.width -= 2;
frame.origin.x += 2;
frame.origin.y += 1;
frame.size.height = 16;
self.frame = frame;
}
else if (button.bezelStyle == NSBezelStyleHelpButton) {
CGRect frame = self.frame;
NSLog(@"Help button needs adjustment: %@ in window %@", ((NSButton *)self).title, self.window.title);
frame.origin.y += 2;
self.frame = frame;
}
}
else if ([self isKindOfClass:[NSSlider class]]) {
NSLog(@"Slider needs adjustment: %s in window %@", sel_getName(self.action), self.window.title);
CGRect frame = self.frame;
frame.origin.y += 3;
self.frame = frame;
}
}
}
@end
@implementation NSToolbarItem (SolariumFixer)
static CGSize minSizeHook(id self, SEL _cmd)
{
return CGSizeMake(8, 0);
}
- (void)awakeFromNib
{
if (@available(macOS 26.0, *)) {
NSLog(@"Toolbar item %@ has view %@", self.label, self.view);
if ([self.view isKindOfClass:[NSTextField class]]) {
NSLog(@"Handling (Text field)");
self.bordered = true;
NSSize maxSize = self.maxSize;
maxSize.height = 36;
self.maxSize = maxSize;
NSSize minSize = self.minSize;
minSize.height = 36;
self.minSize = minSize;
((NSTextField *)self.view).backgroundColor = [NSColor clearColor];
((NSTextField *)self.view).bezeled = false;
((NSTextField *)self.view).bordered = true;
// Work around even more AppKit bugs
self.toolbar.displayMode++;
self.toolbar.displayMode--;
}
else if ([self.view isKindOfClass:[NSPopUpButton class]]) {
NSLog(@"Handling (Pop up button)");
self.bordered = true;
NSSize maxSize = self.maxSize;
maxSize.height = 28;
self.maxSize = maxSize;
NSSize minSize = self.minSize;
minSize.height = 28;
self.minSize = minSize;
}
}
else if (@available(macOS 11.0, *)) { // While at it, make macOS 11-15 a bit more consistent
if ([self.view isKindOfClass:[NSTextField class]]) {
((NSTextField *)self.view).bezelStyle = NSTextFieldRoundedBezel;
}
}
}
+ (void)load
{
if (@available(macOS 26.0, *)) {
method_setImplementation(class_getInstanceMethod(objc_getClass("NSToolbarFlexibleSpaceItem"), @selector(minSize)),
(void *)minSizeHook);
}
}
@end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 B

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 B

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 801 B

After

Width:  |  Height:  |  Size: 543 B

View File

@ -6,7 +6,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "gb.h" #include "gb.h"
/* Band limited synthesis based on: http://www.slack.net/~ant/bl-synth/ */ /* Band limited synthesis loosely based on: http://www.slack.net/~ant/bl-synth/ */
static int32_t band_limited_steps[GB_BAND_LIMITED_PHASES][GB_BAND_LIMITED_WIDTH]; static int32_t band_limited_steps[GB_BAND_LIMITED_PHASES][GB_BAND_LIMITED_WIDTH];
static void __attribute__((constructor)) band_limited_init(void) static void __attribute__((constructor)) band_limited_init(void)
@ -15,37 +15,42 @@ static void __attribute__((constructor)) band_limited_init(void)
double *master = malloc(master_size * sizeof(*master)); double *master = malloc(master_size * sizeof(*master));
memset(master, 0, master_size * sizeof(*master)); memset(master, 0, master_size * sizeof(*master));
const unsigned sine_size = 256 * GB_BAND_LIMITED_PHASES + 2; const double lowpass = 15.0 / 16.0; // 1.0 means using Nyquist as the exact cutoff
const unsigned max_harmonic = sine_size / 2 / GB_BAND_LIMITED_PHASES; const double to_angle = M_PI / GB_BAND_LIMITED_PHASES * lowpass;
nounroll for (unsigned harmonic = 1; harmonic <= max_harmonic; harmonic += 2) { double sum = 0;
double amplitude = 1.0 / harmonic / 2; nounroll for (signed i = 0; i < master_size; i++) {
double to_angle = M_PI * 2 / sine_size * harmonic; // Exact Blackman window
nounroll for (unsigned i = 0; i < master_size; i++) { const double a0 = 7938 / 18608.0;
master[i] += sin(((signed)(i + 1) - (signed)master_size / 2) * to_angle) * amplitude; const double a1 = 9240 / 18608.0;
} const double a2 = 1430 / 18608.0;
double window_angle = (2.0 * M_PI * i) / (master_size);
double window = a0 - a1 * cos(window_angle) + a2 * cos(2 * window_angle);
double angle = (i - (signed)master_size / 2) * to_angle;
sum += master[i] = (angle == 0? 1 : sin(angle) / angle) * window;
} }
// Normalize master waveform nounroll for (signed i = 0; i < master_size; i++) {
nounroll for (unsigned i = 0; i < master_size - 1; i++) { master[i] /= sum;
master[i] += master[master_size - 1];
master[i] /= master[master_size - 1] * 2;
} }
master[master_size - 1] = 1;
nounroll for (unsigned phase = 0; phase < GB_BAND_LIMITED_PHASES; phase++) { nounroll for (signed phase = 0; phase < GB_BAND_LIMITED_PHASES; phase++) {
int32_t error = GB_BAND_LIMITED_ONE; int32_t error = GB_BAND_LIMITED_ONE;
int32_t prev = 0; nounroll for (signed i = 0; i < GB_BAND_LIMITED_WIDTH; i++) {
nounroll for (unsigned i = 0; i < GB_BAND_LIMITED_WIDTH; i++) { double sum = 0;
int32_t cur = master[(GB_BAND_LIMITED_PHASES - 1 - phase) + i * GB_BAND_LIMITED_PHASES] * GB_BAND_LIMITED_ONE; nounroll for (signed j = 0; j < GB_BAND_LIMITED_PHASES; j++) {
int32_t delta = cur - prev; signed index = i * GB_BAND_LIMITED_PHASES - phase + j;
error = error - delta; if (index >= 0) {
prev = cur; sum += master[index];
band_limited_steps[phase][i] = delta; }
}
int32_t cur = sum * GB_BAND_LIMITED_ONE;
error -= cur;
band_limited_steps[phase][i] = cur;
} }
// Make sure the deltas sum to 1.0 // Make sure the deltas sum to 1.0
band_limited_steps[phase][GB_BAND_LIMITED_WIDTH / 2 - 1] += error / 2; band_limited_steps[phase][GB_BAND_LIMITED_WIDTH / 2] += error;
band_limited_steps[phase][0] += error - (error / 2);
} }
free(master); free(master);
} }
@ -56,7 +61,9 @@ static void band_limited_update(GB_band_limited_t *band_limited, const GB_sample
unsigned delay = phase / GB_BAND_LIMITED_PHASES; unsigned delay = phase / GB_BAND_LIMITED_PHASES;
phase = phase & (GB_BAND_LIMITED_PHASES - 1); phase = phase & (GB_BAND_LIMITED_PHASES - 1);
GB_sample_t delta = { struct {
signed left, right;
} delta = {
.left = input->left - band_limited->input.left, .left = input->left - band_limited->input.left,
.right = input->right - band_limited->input.right, .right = input->right - band_limited->input.right,
}; };
@ -73,7 +80,9 @@ static void band_limited_update_unfiltered(GB_band_limited_t *band_limited, cons
{ {
if (input->packed == band_limited->input.packed) return; if (input->packed == band_limited->input.packed) return;
GB_sample_t delta = { struct {
signed left, right;
} delta = {
.left = input->left - band_limited->input.left, .left = input->left - band_limited->input.left,
.right = input->right - band_limited->input.right, .right = input->right - band_limited->input.right,
}; };
@ -94,6 +103,39 @@ static void band_limited_read(GB_band_limited_t *band_limited, GB_sample_t *outp
output->left = band_limited->output.left * multiplier / GB_BAND_LIMITED_ONE; output->left = band_limited->output.left * multiplier / GB_BAND_LIMITED_ONE;
output->right = band_limited->output.right * multiplier / GB_BAND_LIMITED_ONE; output->right = band_limited->output.right * multiplier / GB_BAND_LIMITED_ONE;
/* This hueristic will mute the channel if it's only playing an amplitude of 1 or 2 units, usually
caused by rounding errors when the channel is playing a single frequency above Nyquist. */
unsigned diff = abs(output->left - band_limited->last_output.left);
if (diff > 4) {
band_limited->silence_detection = 0;
band_limited->last_output.packed = output->packed;
return;
}
diff = abs(output->right - band_limited->last_output.right);
if (diff > 4) {
band_limited->silence_detection = 0;
band_limited->last_output.packed = output->packed;
return;
}
if (band_limited->silence_detection == 4000) {
output->packed = band_limited->last_output.packed;
}
else {
band_limited->silence_detection++;
}
}
static inline uint32_t sample_fraction_multiply(GB_gameboy_t *gb, unsigned multiplier)
{
if (unlikely(multiplier == 0)) return 0;
if (likely(multiplier < GB_QUICK_MULTIPLY_COUNT + 1)) {
return gb->apu_output.quick_fraction_multiply_cache[multiplier - 1];
}
return gb->apu_output.quick_fraction_multiply_cache[0] * multiplier;
} }
static const uint8_t duties[] = { static const uint8_t duties[] = {
@ -164,7 +206,7 @@ static void update_sample(GB_gameboy_t *gb, GB_channel_t index, int8_t value, un
if (index == GB_WAVE) { if (index == GB_WAVE) {
/* For some reason, channel 3 is inverted on the AGB, and has a different "silence" value */ /* For some reason, channel 3 is inverted on the AGB, and has a different "silence" value */
value ^= 0xF; value ^= 0xF;
silence = 7; silence = 7 * 2;
} }
uint8_t bias = agb_bias_for_channel(gb, index); uint8_t bias = agb_bias_for_channel(gb, index);
@ -173,8 +215,8 @@ static void update_sample(GB_gameboy_t *gb, GB_channel_t index, int8_t value, un
bool right = gb->io_registers[GB_IO_NR51] & (1 << index); bool right = gb->io_registers[GB_IO_NR51] & (1 << index);
GB_sample_t output = { GB_sample_t output = {
.left = (0xF - (left? value : silence) * 2 + bias) * left_volume, .left = (0xF - (left? value * 2 + bias : silence)) * left_volume,
.right = (0xF - (right? value : silence) * 2 + bias) * right_volume .right = (0xF - (right? value * 2 + bias : silence)) * right_volume
}; };
if (unlikely(gb->apu_output.channel_muted[index])) { if (unlikely(gb->apu_output.channel_muted[index])) {
@ -187,7 +229,7 @@ static void update_sample(GB_gameboy_t *gb, GB_channel_t index, int8_t value, un
else { else {
band_limited_update(&gb->apu_output.band_limited[index], band_limited_update(&gb->apu_output.band_limited[index],
&output, &output,
(gb->apu_output.cycles_since_render + cycles_offset) * GB_BAND_LIMITED_PHASES / gb->apu_output.max_cycles_per_sample); (((gb->apu_output.sample_fraction + sample_fraction_multiply(gb, cycles_offset)) >> 8) * GB_BAND_LIMITED_PHASES) >> 20);
} }
} }
@ -222,7 +264,7 @@ static void update_sample(GB_gameboy_t *gb, GB_channel_t index, int8_t value, un
else { else {
band_limited_update(&gb->apu_output.band_limited[index], band_limited_update(&gb->apu_output.band_limited[index],
&output, &output,
(gb->apu_output.cycles_since_render + cycles_offset) * GB_BAND_LIMITED_PHASES / gb->apu_output.max_cycles_per_sample); (((gb->apu_output.sample_fraction + sample_fraction_multiply(gb, cycles_offset)) >> 8) * GB_BAND_LIMITED_PHASES) >> 20);
} }
} }
} }
@ -307,6 +349,12 @@ static void render(GB_gameboy_t *gb)
output.right += channel_output.right; output.right += channel_output.right;
} }
gb->apu_output.cycles_since_render = 0; gb->apu_output.cycles_since_render = 0;
if (unlikely(gb->apu_output.sample_fraction < (1 << 28))) {
gb->apu_output.sample_fraction = 0;
}
else {
gb->apu_output.sample_fraction -= 1 << 28;
}
if (gb->sgb && gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) return; if (gb->sgb && gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) return;
@ -964,6 +1012,8 @@ restart:;
if (gb->apu_output.sample_rate) { if (gb->apu_output.sample_rate) {
gb->apu_output.cycles_since_render += cycles; gb->apu_output.cycles_since_render += cycles;
gb->apu_output.sample_fraction += sample_fraction_multiply(gb, cycles);
assert(gb->apu_output.sample_fraction < (4 << 28));
if (gb->apu_output.sample_cycles >= clock_rate) { if (gb->apu_output.sample_cycles >= clock_rate) {
gb->apu_output.sample_cycles -= clock_rate; gb->apu_output.sample_cycles -= clock_rate;
@ -1760,6 +1810,10 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate)
if (sample_rate) { if (sample_rate) {
gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate);
gb->apu_output.max_cycles_per_sample = ceil(GB_get_clock_rate(gb) / 2.0 / sample_rate); gb->apu_output.max_cycles_per_sample = ceil(GB_get_clock_rate(gb) / 2.0 / sample_rate);
gb->apu_output.quick_fraction_multiply_cache[0] = round(sample_rate * 2.0 / GB_get_clock_rate(gb) * (1 << 28));
for (unsigned i = 1; i < GB_QUICK_MULTIPLY_COUNT; i++) {
gb->apu_output.quick_fraction_multiply_cache[i] = gb->apu_output.quick_fraction_multiply_cache[0] * (i + 1);
}
} }
else { else {
gb->apu_output.max_cycles_per_sample = 0x400; gb->apu_output.max_cycles_per_sample = 0x400;
@ -1776,6 +1830,11 @@ void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample)
gb->apu_output.sample_rate = GB_get_clock_rate(gb) / cycles_per_sample * 2; gb->apu_output.sample_rate = GB_get_clock_rate(gb) / cycles_per_sample * 2;
gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample); gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample);
gb->apu_output.max_cycles_per_sample = ceil(cycles_per_sample / 4); gb->apu_output.max_cycles_per_sample = ceil(cycles_per_sample / 4);
gb->apu_output.quick_fraction_multiply_cache[0] = round(gb->apu_output.sample_rate * 2.0 / GB_get_clock_rate(gb) * (1 << 28));
for (unsigned i = 1; i < GB_QUICK_MULTIPLY_COUNT; i++) {
gb->apu_output.quick_fraction_multiply_cache[i] = gb->apu_output.quick_fraction_multiply_cache[0] * (i + 1);
}
} }
unsigned GB_get_sample_rate(GB_gameboy_t *gb) unsigned GB_get_sample_rate(GB_gameboy_t *gb)

View File

@ -5,8 +5,10 @@
#include <stdio.h> #include <stdio.h>
#include "defs.h" #include "defs.h"
#define GB_BAND_LIMITED_WIDTH 16 #define GB_BAND_LIMITED_WIDTH 64
#define GB_BAND_LIMITED_PHASES 512 #define GB_BAND_LIMITED_PHASES 256
#define GB_QUICK_MULTIPLY_COUNT 64
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
#define GB_BAND_LIMITED_ONE 0x10000 // fixed point value equal to 1 #define GB_BAND_LIMITED_ONE 0x10000 // fixed point value equal to 1
@ -171,6 +173,8 @@ typedef struct {
} buffer[GB_BAND_LIMITED_WIDTH * 2], output; } buffer[GB_BAND_LIMITED_WIDTH * 2], output;
uint8_t pos; uint8_t pos;
GB_sample_t input; GB_sample_t input;
GB_sample_t last_output;
unsigned silence_detection;
} GB_band_limited_t; } GB_band_limited_t;
typedef struct { typedef struct {
@ -180,6 +184,9 @@ typedef struct {
unsigned max_cycles_per_sample; unsigned max_cycles_per_sample;
uint32_t cycles_since_render; uint32_t cycles_since_render;
uint32_t sample_fraction; // Counter in 1 / sample_rate, in 4.28 fixed format
uint32_t quick_fraction_multiply_cache[GB_QUICK_MULTIPLY_COUNT];
GB_band_limited_t band_limited[GB_N_CHANNELS]; GB_band_limited_t band_limited[GB_N_CHANNELS];
double dac_discharge[GB_N_CHANNELS]; double dac_discharge[GB_N_CHANNELS];
bool channel_muted[GB_N_CHANNELS]; bool channel_muted[GB_N_CHANNELS];

View File

@ -73,7 +73,7 @@ bool GB_cheat_search_filter(GB_gameboy_t *gb, const char *expression, GB_cheat_s
} }
skip:; skip:;
old_data++; old_data++;
if (new_data == gb->ram + gb->ram_size - 1) { if (new_data == gb->ram + gb->ram_size - 1 && gb->mbc_ram_size) {
new_data = gb->mbc_ram; new_data = gb->mbc_ram;
} }
else if (new_data == gb->mbc_ram + gb->mbc_ram_size - 1) { else if (new_data == gb->mbc_ram + gb->mbc_ram_size - 1) {

View File

@ -155,15 +155,19 @@ const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const cha
uint8_t dummy; uint8_t dummy;
/* GameShark */ /* GameShark */
if (strlen(cheat) == 8) { if (strlen(cheat) == 8) {
#ifdef _WIN32
// The hh modifier is not supported on old MSVCRT, it's completely ignored
uint32_t bank = 0;
uint32_t value = 0;
#pragma GCC diagnostic ignored "-Wformat"
#else
uint8_t bank; uint8_t bank;
uint8_t value; uint8_t value;
#endif
uint16_t address; uint16_t address;
if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) { if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) {
if (bank >= 0x80) {
bank &= 0xF;
}
address = __builtin_bswap16(address); address = __builtin_bswap16(address);
return GB_add_cheat(gb, description, address, bank, value, 0, false, enabled); return GB_add_cheat(gb, description, address, bank == 1? GB_CHEAT_ANY_BANK : (bank & 0xF), value, 0, false, enabled);
} }
} }
@ -181,8 +185,13 @@ const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const cha
stripped_cheat[7] = stripped_cheat[8]; stripped_cheat[7] = stripped_cheat[8];
stripped_cheat[8] = 0; stripped_cheat[8] = 0;
#ifdef _WIN32
uint32_t old_value = 0;
uint32_t value = 0;
#else
uint8_t old_value; uint8_t old_value;
uint8_t value; uint8_t value;
#endif
uint16_t address; uint16_t address;
if (strlen(stripped_cheat) != 8 && strlen(stripped_cheat) != 6) { if (strlen(stripped_cheat) != 8 && strlen(stripped_cheat) != 6) {
return NULL; return NULL;

View File

@ -128,7 +128,7 @@ static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank)
} }
} }
static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name, bool prefer_local) static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name, bool prefer_local, bool prefer_no_padding)
{ {
static __thread char output[256]; static __thread char output[256];
const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value, prefer_local); const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value, prefer_local);
@ -138,9 +138,13 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer
} }
if (!symbol) { if (!symbol) {
snprintf(output, sizeof(output), "$%04x", value); if (prefer_no_padding) {
snprintf(output, sizeof(output), "$%x", value);
}
else {
snprintf(output, sizeof(output), "$%04x", value);
}
} }
else if (symbol->addr == value) { else if (symbol->addr == value) {
if (prefer_name) { if (prefer_name) {
snprintf(output, sizeof(output), "%s ($%04x)", symbol->name, value); snprintf(output, sizeof(output), "%s ($%04x)", symbol->name, value);
@ -171,7 +175,7 @@ static GB_symbol_map_t *get_symbol_map(GB_gameboy_t *gb, uint16_t bank)
static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name, bool prefer_local) static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name, bool prefer_local)
{ {
if (!value.has_bank) return value_to_string(gb, value.value, prefer_name, prefer_local); if (!value.has_bank) return value_to_string(gb, value.value, prefer_name, prefer_local, false);
static __thread char output[256]; static __thread char output[256];
const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, value.bank), value.value, prefer_local); const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, value.bank), value.value, prefer_local);
@ -901,11 +905,11 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const
(gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-',
(gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-',
(gb->f & GB_ZERO_FLAG)? 'Z' : '-'); (gb->f & GB_ZERO_FLAG)? 'Z' : '-');
GB_log(gb, "BC = %s\n", value_to_string(gb, gb->bc, false, false)); GB_log(gb, "BC = %s\n", value_to_string(gb, gb->bc, false, false, false));
GB_log(gb, "DE = %s\n", value_to_string(gb, gb->de, false, false)); GB_log(gb, "DE = %s\n", value_to_string(gb, gb->de, false, false, false));
GB_log(gb, "HL = %s\n", value_to_string(gb, gb->hl, false, false)); GB_log(gb, "HL = %s\n", value_to_string(gb, gb->hl, false, false, false));
GB_log(gb, "SP = %s\n", value_to_string(gb, gb->sp, false, false)); GB_log(gb, "SP = %s\n", value_to_string(gb, gb->sp, false, false, false));
GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false, false)); GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false, false, false));
GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled"); GB_log(gb, "IME = %s\n", gb->ime? "Enabled" : "Disabled");
return true; return true;
} }
@ -1462,10 +1466,10 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
return true; return true;
} }
if (!modifiers || !modifiers[0]) { if (!modifiers) {
modifiers = "a"; modifiers = "";
} }
else if (modifiers[1]) { else if (modifiers[0] && modifiers[1]) {
print_usage(gb, command); print_usage(gb, command);
return true; return true;
} }
@ -1474,6 +1478,12 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL); value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL);
if (!error) { if (!error) {
switch (modifiers[0]) { switch (modifiers[0]) {
case '\0':
if (!result.has_bank) {
GB_log(gb, "=%s\n", value_to_string(gb, result.value, false, false, true));
break;
}
// fallthrough
case 'a': case 'a':
GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false, false)); GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false, false));
break; break;
@ -1503,6 +1513,7 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
break; break;
} }
default: default:
print_usage(gb, command);
break; break;
} }
} }
@ -1728,6 +1739,49 @@ static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
return true; return true;
} }
double GB_debugger_get_frame_cpu_usage(GB_gameboy_t *gb)
{
if (gb->last_frame_busy_cycles || gb->last_frame_idle_cycles) {
return (double)gb->last_frame_busy_cycles / (gb->last_frame_busy_cycles + gb->last_frame_idle_cycles);
}
return 0;
}
double GB_debugger_get_second_cpu_usage(GB_gameboy_t *gb)
{
if (gb->last_second_busy_cycles || gb->last_second_idle_cycles) {
return (double)gb->last_second_busy_cycles / (gb->last_second_busy_cycles + gb->last_second_idle_cycles);
}
return 0;
}
double GB_debugger_get_second_cpu_usage(GB_gameboy_t *gb);
static bool usage(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
NO_MODIFIERS
if (strlen(lstrip(arguments))) {
print_usage(gb, command);
return true;
}
if (gb->last_frame_busy_cycles || gb->last_frame_idle_cycles) {
GB_log(gb, "CPU usage (last frame): %.2f%%\n", (double)gb->last_frame_busy_cycles / (gb->last_frame_busy_cycles + gb->last_frame_idle_cycles) * 100);
}
else {
GB_log(gb, "CPU usage (last frame): N/A\n");
}
if (gb->last_second_busy_cycles || gb->last_second_idle_cycles) {
GB_log(gb, "CPU usage (last 60 frames): %.2f%%\n", (double)gb->last_second_busy_cycles / (gb->last_second_busy_cycles + gb->last_second_idle_cycles) * 100);
}
else {
GB_log(gb, "CPU usage (last 60 frames): N/A\n");
}
return true;
}
static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{ {
@ -2158,7 +2212,7 @@ static const debugger_command_t commands[] = {
{"backtrace", 2, backtrace, "Display the current call stack"}, {"backtrace", 2, backtrace, "Display the current call stack"},
{"bt", 2, }, /* Alias */ {"bt", 2, }, /* Alias */
{"print", 1, print, "Evaluate and print an expression. " {"print", 1, print, "Evaluate and print an expression. "
"Use modifier to format as an address (a, default) or as a number in " "Use modifier to format as an address (a) or as a number in "
"decimal (d), hexadecimal (x), octal (o) or binary (b).", "decimal (d), hexadecimal (x), octal (o) or binary (b).",
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer}, "<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
{"eval", 2, }, /* Alias */ {"eval", 2, }, /* Alias */
@ -2184,6 +2238,7 @@ static const debugger_command_t commands[] = {
{"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was " {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was "
"used. Use 'keep' as an argument to display ticks without reseeting " "used. Use 'keep' as an argument to display ticks without reseeting "
"the count.", "(keep)", .argument_completer = keep_completer}, "the count.", "(keep)", .argument_completer = keep_completer},
{"usage", 2, usage, "Display CPU usage"},
{"cartridge", 2, mbc, "Display information about the MBC and cartridge"}, {"cartridge", 2, mbc, "Display information about the MBC and cartridge"},
{"mbc", 3, }, /* Alias */ {"mbc", 3, }, /* Alias */
{"apu", 3, apu, "Display information about the current state of the audio processing unit", {"apu", 3, apu, "Display information about the current state of the audio processing unit",
@ -2330,10 +2385,10 @@ static void test_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t flags, uint
condition_ok: condition_ok:
GB_debugger_break(gb); GB_debugger_break(gb);
if (flags == WATCHPOINT_READ) { if (flags == WATCHPOINT_READ) {
GB_log(gb, "Watchpoint %u: [%s]\n", watchpoint->id, value_to_string(gb, addr, true, false)); GB_log(gb, "Watchpoint %u: [%s]\n", watchpoint->id, value_to_string(gb, addr, true, false, false));
} }
else { else {
GB_log(gb, "Watchpoint %u: [%s] = $%02x\n", watchpoint->id, value_to_string(gb, addr, true, false), value); GB_log(gb, "Watchpoint %u: [%s] = $%02x\n", watchpoint->id, value_to_string(gb, addr, true, false, false), value);
} }
return; return;
} }
@ -2533,7 +2588,7 @@ next_command:
unsigned breakpoint_id = 0; unsigned breakpoint_id = 0;
if (gb->breakpoints && !gb->debug_stopped && (breakpoint_id = should_break(gb, gb->pc, false))) { if (gb->breakpoints && !gb->debug_stopped && (breakpoint_id = should_break(gb, gb->pc, false))) {
GB_debugger_break(gb); GB_debugger_break(gb);
GB_log(gb, "Breakpoint %u: PC = %s\n", breakpoint_id, value_to_string(gb, gb->pc, true, false)); GB_log(gb, "Breakpoint %u: PC = %s\n", breakpoint_id, value_to_string(gb, gb->pc, true, false, false));
GB_cpu_disassemble(gb, gb->pc, 5); GB_cpu_disassemble(gb, gb->pc, 5);
} }
@ -2544,7 +2599,7 @@ next_command:
bool should_delete_state = true; bool should_delete_state = true;
if (jump_to_result == JUMP_TO_BREAK) { if (jump_to_result == JUMP_TO_BREAK) {
GB_debugger_break(gb); GB_debugger_break(gb);
GB_log(gb, "Jumping to breakpoint %u: %s\n", breakpoint_id, value_to_string(gb, address, true, false)); GB_log(gb, "Jumping to breakpoint %u: %s\n", breakpoint_id, value_to_string(gb, address, true, false, false));
GB_cpu_disassemble(gb, gb->pc, 5); GB_cpu_disassemble(gb, gb->pc, 5);
gb->non_trivial_jump_breakpoint_occured = false; gb->non_trivial_jump_breakpoint_occured = false;
} }
@ -2554,7 +2609,7 @@ next_command:
} }
else { else {
gb->non_trivial_jump_breakpoint_occured = true; gb->non_trivial_jump_breakpoint_occured = true;
GB_log(gb, "Jumping to breakpoint %u: %s\n", breakpoint_id, value_to_string(gb, gb->pc, true, false)); GB_log(gb, "Jumping to breakpoint %u: %s\n", breakpoint_id, value_to_string(gb, gb->pc, true, false, false));
GB_load_state_from_buffer(gb, gb->nontrivial_jump_state, -1); GB_load_state_from_buffer(gb, gb->nontrivial_jump_state, -1);
GB_rewind_push(gb); GB_rewind_push(gb);
GB_cpu_disassemble(gb, gb->pc, 5); GB_cpu_disassemble(gb, gb->pc, 5);
@ -2720,6 +2775,9 @@ const char *GB_debugger_describe_address(GB_gameboy_t *gb,
if (bank == (uint16_t)-1) { if (bank == (uint16_t)-1) {
bank = bank_for_addr(gb, addr); bank = bank_for_addr(gb, addr);
} }
if ((addr >> 12) == 0xC) {
bank = 0;
}
if (exact_match) { if (exact_match) {
const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, bank), addr, prefer_local); const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, bank), addr, prefer_local);
if (symbol && symbol->addr == addr) return symbol->name; if (symbol && symbol->addr == addr) return symbol->name;

View File

@ -25,6 +25,9 @@ void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled);
void GB_debugger_clear_symbols(GB_gameboy_t *gb); void GB_debugger_clear_symbols(GB_gameboy_t *gb);
void GB_debugger_set_reload_callback(GB_gameboy_t *gb, GB_debugger_reload_callback_t callback); void GB_debugger_set_reload_callback(GB_gameboy_t *gb, GB_debugger_reload_callback_t callback);
double GB_debugger_get_frame_cpu_usage(GB_gameboy_t *gb);
double GB_debugger_get_second_cpu_usage(GB_gameboy_t *gb);
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
internal void GB_debugger_run(GB_gameboy_t *gb); internal void GB_debugger_run(GB_gameboy_t *gb);
internal void GB_debugger_handle_async_commands(GB_gameboy_t *gb); internal void GB_debugger_handle_async_commands(GB_gameboy_t *gb);

View File

@ -4,13 +4,18 @@
#define GB_unlikely(x) __builtin_expect((bool)(x), 0) #define GB_unlikely(x) __builtin_expect((bool)(x), 0)
#define GB_inline_const(type, ...) (*({static const typeof(type) _= __VA_ARGS__; &_;})) #define GB_inline_const(type, ...) (*({static const typeof(type) _= __VA_ARGS__; &_;}))
#if !defined(typeof)
#if defined(__cplusplus) || __STDC_VERSION__ < 202311
#define typeof __typeof__
#endif
#endif
#ifdef GB_INTERNAL #ifdef GB_INTERNAL
// "Keyword" definitions // "Keyword" definitions
#define likely(x) GB_likely(x) #define likely(x) GB_likely(x)
#define unlikely(x) GB_unlikely(x) #define unlikely(x) GB_unlikely(x)
#define inline_const GB_inline_const #define inline_const GB_inline_const
#define typeof __typeof__
#if !defined(MIN) #if !defined(MIN)
#define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) #define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })

View File

@ -176,13 +176,34 @@ void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
gb->cycles_since_vblank_callback = 0; gb->cycles_since_vblank_callback = 0;
gb->lcd_disabled_outside_of_vblank = false; gb->lcd_disabled_outside_of_vblank = false;
#ifndef GB_DISABLE_DEBUGGER
gb->last_frame_idle_cycles = gb->current_frame_idle_cycles;
gb->last_frame_busy_cycles = gb->current_frame_busy_cycles;
gb->current_frame_idle_cycles = 0;
gb->current_frame_busy_cycles = 0;
if (gb->usage_frame_count++ == 60) {
gb->last_second_idle_cycles = gb->current_second_idle_cycles;
gb->last_second_busy_cycles = gb->current_second_busy_cycles;
gb->current_second_idle_cycles = 0;
gb->current_second_busy_cycles = 0;
gb->usage_frame_count = 0;
}
#endif
/* TODO: Slow in turbo mode! */ /* TODO: Slow in turbo mode! */
if (GB_is_hle_sgb(gb)) { if (GB_is_hle_sgb(gb)) {
GB_sgb_render(gb); GB_sgb_render(gb);
} }
if (gb->turbo) { if (gb->turbo) {
#ifndef GB_DISABLE_DEBUGGER
if (unlikely(gb->backstep_instructions)) return;
#endif
if (GB_timing_sync_turbo(gb)) { if (GB_timing_sync_turbo(gb)) {
if (gb->vblank_callback && gb->enable_skipped_frame_vblank_callbacks) {
gb->vblank_callback(gb, GB_VBLANK_TYPE_SKIPPED_FRAME);
}
return; return;
} }
} }
@ -2470,3 +2491,8 @@ double GB_get_usual_frame_rate(GB_gameboy_t *gb)
{ {
return GB_get_clock_rate(gb) / (double)LCDC_PERIOD; return GB_get_clock_rate(gb) / (double)LCDC_PERIOD;
} }
void GB_set_enable_skipped_frame_vblank_callbacks(GB_gameboy_t *gb, bool enable)
{
gb->enable_skipped_frame_vblank_callbacks = enable;
}

View File

@ -20,6 +20,7 @@ typedef enum {
GB_VBLANK_TYPE_LCD_OFF, // An artificial frame pushed while the LCD was off GB_VBLANK_TYPE_LCD_OFF, // An artificial frame pushed while the LCD was off
GB_VBLANK_TYPE_ARTIFICIAL, // An artificial frame pushed for some other reason GB_VBLANK_TYPE_ARTIFICIAL, // An artificial frame pushed for some other reason
GB_VBLANK_TYPE_REPEAT, // A frame that would not render on actual hardware, but the screen should retain the previous frame GB_VBLANK_TYPE_REPEAT, // A frame that would not render on actual hardware, but the screen should retain the previous frame
GB_VBLANK_TYPE_SKIPPED_FRAME, // If enabled via GB_set_enable_skipped_frame_vblank_callbacks, called on skipped frames during turbo mode
} GB_vblank_type_t; } GB_vblank_type_t;
typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb, GB_vblank_type_t type); typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb, GB_vblank_type_t type);
@ -95,6 +96,7 @@ static const GB_color_correction_mode_t __attribute__((deprecated("Use GB_COLOR_
static const GB_color_correction_mode_t __attribute__((deprecated("Use GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST instead"))) GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS = GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST; static const GB_color_correction_mode_t __attribute__((deprecated("Use GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST instead"))) GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS = GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST;
void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback); void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback);
void GB_set_enable_skipped_frame_vblank_callbacks(GB_gameboy_t *gb, bool enable);
void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback); void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback);
void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette);
const GB_palette_t *GB_get_palette(GB_gameboy_t *gb); const GB_palette_t *GB_get_palette(GB_gameboy_t *gb);

View File

@ -1222,8 +1222,10 @@ uint64_t GB_run_frame(GB_gameboy_t *gb)
/* Configure turbo temporarily, the user wants to handle FPS capping manually. */ /* Configure turbo temporarily, the user wants to handle FPS capping manually. */
bool old_turbo = gb->turbo; bool old_turbo = gb->turbo;
bool old_dont_skip = gb->turbo_dont_skip; bool old_dont_skip = gb->turbo_dont_skip;
double old_turbo_cap = gb->turbo_cap_multiplier;
gb->turbo = true; gb->turbo = true;
gb->turbo_dont_skip = true; gb->turbo_dont_skip = true;
gb->turbo_cap_multiplier = 0;
gb->cycles_since_last_sync = 0; gb->cycles_since_last_sync = 0;
while (true) { while (true) {
@ -1234,6 +1236,7 @@ uint64_t GB_run_frame(GB_gameboy_t *gb)
} }
gb->turbo = old_turbo; gb->turbo = old_turbo;
gb->turbo_dont_skip = old_dont_skip; gb->turbo_dont_skip = old_dont_skip;
gb->turbo_cap_multiplier = old_turbo_cap;
return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */
} }
@ -1418,6 +1421,11 @@ void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip)
gb->turbo_dont_skip = no_frame_skip; gb->turbo_dont_skip = no_frame_skip;
} }
void GB_set_turbo_cap(GB_gameboy_t *gb, double multiplier)
{
gb->turbo_cap_multiplier = multiplier;
}
void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled) void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled)
{ {
gb->disable_rendering = disabled; gb->disable_rendering = disabled;

View File

@ -697,6 +697,7 @@ struct GB_gameboy_internal_s {
/* Timing */ /* Timing */
uint64_t last_sync; uint64_t last_sync;
uint64_t last_render;
uint64_t cycles_since_last_sync; // In 8MHz units uint64_t cycles_since_last_sync; // In 8MHz units
GB_rtc_mode_t rtc_mode; GB_rtc_mode_t rtc_mode;
uint32_t rtc_second_length; uint32_t rtc_second_length;
@ -782,6 +783,14 @@ struct GB_gameboy_internal_s {
/* Callbacks */ /* Callbacks */
GB_debugger_reload_callback_t debugger_reload_callback; GB_debugger_reload_callback_t debugger_reload_callback;
/* CPU usage */
uint32_t current_frame_idle_cycles, current_frame_busy_cycles;
uint32_t last_frame_idle_cycles, last_frame_busy_cycles;
uint32_t current_second_idle_cycles, current_second_busy_cycles;
uint32_t last_second_idle_cycles, last_second_busy_cycles;
uint8_t usage_frame_count;
#endif #endif
#ifndef GB_DISABLE_REWIND #ifndef GB_DISABLE_REWIND
@ -822,6 +831,8 @@ struct GB_gameboy_internal_s {
/* Misc */ /* Misc */
bool turbo; bool turbo;
bool turbo_dont_skip; bool turbo_dont_skip;
double turbo_cap_multiplier;
bool enable_skipped_frame_vblank_callbacks;
bool disable_rendering; bool disable_rendering;
uint8_t boot_rom[0x900]; uint8_t boot_rom[0x900];
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
@ -941,6 +952,7 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t
int GB_load_battery(GB_gameboy_t *gb, const char *path); int GB_load_battery(GB_gameboy_t *gb, const char *path);
void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip); void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip);
void GB_set_turbo_cap(GB_gameboy_t *gb, double multiplier); // Use 0 to use no cap
void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled); void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled);
void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3); void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3);

View File

@ -551,6 +551,7 @@ static const uint8_t *get_header_bank(GB_gameboy_t *gb)
static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool append_bess) static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool append_bess)
{ {
errno = 0;
if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error; if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error;
if (!DUMP_SECTION(gb, file, core_state)) goto error; if (!DUMP_SECTION(gb, file, core_state)) goto error;
if (!DUMP_SECTION(gb, file, dma )) goto error; if (!DUMP_SECTION(gb, file, dma )) goto error;
@ -838,8 +839,10 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
goto error; goto error;
} }
return 0;;
error: error:
return 0; if (errno == 0) return EIO;
return errno;
} }
int GB_save_state(GB_gameboy_t *gb, const char *path) int GB_save_state(GB_gameboy_t *gb, const char *path)

View File

@ -47,10 +47,10 @@ bool GB_timing_sync_turbo(GB_gameboy_t *gb)
#endif #endif
if (!gb->turbo_dont_skip) { if (!gb->turbo_dont_skip) {
int64_t nanoseconds = get_nanoseconds(); int64_t nanoseconds = get_nanoseconds();
if (nanoseconds <= gb->last_sync + (1000000000LL * LCDC_PERIOD / GB_get_clock_rate(gb))) { if (nanoseconds <= gb->last_render + 1000000000 / 60) {
return true; return true;
} }
gb->last_sync = nanoseconds; gb->last_render = nanoseconds;
} }
return false; return false;
} }
@ -63,23 +63,32 @@ void GB_timing_sync(GB_gameboy_t *gb)
/* Prevent syncing if not enough time has passed.*/ /* Prevent syncing if not enough time has passed.*/
if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return;
unsigned target_clock_rate;
if (gb->turbo) { if (gb->turbo) {
gb->cycles_since_last_sync = 0; if (gb->turbo_cap_multiplier) {
if (gb->update_input_hint_callback) { target_clock_rate = GB_get_clock_rate(gb) * gb->turbo_cap_multiplier;
gb->update_input_hint_callback(gb);
} }
return; else {
gb->cycles_since_last_sync = 0;
if (gb->update_input_hint_callback) {
gb->update_input_hint_callback(gb);
}
return;
}
}
else {
target_clock_rate = GB_get_clock_rate(gb);
} }
uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / target_clock_rate; /* / 2 because we use 8MHz units */
int64_t nanoseconds = get_nanoseconds(); int64_t nanoseconds = get_nanoseconds();
int64_t time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds; int64_t time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds;
if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { // +20% to be more forgiving if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1200000000LL / target_clock_rate) { // +20% to be more forgiving
nsleep(time_to_sleep); nsleep(time_to_sleep);
gb->last_sync += target_nanoseconds; gb->last_sync += target_nanoseconds;
} }
else { else {
if (time_to_sleep < 0 && -time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { if (time_to_sleep < 0 && -time_to_sleep < LCDC_PERIOD * 1200000000LL / target_clock_rate) {
// We're running a bit too slow, but the difference is small enough, // We're running a bit too slow, but the difference is small enough,
// just skip this sync and let it even out // just skip this sync and let it even out
return; return;
@ -472,6 +481,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
#ifndef GB_DISABLE_DEBUGGER #ifndef GB_DISABLE_DEBUGGER
gb->absolute_debugger_ticks += cycles; gb->absolute_debugger_ticks += cycles;
*((gb->halted || gb->stopped)? &gb->current_frame_idle_cycles : &gb->current_frame_busy_cycles) += cycles;
*((gb->halted || gb->stopped)? &gb->current_second_idle_cycles : &gb->current_second_busy_cycles) += cycles;
#endif #endif
// Not affected by speed boost // Not affected by speed boost

View File

@ -473,7 +473,7 @@ static inline Class preferredByteArrayClass(void) {
} }
- (void)_setSingleSelectedContentsRange:(HFRange)newSelection { - (void)_setSingleSelectedContentsRange:(HFRange)newSelection {
HFASSERT(HFRangeIsSubrangeOfRange(newSelection, HFRangeMake(0, [self contentsLength]))); if (!HFRangeIsSubrangeOfRange(newSelection, HFRangeMake(0, [self contentsLength]))) return;
BOOL selectionChanged; BOOL selectionChanged;
if ([selectedContentsRanges count] == 1) { if ([selectedContentsRanges count] == 1) {
selectionChanged = ! HFRangeEqualsRange([selectedContentsRanges[0] HFRange], newSelection); selectionChanged = ! HFRangeEqualsRange([selectedContentsRanges[0] HFRange], newSelection);

View File

@ -36,10 +36,14 @@ else
DEFAULT := sdl DEFAULT := sdl
endif endif
NULL := /dev/null NULL := /dev/null
ifeq ($(PLATFORM),windows32) ifeq ($(PLATFORM),windows32)
ifneq ($(shell echo /dev/null*),/dev/null)
# Windows shell is not "aware" of /dev/null, use NUL and pray
NULL := NUL NULL := NUL
endif endif
endif
PREFIX ?= /usr/local PREFIX ?= /usr/local
ifneq ($(shell which xdg-open 2> $(NULL))$(FREEDESKTOP),) ifneq ($(shell which xdg-open 2> $(NULL))$(FREEDESKTOP),)
@ -96,15 +100,8 @@ else
CPPP_FLAGS += -UGB_DISABLE_CHEAT_SEARCH CPPP_FLAGS += -UGB_DISABLE_CHEAT_SEARCH
endif endif
ifneq ($(CORE_FILTER)$(DISABLE_TIMEKEEPING),)
ifneq ($(MAKECMDGOALS),lib)
$(error SameBoy features can only be disabled when compiling the 'lib' target)
endif
endif
CPPP_FLAGS += -UGB_INTERNAL CPPP_FLAGS += -UGB_INTERNAL
include version.mk include version.mk
COPYRIGHT_YEAR := $(shell grep -oE "20[2-9][0-9]" LICENSE) COPYRIGHT_YEAR := $(shell grep -oE "20[2-9][0-9]" LICENSE)
export VERSION export VERSION
@ -117,6 +114,12 @@ LIBDIR := build/lib
PKGCONF_DIR := $(LIBDIR)/pkgconfig PKGCONF_DIR := $(LIBDIR)/pkgconfig
PKGCONF_FILE := $(PKGCONF_DIR)/sameboy.pc PKGCONF_FILE := $(PKGCONF_DIR)/sameboy.pc
ifneq ($(CORE_FILTER)$(DISABLE_TIMEKEEPING),)
ifneq ($(filter-out lib headers $(LIBDIR)/% $(INC)/%,$(MAKECMDGOALS)),)
$(error SameBoy features can only be disabled when compiling the 'lib' target)
endif
endif
BOOTROMS_DIR ?= $(BIN)/BootROMs BOOTROMS_DIR ?= $(BIN)/BootROMs
ifdef DATA_DIR ifdef DATA_DIR
@ -132,6 +135,8 @@ CC := clang
endif endif
endif endif
IBTOOL ?= ibtool
# Find libraries with pkg-config if available. # Find libraries with pkg-config if available.
ifneq (, $(shell which pkg-config 2> $(NULL))) ifneq (, $(shell which pkg-config 2> $(NULL)))
# But not on macOS, it's annoying, and not on Haiku, where OpenGL is broken # But not on macOS, it's annoying, and not on Haiku, where OpenGL is broken
@ -210,9 +215,10 @@ CFLAGS += -DUPDATE_SUPPORT
endif endif
ifeq (,$(PKG_CONFIG)) ifeq (,$(PKG_CONFIG))
ifneq ($(PLATFORM),windows32)
SDL_CFLAGS := $(shell sdl2-config --cflags) SDL_CFLAGS := $(shell sdl2-config --cflags)
SDL_LDFLAGS := $(shell sdl2-config --libs) -lpthread SDL_LDFLAGS := $(shell sdl2-config --libs) -lpthread
endif
ifeq ($(PLATFORM),Darwin) ifeq ($(PLATFORM),Darwin)
SDL_LDFLAGS += -framework AppKit SDL_LDFLAGS += -framework AppKit
endif endif
@ -282,11 +288,13 @@ sdl: $(BIN)/SDL/xaudio2_9redist.dll
endif endif
else else
LDFLAGS += -lc -lm LDFLAGS += -lc -lm
# libdl is not available as a standalone library in Haiku # libdl is not available as a standalone library in Haiku or OpenBSD
ifneq ($(PLATFORM),Haiku) ifneq ($(PLATFORM),Haiku)
ifneq ($(PLATFORM),OpenBSD)
LDFLAGS += -ldl LDFLAGS += -ldl
endif endif
endif endif
endif
ifeq ($(MAKECMDGOALS),_ios) ifeq ($(MAKECMDGOALS),_ios)
OBJ := build/obj-ios OBJ := build/obj-ios
@ -429,7 +437,8 @@ SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES))
TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES)) TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES))
XDG_THUMBNAILER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(XDG_THUMBNAILER_SOURCES)) $(OBJ)/XdgThumbnailer/resources.c.o XDG_THUMBNAILER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(XDG_THUMBNAILER_SOURCES)) $(OBJ)/XdgThumbnailer/resources.c.o
lib: $(PUBLIC_HEADERS) lib: headers
headers: $(PUBLIC_HEADERS)
# Automatic dependency generation # Automatic dependency generation
@ -541,9 +550,10 @@ $(OBJ)/installer: iOS/installer.m
# Cocoa Port # Cocoa Port
$(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \
$(shell ls Cocoa/*.icns Cocoa/*.png) \ $(shell ls Cocoa/*.icns Cocoa/*.png Cocoa/*.car) \
Cocoa/License.html \ Cocoa/License.html \
Cocoa/Info.plist \ Cocoa/Info.plist \
Cocoa/SameBoy.entitlements \
Misc/registers.sym \ Misc/registers.sym \
$(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \
$(BIN)/SameBoy.app/Contents/Resources/mgb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/mgb_boot.bin \
@ -558,15 +568,13 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \
$(BIN)/SameBoy.app/Contents/PlugIns/Previewer.appex \ $(BIN)/SameBoy.app/Contents/PlugIns/Previewer.appex \
Shaders Shaders
$(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources
cp Cocoa/*.icns Cocoa/*.png Misc/registers.sym $(BIN)/SameBoy.app/Contents/Resources/ cp Cocoa/*.icns Cocoa/*.png Cocoa/*.car Misc/registers.sym $(BIN)/SameBoy.app/Contents/Resources/
sed "s/@VERSION/$(VERSION)/;s/@COPYRIGHT_YEAR/$(COPYRIGHT_YEAR)/" < Cocoa/Info.plist > $(BIN)/SameBoy.app/Contents/Info.plist sed "s/@VERSION/$(VERSION)/;s/@COPYRIGHT_YEAR/$(COPYRIGHT_YEAR)/" < Cocoa/Info.plist > $(BIN)/SameBoy.app/Contents/Info.plist
sed "s/@COPYRIGHT_YEAR/$(COPYRIGHT_YEAR)/" < Cocoa/License.html > $(BIN)/SameBoy.app/Contents/Resources/Credits.html sed "s/@COPYRIGHT_YEAR/$(COPYRIGHT_YEAR)/" < Cocoa/License.html > $(BIN)/SameBoy.app/Contents/Resources/Credits.html
$(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources/Shaders $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Resources/Shaders
cp Shaders/*.fsh Shaders/*.metal $(BIN)/SameBoy.app/Contents/Resources/Shaders cp Shaders/*.fsh Shaders/*.metal $(BIN)/SameBoy.app/Contents/Resources/Shaders
$(MKDIR) -p $(BIN)/SameBoy.app/Contents/Library/QuickLook/ $(MKDIR) -p $(BIN)/SameBoy.app/Contents/Library/QuickLook/
ifeq ($(CONF), release) $(CODESIGN) $@ --entitlements Cocoa/SameBoy.entitlements
$(CODESIGN) $@
endif
# We place the dylib inside the Quick Look plugin, because Quick Look plugins run in a very strict sandbox # We place the dylib inside the Quick Look plugin, because Quick Look plugins run in a very strict sandbox
@ -583,10 +591,10 @@ ifeq ($(CONF), release)
endif endif
$(BIN)/SameBoy.app/Contents/Resources/%.nib: Cocoa/%.xib $(BIN)/SameBoy.app/Contents/Resources/%.nib: Cocoa/%.xib
ibtool --target-device mac --minimum-deployment-target 10.9 --compile $@ $^ 2>&1 | cat - $(IBTOOL) --target-device mac --minimum-deployment-target 10.9 --compile $@ $^ 2>&1 | cat -
$(BIN)/SameBoy-iOS.app/%.storyboardc: iOS/%.storyboard $(BIN)/SameBoy-iOS.app/%.storyboardc: iOS/%.storyboard
ibtool --target-device iphone --target-device ipad --minimum-deployment-target $(IOS_MIN) --compile $@ $^ 2>&1 | cat - $(IBTOOL) --target-device iphone --target-device ipad --minimum-deployment-target $(IOS_MIN) --compile $@ $^ 2>&1 | cat -
# Quick Look generators # Quick Look generators
@ -666,11 +674,11 @@ $(BIN)/SDL/sameboy.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.
$(BIN)/SDL/sameboy_debugger.txt: $(BIN)/SDL/sameboy_debugger.txt:
echo Looking for sameboy_debugger.exe? > $@ echo Looking for sameboy_debugger.exe? > $@
echo\>> $@ echo >> $@
echo Starting with SameBoy v1.0.1, sameboy.exe and sameboy_debugger.exe >> $@ echo Starting with SameBoy v1.0.1, sameboy.exe and sameboy_debugger.exe >> $@
echo have been merged into a single executable. You can open a debugger >> $@ echo have been merged into a single executable. You can open a debugger >> $@
echo console at any time by pressing Ctrl+C to interrupt the currently >> $@ echo console at any time by pressing Ctrl+C to interrupt the currently >> $@
echo open ROM. Once you're done debugging, you can close the debugger >> $@ echo open ROM. Once you\'re done debugging, you can close the debugger >> $@
echo console and resume normal execution. >> $@ echo console and resume normal execution. >> $@
ifneq ($(USE_WINDRES),) ifneq ($(USE_WINDRES),)
@ -683,13 +691,13 @@ $(OBJ)/%.res: %.rc
rc /fo $@ /dVERSION=\"$(VERSION)\" /dCOPYRIGHT_YEAR=\"$(COPYRIGHT_YEAR)\" $^ rc /fo $@ /dVERSION=\"$(VERSION)\" /dCOPYRIGHT_YEAR=\"$(COPYRIGHT_YEAR)\" $^
%.o: %.res %.o: %.res
cvtres /OUT:"$@" $^ cvtres /MACHINE:X64 /OUT:"$@" $^
endif endif
# Copy required DLL files for the Windows port # Copy required DLL files for the Windows port
$(BIN)/SDL/%.dll: $(BIN)/SDL/%.dll:
-@$(MKDIR) -p $(dir $@) -@$(MKDIR) -p $(dir $@)
@$(eval MATCH := $(shell where $$LIB:$(notdir $@))) @$(eval MATCH := $(shell where "$(lib)":$(notdir $@)))
cp "$(MATCH)" $@ cp "$(MATCH)" $@
# Tester # Tester
@ -781,7 +789,7 @@ install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop)
install -d $(DESTDIR)$(DATA_DIR)/BootROMs install -d $(DESTDIR)$(DATA_DIR)/BootROMs
install -d $(DESTDIR)$(PREFIX)/bin install -d $(DESTDIR)$(PREFIX)/bin
install -d $(DESTDIR)$(PREFIX)/share/thumbnailers install -d $(DESTDIR)$(PREFIX)/share/thumbnailers
install -d $(DESTDIR)$(PREFIX)/share/mime install -d $(DESTDIR)$(PREFIX)/share/mime/packages
install -d $(DESTDIR)$(PREFIX)/share/applications install -d $(DESTDIR)$(PREFIX)/share/applications
(cd $(BIN)/SDL && find . \! -name sameboy -type f -exec install -m 644 {} "$(abspath $(DESTDIR))$(DATA_DIR)/{}" \; ) (cd $(BIN)/SDL && find . \! -name sameboy -type f -exec install -m 644 {} "$(abspath $(DESTDIR))$(DATA_DIR)/{}" \; )
@ -797,7 +805,7 @@ ifeq ($(DESTDIR),)
xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/ColorCartridge/$${size}x$${size}.png x-gameboy-color-rom; \ xdg-icon-resource install --novendor --theme hicolor --size $$size --context mimetypes FreeDesktop/ColorCartridge/$${size}x$${size}.png x-gameboy-color-rom; \
done done
else else
install -m 644 FreeDesktop/sameboy.xml $(DESTDIR)$(PREFIX)/share/mime/sameboy.xml install -m 644 FreeDesktop/sameboy.xml $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml
install -m 644 FreeDesktop/sameboy.desktop $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop install -m 644 FreeDesktop/sameboy.desktop $(DESTDIR)$(PREFIX)/share/applications/sameboy.desktop
for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \ for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \
install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/apps; \ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/apps; \

View File

@ -47,8 +47,9 @@ SameBoy requires the following tools and libraries to build:
On Windows, SameBoy also requires: On Windows, SameBoy also requires:
* Visual Studio (For headers, etc.) * Visual Studio (For headers, etc.)
* [GnuWin](http://gnuwin32.sourceforge.net/) * [Git Bash](https://git-scm.com/downloads/win) or another distribution of basic Unix utilities
* Running vcvars64 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, respectively. (see [Build FAQ](https://github.com/LIJI32/SameBoy/blob/master/build-faq.md) for more details on Windows compilation) * Git Bash does not include make, you can get it [here](https://sourceforge.net/projects/ezwinports/files/make-4.4.1-without-guile-w32-bin.zip/download).
* Running `vcvars64.bat` or `vcvarsx86_amd64.bat` before running make. Make sure all required tools, libraries, and headers are in %PATH%, %lib%, and %include%`, respectively. (see [Build FAQ](https://github.com/LIJI32/SameBoy/blob/master/build-faq.md) for more details on Windows compilation)
To compile, simply run `make`. The targets are: To compile, simply run `make`. The targets are:
* `cocoa` (Default for macOS) * `cocoa` (Default for macOS)

View File

@ -161,6 +161,10 @@ typedef struct {
/* v1.0.1 */ /* v1.0.1 */
bool disable_rounded_corners; // Windows only bool disable_rounded_corners; // Windows only
bool use_faux_analog_inputs; bool use_faux_analog_inputs;
/* v1.0.2 */
int8_t vsync_mode;
uint8_t turbo_cap;
}; };
} configuration_t; } configuration_t;

View File

@ -1173,6 +1173,45 @@ static const char *current_agb_revision_string(unsigned index)
return "CPU AGB A (AGB)"; return "CPU AGB A (AGB)";
} }
static void cycle_turbo_cap(unsigned index)
{
if (configuration.turbo_cap >= 16) { // 400%
configuration.turbo_cap = 0; // uncapped
}
else if (configuration.turbo_cap == 0) { // uncapped
configuration.turbo_cap = 6; // 150%
}
else {
configuration.turbo_cap++;
}
}
static void cycle_turbo_cap_backwards(unsigned index)
{
if (configuration.turbo_cap == 0) { // uncapped
configuration.turbo_cap = 16; // 400%
}
else if (configuration.turbo_cap == 6) { // 150%
configuration.turbo_cap = 0; // uncapped
}
else {
configuration.turbo_cap--;
}
}
static const char *current_turbo_cap_string(unsigned index)
{
if (configuration.turbo_cap == 0) {
return "Uncapped";
}
static char ret[5];
snprintf(ret, sizeof(ret), "%d%%", configuration.turbo_cap * 25);
return ret;
}
static const struct menu_item emulation_menu[] = { static const struct menu_item emulation_menu[] = {
{"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards}, {"Emulated Model:", cycle_model, current_model_string, cycle_model_backwards},
{"SGB Revision:", cycle_sgb_revision, current_sgb_revision_string, cycle_sgb_revision_backwards}, {"SGB Revision:", cycle_sgb_revision, current_sgb_revision_string, cycle_sgb_revision_backwards},
@ -1181,6 +1220,7 @@ static const struct menu_item emulation_menu[] = {
{"Boot ROMs Folder:", toggle_bootrom, current_bootrom_string, toggle_bootrom}, {"Boot ROMs Folder:", toggle_bootrom, current_bootrom_string, toggle_bootrom},
{"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards}, {"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards},
{"Real Time Clock:", toggle_rtc_mode, current_rtc_mode_string, toggle_rtc_mode}, {"Real Time Clock:", toggle_rtc_mode, current_rtc_mode_string, toggle_rtc_mode},
{"Turbo speed cap:", cycle_turbo_cap, current_turbo_cap_string, cycle_turbo_cap_backwards},
{"Back", enter_options_menu}, {"Back", enter_options_menu},
{NULL,} {NULL,}
}; };
@ -1598,6 +1638,40 @@ static const char *current_osd_mode(unsigned index)
return configuration.osd? "Enabled" : "Disabled"; return configuration.osd? "Enabled" : "Disabled";
} }
static const char *current_vsync_mode(unsigned index)
{
switch (configuration.vsync_mode) {
default:
case 0: return "Disabled";
case 1: return "Enabled";
case -1: return "Adaptive";
}
}
static void cycle_vsync(unsigned index)
{
retry:
configuration.vsync_mode++;
if (configuration.vsync_mode == 2) {
configuration.vsync_mode = -1;
}
if (SDL_GL_SetSwapInterval(configuration.vsync_mode) && configuration.vsync_mode != 0) {
goto retry;
}
}
static void cycle_vsync_backwards(unsigned index)
{
retry:
configuration.vsync_mode--;
if (configuration.vsync_mode == -2) {
configuration.vsync_mode = 1;
}
if (SDL_GL_SetSwapInterval(configuration.vsync_mode) && configuration.vsync_mode != 0) {
goto retry;
}
}
#ifdef _WIN32 #ifdef _WIN32
// Don't use the standard header definitions because we might not have the newest headers // Don't use the standard header definitions because we might not have the newest headers
@ -1652,6 +1726,7 @@ static const struct menu_item graphics_menu[] = {
{"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards}, {"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards},
{"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards}, {"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards},
{"On-Screen Display:", toggle_osd, current_osd_mode, toggle_osd}, {"On-Screen Display:", toggle_osd, current_osd_mode, toggle_osd},
{"Vsync Mode:", cycle_vsync, current_vsync_mode, cycle_vsync_backwards},
#ifdef _WIN32 #ifdef _WIN32
{"Window Corners:", toggle_corners, current_corner_mode, toggle_corners}, {"Window Corners:", toggle_corners, current_corner_mode, toggle_corners},
#endif #endif
@ -2316,6 +2391,10 @@ void run_gui(bool is_running)
case SDL_SCANCODE_LEFT: case SDL_SCANCODE_LEFT:
case SDL_SCANCODE_UP: case SDL_SCANCODE_UP:
case SDL_SCANCODE_DOWN: case SDL_SCANCODE_DOWN:
case SDL_SCANCODE_H:
case SDL_SCANCODE_J:
case SDL_SCANCODE_K:
case SDL_SCANCODE_L:
break; break;
default: default:
@ -2703,12 +2782,16 @@ void run_gui(bool is_running)
} }
} }
else if (gui_state == SHOWING_MENU) { else if (gui_state == SHOWING_MENU) {
if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) { if ((event.key.keysym.scancode == SDL_SCANCODE_DOWN ||
event.key.keysym.scancode == SDL_SCANCODE_J) &&
current_menu[current_selection + 1].string) {
current_selection++; current_selection++;
mouse_scroling = false; mouse_scroling = false;
should_render = true; should_render = true;
} }
else if (event.key.keysym.scancode == SDL_SCANCODE_UP && current_selection) { else if ((event.key.keysym.scancode == SDL_SCANCODE_UP ||
event.key.keysym.scancode == SDL_SCANCODE_K) &&
current_selection) {
current_selection--; current_selection--;
mouse_scroling = false; mouse_scroling = false;
should_render = true; should_render = true;
@ -2732,11 +2815,15 @@ void run_gui(bool is_running)
return; return;
} }
} }
else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT && current_menu[current_selection].backwards_handler) { else if ((event.key.keysym.scancode == SDL_SCANCODE_RIGHT ||
event.key.keysym.scancode == SDL_SCANCODE_L) &&
current_menu[current_selection].backwards_handler) {
current_menu[current_selection].handler(current_selection); current_menu[current_selection].handler(current_selection);
should_render = true; should_render = true;
} }
else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT && current_menu[current_selection].backwards_handler) { else if ((event.key.keysym.scancode == SDL_SCANCODE_LEFT ||
event.key.keysym.scancode == SDL_SCANCODE_H) &&
current_menu[current_selection].backwards_handler) {
current_menu[current_selection].backwards_handler(current_selection); current_menu[current_selection].backwards_handler(current_selection);
should_render = true; should_render = true;
} }

View File

@ -277,6 +277,7 @@ static void open_menu(void)
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
GB_set_rewind_length(&gb, configuration.rewind_length); GB_set_rewind_length(&gb, configuration.rewind_length);
GB_set_rtc_mode(&gb, configuration.rtc_mode); GB_set_rtc_mode(&gb, configuration.rtc_mode);
GB_set_turbo_cap(&gb, configuration.turbo_cap / 4.0);
if (previous_width != GB_get_screen_width(&gb)) { if (previous_width != GB_get_screen_width(&gb)) {
signed current_window_width, current_window_height; signed current_window_width, current_window_height;
SDL_GetWindowSize(window, &current_window_width, &current_window_height); SDL_GetWindowSize(window, &current_window_width, &current_window_height);
@ -381,6 +382,7 @@ static void handle_events(GB_gameboy_t *gb)
GB_audio_clear_queue(); GB_audio_clear_queue();
turbo_down = event.type == SDL_JOYBUTTONDOWN; turbo_down = event.type == SDL_JOYBUTTONDOWN;
GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down);
SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode);
} }
else if (button == JOYPAD_BUTTON_SLOW_MOTION) { else if (button == JOYPAD_BUTTON_SLOW_MOTION) {
underclock_down = event.type == SDL_JOYBUTTONDOWN; underclock_down = event.type == SDL_JOYBUTTONDOWN;
@ -391,6 +393,7 @@ static void handle_events(GB_gameboy_t *gb)
rewind_paused = false; rewind_paused = false;
} }
GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down);
SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode);
} }
else if (button == JOYPAD_BUTTON_MENU && event.type == SDL_JOYBUTTONDOWN) { else if (button == JOYPAD_BUTTON_MENU && event.type == SDL_JOYBUTTONDOWN) {
open_menu(); open_menu();
@ -610,6 +613,7 @@ static void handle_events(GB_gameboy_t *gb)
turbo_down = event.type == SDL_KEYDOWN; turbo_down = event.type == SDL_KEYDOWN;
GB_audio_clear_queue(); GB_audio_clear_queue();
GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down);
SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode);
} }
else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_REWIND]) { else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_REWIND]) {
rewind_down = event.type == SDL_KEYDOWN; rewind_down = event.type == SDL_KEYDOWN;
@ -617,6 +621,7 @@ static void handle_events(GB_gameboy_t *gb)
rewind_paused = false; rewind_paused = false;
} }
GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down); GB_set_turbo_mode(gb, turbo_down, turbo_down && rewind_down);
SDL_GL_SetSwapInterval(turbo_down? 0 : configuration.vsync_mode);
} }
else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_UNDERCLOCK]) { else if (event.key.keysym.scancode == configuration.keys_2[GB_CONF_KEYS2_UNDERCLOCK]) {
underclock_down = event.type == SDL_KEYDOWN; underclock_down = event.type == SDL_KEYDOWN;
@ -737,15 +742,23 @@ static void debugger_reset(int ignore)
#endif #endif
static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
{ {
if (turbo_down) { if (turbo_down) {
static unsigned skip = 0; static unsigned skip = 0;
skip++; skip++;
if (skip == GB_audio_get_frequency() / 8) { if (skip == GB_audio_get_frequency() / 8) {
skip = 0; skip = 0;
} }
if (skip > GB_audio_get_frequency() / 16) { if (configuration.turbo_cap) {
return; if (skip > GB_audio_get_frequency() / 8 * 4 / configuration.turbo_cap) {
return;
}
}
else {
if (skip > GB_audio_get_frequency() / 16) {
return;
}
} }
} }
@ -1095,6 +1108,7 @@ restart:;
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode); GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
GB_set_rewind_length(&gb, configuration.rewind_length); GB_set_rewind_length(&gb, configuration.rewind_length);
GB_set_rtc_mode(&gb, configuration.rtc_mode); GB_set_rtc_mode(&gb, configuration.rtc_mode);
GB_set_turbo_cap(&gb, configuration.turbo_cap / 4.0);
GB_set_update_input_hint_callback(&gb, handle_events); GB_set_update_input_hint_callback(&gb, handle_events);
GB_apu_set_sample_callback(&gb, gb_audio_callback); GB_apu_set_sample_callback(&gb, gb_audio_callback);
@ -1382,7 +1396,9 @@ int main(int argc, char **argv)
signal(SIGUSR1, debugger_reset); signal(SIGUSR1, debugger_reset);
#endif #endif
SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_AUDIO); if (SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_AUDIO) < 0) {
fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
}
// This is, essentially, best-effort. // This is, essentially, best-effort.
// This function will not be called if the process is terminated in any way, anyhow. // This function will not be called if the process is terminated in any way, anyhow.
atexit(SDL_Quit); atexit(SDL_Quit);
@ -1536,6 +1552,8 @@ int main(int argc, char **argv)
} }
#endif #endif
SDL_GL_SetSwapInterval(configuration.vsync_mode);
if (filename == NULL) { if (filename == NULL) {
stop_on_start = false; stop_on_start = false;
run_gui(false); run_gui(false);

View File

@ -26,9 +26,9 @@ The following examples will be referenced later:
After downloading [rgbds](https://github.com/gbdev/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`. After downloading [rgbds](https://github.com/gbdev/rgbds/releases/), ensure that it is added to the `%PATH%`. This may be done by adding it to the user's or SYSTEM's Environment Variables, or may be added to the command line at compilation time via `set path=%path%;C:\path\to\rgbds`.
### GnuWin ### Git Bash & Make
Ensure that the `gnuwin32\bin\` directory is included in `%PATH%`. Like rgbds above, this may instead be manually included on the command line before installation: `set path=%path%;C:\path\to\gnuwin32\bin`. Ensure that the `Git\usr\bin` directory is included in `%PATH%`. Like rgbds above, this may instead be manually included on the command line before installation: `set path=%path%;C:\path\to\Git\usr\bin`. Similarly, make sure that the directory containing `make.exe` is also included.
## Building ## Building
@ -40,18 +40,5 @@ set lib=%lib%;C:\SDL2\lib\x64
set include=%include%;C:\SDL2\include set include=%include%;C:\SDL2\include
make make
``` ```
Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `%PATH%` properly includes `rgbds` and `gnuwin32\bin`, and that the `lib` and `include` paths include the appropriate SDL2 directories. On some versions of Visual Studio, you might need to use `vcvarsx86_amd64` instead of `vcvars64`. Please note that these directories (`C:\SDL2\*`) are the examples given within the "SDL Port" section above. Ensure that your `%PATH%` properly includes `rgbds` and `Git\usr\bin`, and that the `lib` and `include` paths include the appropriate SDL2 directories.
## Common Errors
### Error -1073741819
If encountering an error that appears as follows:
``` make: *** [build/bin/BootROMs/dmg_boot.bin] Error -1073741819```
Simply run `make` again, and the process will continue. This appears to happen occasionally with `build/bin/BootROMs/dmg_boot.bin` and `build/bin/BootROMs/sgb2_boot.bin`. It does not affect the compiled output. This appears to be an issue with GnuWin.
### The system cannot find the file specified (`usr/bin/mkdir`)
If errors arise (i.e., particularly with the `CREATE_PROCESS('usr/bin/mkdir')` calls, also verify that Git for Windows has not been installed with full Linux support. If it has, remove `C:\Program Files\Git\usr\bin` from the SYSTEM %PATH% until after compilation. This happens because the Git for Windows version of `which` is used instead of the GnuWin one, and it returns a Unix-style path instead of a Windows one.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

BIN
iOS/Cartridge64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

BIN
iOS/ColorCartridge64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -24,7 +24,8 @@
if (section == 0) return 1; if (section == 0) return 1;
size_t count; size_t count;
GB_get_cheats(_gb, &count); GB_get_cheats(_gb, &count);
self.toolbarItems.firstObject.enabled = count; self.toolbarItems[0].enabled = count;
((UIButton *)(self.toolbarItems[0].customView.subviews[0])).enabled = count;
return count; return count;
} }
@ -91,11 +92,11 @@
} }
else { else {
alertController.title = @"Invalid cheat code entered"; alertController.title = @"Invalid cheat code entered";
[self presentViewController:alertController animated:YES completion:nil]; [self presentViewController:alertController animated:true completion:nil];
} }
}]]; }]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alertController animated:YES completion:nil]; [self presentViewController:alertController animated:true completion:nil];
} }
+ (UIBarButtonItem *)buttonWithLabel:(NSString *)label + (UIBarButtonItem *)buttonWithLabel:(NSString *)label
@ -119,9 +120,17 @@
[button sizeToFit]; [button sizeToFit];
CGRect frame = button.frame; CGRect frame = button.frame;
frame.size.width = ceil(frame.size.width + (label? 4 : 0)); frame.size.width = ceil(frame.size.width + (label? 4 : 0));
if (@available(iOS 19.0, *)) {
if (label) {
frame.size.width += 12;
}
}
frame.size.height = 28; frame.size.height = 28;
button.frame = frame; button.frame = frame;
return [[UIBarButtonItem alloc] initWithCustomView:button]; UIView *wrapper = [[UIView alloc] initWithFrame:button.bounds];
[wrapper addSubview:button];
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:wrapper];
return item;
} }
return [[UIBarButtonItem alloc] initWithTitle:label style:UIBarButtonItemStylePlain target:target action:action]; return [[UIBarButtonItem alloc] initWithTitle:label style:UIBarButtonItemStylePlain target:target action:action];
} }
@ -153,9 +162,7 @@
UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url] UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url]
applicationActivities:nil]; applicationActivities:nil];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { controller.popoverPresentationController.barButtonItem = self.toolbarItems.firstObject;
controller.popoverPresentationController.barButtonItem = self.toolbarItems.firstObject;
}
[self presentViewController:controller [self presentViewController:controller
animated:true animated:true
@ -178,28 +185,38 @@
hasSFSymbols = true; hasSFSymbols = true;
} }
self.toolbarItems = @[ UIBarButtonItem *export = hasSFSymbols?
hasSFSymbols? [self.class buttonWithLabel:nil
[self.class buttonWithLabel:nil imageWithName:@"square.and.arrow.up"
imageWithName:@"square.and.arrow.up"
target:self
action:@selector(exportCheats)] :
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
target:self
action:@selector(exportCheats)],
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:NULL],
[self.class buttonWithLabel:@"Import"
imageWithName:@"square.and.arrow.down"
target:self target:self
action:@selector(importCheats)], action:@selector(exportCheats)] :
[self.class buttonWithLabel:@"Add" [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
imageWithName:@"plus" target:self
target:self action:@selector(exportCheats)];
action:@selector(addCheat)],
UIBarButtonItem *flexItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
]; target:nil
action:NULL];
UIBarButtonItem *import = [self.class buttonWithLabel:@"Import"
imageWithName:@"square.and.arrow.down"
target:self
action:@selector(importCheats)];
UIBarButtonItem *add = [self.class buttonWithLabel:@"Add"
imageWithName:@"plus"
target:self
action:@selector(addCheat)];
if (@available(iOS 19.0, *)) {
self.toolbarItems = @[export,
flexItem,
import, [UIBarButtonItem fixedSpaceItemOfWidth:0], add];
}
else {
self.toolbarItems = @[export,
flexItem,
import, add];
}
_gb = gb; _gb = gb;
return self; return self;

View File

@ -12,6 +12,9 @@ static double StatusBarHeight(void)
UIEdgeInsets insets = window.safeAreaInsets; UIEdgeInsets insets = window.safeAreaInsets;
ret = MAX(MAX(insets.left, insets.right), MAX(insets.top, insets.bottom)) ?: 20; ret = MAX(MAX(insets.left, insets.right), MAX(insets.top, insets.bottom)) ?: 20;
[window setHidden:true]; [window setHidden:true];
if (!ret && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
ret = 32; // iPadOS is buggy af
}
} }
}); });
return ret; return ret;
@ -47,7 +50,7 @@ static bool HasHomeBar(void)
} }
_minY = StatusBarHeight() * _factor; _minY = StatusBarHeight() * _factor;
_cutout = _minY <= 24 * _factor? 0 : _minY; _cutout = (_minY <= 24 * _factor || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)? 0 : _minY;
if (HasHomeBar()) { if (HasHomeBar()) {
_homeBar = 21 * _factor; _homeBar = 21 * _factor;

View File

@ -2,4 +2,6 @@
@interface GBMenuViewController : UIAlertController @interface GBMenuViewController : UIAlertController
+ (instancetype)menu; + (instancetype)menu;
@property (nonatomic) NSInteger selectedButtonIndex;
@property (nonatomic, strong) NSArray<UIButton *> *menuButtons;
@end @end

View File

@ -29,6 +29,7 @@ static NSString *const tips[] = {
{ {
UILabel *_tipLabel; UILabel *_tipLabel;
UIVisualEffectView *_effectView; UIVisualEffectView *_effectView;
NSMutableArray<UIButton *> *_buttons;
} }
+ (instancetype)menu + (instancetype)menu
@ -41,6 +42,8 @@ static NSString *const tips[] = {
[ret addAction:[UIAlertAction actionWithTitle:@"Close" [ret addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel
handler:nil]]; handler:nil]];
ret.selectedButtonIndex = -1;
ret->_buttons = [[NSMutableArray alloc] init];
return ret; return ret;
} }
@ -50,6 +53,7 @@ static NSString *const tips[] = {
- (void)viewWillAppear:(BOOL)animated - (void)viewWillAppear:(BOOL)animated
{ {
[super viewWillAppear:true]; [super viewWillAppear:true];
if (_effectView) return;
static const struct { static const struct {
NSString *label; NSString *label;
NSString *image; NSString *image;
@ -80,6 +84,7 @@ static NSString *const tips[] = {
button.frame = CGRectMake(round(width * x), height * y, round(width), height); button.frame = CGRectMake(round(width * x), height * y, round(width), height);
button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
[self.view addSubview:button]; [self.view addSubview:button];
[_buttons addObject:button];
if (!buttons[i].selector) { if (!buttons[i].selector) {
button.enabled = false; button.enabled = false;
@ -102,10 +107,23 @@ static NSString *const tips[] = {
[button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside]; [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
} }
_effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleProminent]]; self.menuButtons = [_buttons copy];
[self updateSelectedButton];
UIVisualEffect *effect = nil;
/*
// Unfortunately, UIGlassEffect is still very buggy.
if (@available(iOS 19.0, *)) {
effect = [[objc_getClass("UIGlassEffect") alloc] init];
}
else */ {
effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleProminent];
}
_effectView = [[UIVisualEffectView alloc] initWithEffect:nil];
_effectView.layer.cornerRadius = 8; _effectView.layer.cornerRadius = 8;
_effectView.layer.masksToBounds = true; _effectView.layer.masksToBounds = true;
[self.view.superview addSubview:_effectView]; [self.view.window addSubview:_effectView];
_tipLabel = [[UILabel alloc] init]; _tipLabel = [[UILabel alloc] init];
unsigned tipIndex = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBTipIndex"]; unsigned tipIndex = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBTipIndex"];
_tipLabel.text = tips[tipIndex % (sizeof(tips) / sizeof(tips[0]))]; _tipLabel.text = tips[tipIndex % (sizeof(tips) / sizeof(tips[0]))];
@ -113,20 +131,23 @@ static NSString *const tips[] = {
_tipLabel.textColor = [UIColor labelColor]; _tipLabel.textColor = [UIColor labelColor];
} }
_tipLabel.font = [UIFont systemFontOfSize:14]; _tipLabel.font = [UIFont systemFontOfSize:14];
_tipLabel.alpha = 0.8; _tipLabel.alpha = 0;
[[NSUserDefaults standardUserDefaults] setInteger:tipIndex + 1 forKey:@"GBTipIndex"]; [[NSUserDefaults standardUserDefaults] setInteger:tipIndex + 1 forKey:@"GBTipIndex"];
_tipLabel.lineBreakMode = NSLineBreakByWordWrapping; _tipLabel.lineBreakMode = NSLineBreakByWordWrapping;
_tipLabel.numberOfLines = 3; _tipLabel.numberOfLines = 3;
[_effectView.contentView addSubview:_tipLabel]; [_effectView.contentView addSubview:_tipLabel];
[self layoutTip]; [self layoutTip];
_effectView.alpha = 0;
[UIView animateWithDuration:0.25 animations:^{ [UIView animateWithDuration:0.25 animations:^{
_effectView.alpha = 1.0; _effectView.effect = effect;
_tipLabel.alpha = 0.8;
}]; }];
} }
- (void)layoutTip - (void)layoutTip
{ {
[_effectView.superview addSubview:_effectView];
UIView *view = self.view.superview; UIView *view = self.view.superview;
CGSize outerSize = view.frame.size; CGSize outerSize = view.frame.size;
CGSize size = [_tipLabel textRectForBounds:(CGRect){{0, 0}, CGSize size = [_tipLabel textRectForBounds:(CGRect){{0, 0},
@ -135,8 +156,12 @@ static NSString *const tips[] = {
limitedToNumberOfLines:3].size; limitedToNumberOfLines:3].size;
size.width = ceil(size.width); size.width = ceil(size.width);
_tipLabel.frame = (CGRect){{8, 8}, size}; _tipLabel.frame = (CGRect){{8, 8}, size};
unsigned topInset = view.window.safeAreaInsets.top;
if (!topInset && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
topInset = 32; // iPadOS is buggy af
}
_effectView.frame = (CGRect) { _effectView.frame = (CGRect) {
{round((outerSize.width - size.width - 16) / 2), view.window.safeAreaInsets.top + 12}, {round((outerSize.width - size.width - 16) / 2), topInset + 12},
{size.width + 16, size.height + 16} {size.width + 16, size.height + 16}
}; };
} }
@ -145,7 +170,10 @@ static NSString *const tips[] = {
- (void)viewWillDisappear:(BOOL)animated - (void)viewWillDisappear:(BOOL)animated
{ {
[UIView animateWithDuration:0.25 animations:^{ [UIView animateWithDuration:0.25 animations:^{
_effectView.alpha = 0; _effectView.effect = nil;
_tipLabel.alpha = 0;
} completion:^(BOOL finished) {
[_effectView removeFromSuperview];
}]; }];
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
} }
@ -189,4 +217,116 @@ static NSString *const tips[] = {
return [[UIViewController alloc] init]; return [[UIViewController alloc] init];
} }
#pragma mark - Vim Navigation
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (NSArray<UIKeyCommand *> *)keyCommands
{
return @[
[UIKeyCommand keyCommandWithInput:@"h" modifierFlags:0 action:@selector(moveLeft)],
[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:0 action:@selector(moveLeft)],
[UIKeyCommand keyCommandWithInput:@"j" modifierFlags:0 action:@selector(moveDown)],
[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:0 action:@selector(moveDown)],
[UIKeyCommand keyCommandWithInput:@"k" modifierFlags:0 action:@selector(moveUp)],
[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:0 action:@selector(moveUp)],
[UIKeyCommand keyCommandWithInput:@"l" modifierFlags:0 action:@selector(moveRight)],
[UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:0 action:@selector(moveRight)],
[UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(activateSelected)],
[UIKeyCommand keyCommandWithInput:@" " modifierFlags:0 action:@selector(activateSelected)],
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(dismissSelf)],
];
}
- (void)moveLeft
{
if (self.selectedButtonIndex == -1) {
self.selectedButtonIndex = 0;
}
else if (self.selectedButtonIndex % 4 > 0) {
self.selectedButtonIndex--;
}
[self updateSelectedButton];
}
- (void)moveRight
{
if (self.selectedButtonIndex == -1) {
self.selectedButtonIndex = 0;
}
else if (self.selectedButtonIndex % 4 < 3 && self.selectedButtonIndex + 1 < self.menuButtons.count) {
self.selectedButtonIndex++;
}
[self updateSelectedButton];
}
- (void)moveUp
{
if (self.selectedButtonIndex == -1) {
self.selectedButtonIndex = 0;
}
else if (self.selectedButtonIndex >= 4) {
self.selectedButtonIndex -= 4;
}
[self updateSelectedButton];
}
- (void)moveDown
{
if (self.selectedButtonIndex == -1) {
self.selectedButtonIndex = 0;
}
else if (self.selectedButtonIndex + 4 < self.menuButtons.count) {
self.selectedButtonIndex += 4;
}
[self updateSelectedButton];
}
- (void)activateSelected
{
if (self.selectedButtonIndex >= 0 && self.selectedButtonIndex < self.menuButtons.count) {
UIButton *button = self.menuButtons[self.selectedButtonIndex];
if (button.enabled) {
[button sendActionsForControlEvents:UIControlEventTouchUpInside];
}
}
}
- (void)updateSelectedButton
{
for (NSInteger i = 0; i < self.menuButtons.count; i++) {
UIButton *button = self.menuButtons[i];
if (i == self.selectedButtonIndex) {
button.backgroundColor = [UIColor colorWithWhite:0.5 alpha:0.3];
button.layer.borderWidth = 2.0;
button.layer.borderColor = [UIColor systemBlueColor].CGColor;
if (@available(iOS 19.0, *)) {
button.layer.cornerRadius = 32.0;
}
else {
button.layer.cornerRadius = 8.0;
}
}
else {
button.backgroundColor = [UIColor clearColor];
button.layer.borderWidth = 0.0;
button.layer.borderColor = [UIColor clearColor].CGColor;
}
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)dismissSelf
{
[self.presentingViewController dismissViewControllerAnimated:true completion:nil];
}
@end @end

View File

@ -10,6 +10,7 @@
@property (readonly) NSString *batterySaveFile; @property (readonly) NSString *batterySaveFile;
@property (readonly) NSString *autosaveStateFile; @property (readonly) NSString *autosaveStateFile;
@property (readonly) NSString *cheatsFile; @property (readonly) NSString *cheatsFile;
@property (readonly) NSArray <NSString *> *forbiddenNames;
@property (readonly) NSString *localRoot; @property (readonly) NSString *localRoot;
- (NSString *)stateFile:(unsigned)index; - (NSString *)stateFile:(unsigned)index;

View File

@ -25,12 +25,26 @@
if (!self) return nil; if (!self) return nil;
self.currentROM = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBLastROM"]; self.currentROM = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBLastROM"];
_doneInitializing = true; _doneInitializing = true;
// Pre 1.0.2 versions might have kept temp files in there incorrectly
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBDeletedInbox"]) {
[[NSFileManager defaultManager] removeItemAtPath:[self.localRoot stringByAppendingPathComponent:@"Inbox"] error:nil];
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBDeletedInbox"];
}
return self; return self;
} }
- (NSArray<NSString *> *)forbiddenNames
{
return @[@"Inbox", @"Boot ROMs"];
}
- (void)setCurrentROM:(NSString *)currentROM - (void)setCurrentROM:(NSString *)currentROM
{ {
_romFile = nil; _romFile = nil;
if ([self.forbiddenNames containsObject:currentROM]) {
currentROM = nil;
}
_currentROM = currentROM; _currentROM = currentROM;
bool foundROM = self.romFile; bool foundROM = self.romFile;
@ -58,6 +72,9 @@
- (NSString *)romDirectoryForROM:(NSString *)romFile - (NSString *)romDirectoryForROM:(NSString *)romFile
{ {
if ([self.forbiddenNames containsObject:romFile]) {
return nil;
}
return [self.localRoot stringByAppendingPathComponent:romFile]; return [self.localRoot stringByAppendingPathComponent:romFile];
} }
@ -71,12 +88,13 @@
- (NSString *)romFileForROM:(NSString *)rom - (NSString *)romFileForROM:(NSString *)rom
{ {
if ([rom isEqualToString:@"Inbox"]) return nil;
if ([rom isEqualToString:@"Boot ROMs"]) return nil;
if (rom == _currentROM) { if (rom == _currentROM) {
return self.romFile; return self.romFile;
} }
if ([self.forbiddenNames containsObject:rom]) {
return nil;
}
return [self romFileForDirectory:[self romDirectoryForROM:rom]]; return [self romFileForDirectory:[self romDirectoryForROM:rom]];
} }
@ -143,6 +161,9 @@
- (NSString *)makeNameUnique:(NSString *)name - (NSString *)makeNameUnique:(NSString *)name
{ {
if ([self.forbiddenNames containsObject:name]) {
name = @"Imported ROM";
}
NSString *root = self.localRoot; NSString *root = self.localRoot;
if (![[NSFileManager defaultManager] fileExistsAtPath:[root stringByAppendingPathComponent:name]]) return name; if (![[NSFileManager defaultManager] fileExistsAtPath:[root stringByAppendingPathComponent:name]]) return name;
@ -195,9 +216,11 @@
[[NSFileManager defaultManager] removeItemAtPath:romFolder error:nil]; [[NSFileManager defaultManager] removeItemAtPath:romFolder error:nil];
return nil; return nil;
} }
} }
// Remove the Inbox directory if empty after import
rmdir([self.localRoot stringByAppendingPathComponent:@"Inbox"].UTF8String);
return friendlyName; return friendlyName;
} }

View File

@ -247,8 +247,19 @@
if ([newName containsString:@"/"]) { if ([newName containsString:@"/"]) {
[self.tableView reloadData]; [self.tableView reloadData];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"You can't use a name that contains “/”. Please choose another name." UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Invalid Name"
message:nil message:@"You can't use a name that contains “/”. Please choose another name."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alert animated:true completion:nil];
return;
}
if ([[GBROMManager sharedManager].forbiddenNames containsObject:newName]) {
[self.tableView reloadData];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Invalid Name"
message:@"This name is reserved by SameBoy or iOS. Please choose another name."
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" [alert addAction:[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleCancel style:UIAlertActionStyleCancel

View File

@ -15,11 +15,10 @@ typedef enum {
GBTurbo, GBTurbo,
GBRewind, GBRewind,
GBUnderclock, GBUnderclock,
GBRapidA,
GBRapidB,
// GBHotkey1, // Todo // GBHotkey1, // Todo
// GBHotkey2, // Todo // GBHotkey2, // Todo
GBTotalButtonCount,
GBKeyboardButtonCount = GBUnderclock + 1,
GBPerPlayerButtonCount = GBStart + 1,
GBUnusedButton = 0xFF, GBUnusedButton = 0xFF,
} GBButton; } GBButton;

View File

@ -15,13 +15,34 @@ static NSString const *typeCheck = @"check";
static NSString const *typeDisabled = @"disabled"; static NSString const *typeDisabled = @"disabled";
static NSString const *typeSeparator = @"separator"; static NSString const *typeSeparator = @"separator";
static NSString const *typeSlider = @"slider"; static NSString const *typeSlider = @"slider";
static NSString const *typeLightTemp = @"typeLightTemp"; static NSString const *typeLightTemp = @"lightTemp";
static NSString const *typeTurboSlider = @"turboSlider";
@implementation GBSettingsViewController @implementation GBSettingsViewController
{ {
NSArray<NSDictionary *> *_structure; NSArray<NSDictionary *> *_structure;
UINavigationController *_detailsNavigation; UINavigationController *_detailsNavigation;
NSArray<NSArray<GBTheme *> *> *_themes; // For prewarming NSArray<NSArray<GBTheme *> *> *_themes; // For prewarming
bool _iPadRoot;
__weak UISlider *_turboSlider;
}
+ (UIImage *)settingsImageNamed:(NSString *)name
{
UIImage *base = [UIImage imageNamed:name];
UIGraphicsBeginImageContextWithOptions(base.size, false, base.scale);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:(CGRect){{0, 0}, base.size} cornerRadius:8];
CGContextSaveGState(UIGraphicsGetCurrentContext());
[path addClip];
[base drawInRect:path.bounds];
if (@available(iOS 19.0, *)) {
CGContextRestoreGState(UIGraphicsGetCurrentContext());
UIImage *overlay = [UIImage imageNamed:@"settingsOverlay"];
[overlay drawInRect:path.bounds];
}
UIImage *ret = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return ret;
} }
+ (NSArray<NSDictionary *> *)rootStructure + (NSArray<NSDictionary *> *)rootStructure
@ -48,17 +69,32 @@ static NSString const *typeLightTemp = @"typeLightTemp";
] ]
}, },
@{ @{
@"header": @"Turbo Speed", @"header": @"Turbo Speed Cap",
@"items": @[ @"items": @[
@{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"200%", @"value": @2,}, @{@"type": typeCheck, @"pref": @"GBTurboCap", @"title": @"Cap Turbo Speed", @"block": ^void(GBSettingsViewController *controller) {
@{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"400%", @"value": @4,}, if ([[NSUserDefaults standardUserDefaults] floatForKey:@"GBTurboCap"] == 1.0) {
@{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"Uncapped", @"value": @1,}, if (controller->_turboSlider) {
[[NSUserDefaults standardUserDefaults] setFloat:controller->_turboSlider.value forKey:@"GBTurboCap"];
controller->_turboSlider.enabled = true;
}
else {
[[NSUserDefaults standardUserDefaults] setFloat:2.0 forKey:@"GBTurboCap"];
}
}
else {
controller->_turboSlider.enabled = false;
}
}},
@{@"type": typeTurboSlider, @"pref": @"GBTurboCap", @"min": @1.5, @"max": @4, @"minImage": @"tortoise.fill", @"maxImage": @"hare.fill"},
], ],
@"footer": ^NSString *(){ @"footer": ^NSString *(){
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"]) { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"]) {
return @"This setting will have no effect because horizontal swipes are configured to dynamically control speed in the “Controls” settings"; return @"This setting will have no effect because horizontal swipes are configured to dynamically control speed in the “Controls” settings.";
} }
return @""; if ([[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"]) {
return [NSString stringWithFormat:@"Turbo speed is capped to %u%%", (unsigned)round([[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboCap"] * 100)];
}
return @"Turbo speed is not capped";
}, },
}, },
@{ @{
@ -169,7 +205,7 @@ static NSString const *typeLightTemp = @"typeLightTemp";
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"HQ2x", @"value": @"HQ2x"}, @{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"HQ2x", @"value": @"HQ2x"},
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"OmniScale", @"value": @"OmniScale"}, @{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"OmniScale", @"value": @"OmniScale"},
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"OmniScale Legacy", @"value": @"OmniScaleLegacy"}, @{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"OmniScale Legacy", @"value": @"OmniScaleLegacy"},
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"AA OmniScale Legacy", @"value": @"AAOmniScaleLegacy"}, @{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Anti-aliased OmniScale Legacy", @"value": @"AAOmniScaleLegacy"},
] ]
}, },
] ]
@ -365,31 +401,31 @@ static NSString const *typeLightTemp = @"typeLightTemp";
@"title": @"Emulation", @"title": @"Emulation",
@"type": typeSubmenu, @"type": typeSubmenu,
@"submenu": emulationMenu, @"submenu": emulationMenu,
@"image": [UIImage imageNamed:@"emulationSettings"], @"image": [self settingsImageNamed:@"emulationSettings"],
}, },
@{ @{
@"title": @"Video", @"title": @"Video",
@"type": typeSubmenu, @"type": typeSubmenu,
@"submenu": videoMenu, @"submenu": videoMenu,
@"image": [UIImage imageNamed:@"videoSettings"], @"image": [self settingsImageNamed:@"videoSettings"],
}, },
@{ @{
@"title": @"Audio", @"title": @"Audio",
@"type": typeSubmenu, @"type": typeSubmenu,
@"submenu": audioMenu, @"submenu": audioMenu,
@"image": [UIImage imageNamed:@"audioSettings"], @"image": [self settingsImageNamed:@"audioSettings"],
}, },
@{ @{
@"title": @"Controls", @"title": @"Controls",
@"type": typeSubmenu, @"type": typeSubmenu,
@"submenu": controlsMenu, @"submenu": controlsMenu,
@"image": [UIImage imageNamed:@"controlsSettings"], @"image": [self settingsImageNamed:@"controlsSettings"],
}, },
@{ @{
@"title": @"Themes", @"title": @"Themes",
@"type": typeSubmenu, @"type": typeSubmenu,
@"class": [GBThemesViewController class], @"class": [GBThemesViewController class],
@"image": [UIImage imageNamed:@"themeSettings"], @"image": [self settingsImageNamed:@"themeSettings"],
}, },
]; ];
@ -416,12 +452,20 @@ static NSString const *typeLightTemp = @"typeLightTemp";
return controller; return controller;
} }
UISplitViewController *split = [[UISplitViewController alloc] init]; UISplitViewController *split = nil;
if (@available(iOS 14.5, *)) {
split = [[UISplitViewController alloc] initWithStyle:UISplitViewControllerStyleDoubleColumn];
split.displayModeButtonVisibility = UISplitViewControllerDisplayModeButtonVisibilityNever;
}
else {
split = [[UISplitViewController alloc] init];
}
UIViewController *blank = [[UIViewController alloc] init]; UIViewController *blank = [[UIViewController alloc] init];
blank.view.backgroundColor = root.view.backgroundColor; blank.view.backgroundColor = root.view.backgroundColor;
root->_detailsNavigation = [[UINavigationController alloc] initWithRootViewController:blank]; root->_detailsNavigation = [[UINavigationController alloc] initWithRootViewController:blank];
split.viewControllers = @[controller, root->_detailsNavigation]; split.viewControllers = @[controller, root->_detailsNavigation];
split.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible; split.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
root->_iPadRoot = true;
return split; return split;
} }
@ -569,6 +613,8 @@ static NSString *LocalizedNameForElement(GCControllerElement *element, GBControl
@{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"B", @"value": @(GBB)}, @{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"B", @"value": @(GBB)},
@{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Select", @"value": @(GBSelect)}, @{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Select", @"value": @(GBSelect)},
@{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Start", @"value": @(GBStart)}, @{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Start", @"value": @(GBStart)},
@{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Rapid A", @"value": @(GBRapidA)},
@{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Rapid B", @"value": @(GBRapidB)},
@{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Turbo", @"value": @(GBTurbo)}, @{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Turbo", @"value": @(GBTurbo)},
@{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Rewind", @"value": @(GBRewind)}, @{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Rewind", @"value": @(GBRewind)},
@{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Slow-motion", @"value": @(GBUnderclock)}, @{@"type": typeRadio, @"getter": getter, @"setter": setter, @"title": @"Slow-motion", @"value": @(GBUnderclock)},
@ -709,14 +755,14 @@ static id ValueForItem(NSDictionary *item)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ {
NSDictionary *item = [self itemForIndexPath:indexPath]; NSDictionary *item = [self itemForIndexPath:indexPath];
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil]; UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
cell.textLabel.text = item[@"title"]; cell.textLabel.text = item[@"title"];
if (item[@"type"] == typeSubmenu || item[@"type"] == typeOptionSubmenu || item[@"type"] == typeBlock) { NSString *type = item[@"type"];
if (type == typeSubmenu || type == typeOptionSubmenu || type == typeBlock) {
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleBlue; cell.selectionStyle = UITableViewCellSelectionStyleBlue;
if (item[@"type"] == typeOptionSubmenu) { if (type == typeOptionSubmenu) {
for (NSDictionary *section in item[@"submenu"]) { for (NSDictionary *section in item[@"submenu"]) {
for (NSDictionary *item in section[@"items"]) { for (NSDictionary *item in section[@"items"]) {
if (item[@"value"] && [ValueForItem(item) isEqual:item[@"value"]]) { if (item[@"value"] && [ValueForItem(item) isEqual:item[@"value"]]) {
@ -730,14 +776,14 @@ static id ValueForItem(NSDictionary *item)
cell.detailTextLabel.text = [[NSUserDefaults standardUserDefaults] stringForKey:item[@"pref"]]; cell.detailTextLabel.text = [[NSUserDefaults standardUserDefaults] stringForKey:item[@"pref"]];
} }
} }
else if (item[@"type"] == typeRadio) { else if (type == typeRadio) {
id settingValue = ValueForItem(item); id settingValue = ValueForItem(item);
id itemValue = item[@"value"]; id itemValue = item[@"value"];
if (settingValue == itemValue || [settingValue isEqual:itemValue]) { if (settingValue == itemValue || [settingValue isEqual:itemValue]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark; cell.accessoryType = UITableViewCellAccessoryCheckmark;
} }
} }
else if (item[@"type"] == typeCheck) { else if (type == typeCheck) {
UISwitch *button = [[UISwitch alloc] init]; UISwitch *button = [[UISwitch alloc] init];
cell.accessoryView = button; cell.accessoryView = button;
if ([[NSUserDefaults standardUserDefaults] boolForKey:item[@"pref"]]) { if ([[NSUserDefaults standardUserDefaults] boolForKey:item[@"pref"]]) {
@ -747,6 +793,9 @@ static id ValueForItem(NSDictionary *item)
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
id block = ^(){ id block = ^(){
[[NSUserDefaults standardUserDefaults] setBool:button.on forKey:item[@"pref"]]; [[NSUserDefaults standardUserDefaults] setBool:button.on forKey:item[@"pref"]];
if (item[@"block"]) {
((void(^)(GBSettingsViewController *))item[@"block"])(self);
}
unsigned section = [indexPath indexAtPosition:0]; unsigned section = [indexPath indexAtPosition:0];
UITableViewHeaderFooterView *view = [weakSelf.tableView footerViewForSection:section]; UITableViewHeaderFooterView *view = [weakSelf.tableView footerViewForSection:section];
view.textLabel.text = [weakSelf tableView:weakSelf.tableView titleForFooterInSection:section]; view.textLabel.text = [weakSelf tableView:weakSelf.tableView titleForFooterInSection:section];
@ -761,7 +810,7 @@ static id ValueForItem(NSDictionary *item)
[button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventValueChanged]; [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventValueChanged];
cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.selectionStyle = UITableViewCellSelectionStyleNone;
} }
else if (item[@"type"] == typeDisabled) { else if (type == typeDisabled) {
cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
cell.textLabel.textColor = [UIColor separatorColor]; cell.textLabel.textColor = [UIColor separatorColor];
@ -770,24 +819,34 @@ static id ValueForItem(NSDictionary *item)
cell.textLabel.textColor = [UIColor colorWithWhite:0 alpha:0.75]; cell.textLabel.textColor = [UIColor colorWithWhite:0 alpha:0.75];
} }
} }
else if (item[@"type"] == typeSeparator) { else if (type == typeSeparator) {
cell.backgroundColor = [UIColor clearColor]; cell.backgroundColor = [UIColor clearColor];
cell.separatorInset = UIEdgeInsetsZero; cell.separatorInset = UIEdgeInsetsZero;
} }
else if (item[@"type"] == typeSlider || else if (type == typeSlider ||
item[@"type"] == typeLightTemp) { type == typeLightTemp ||
type == typeTurboSlider) {
CGRect rect = cell.contentView.bounds; CGRect rect = cell.contentView.bounds;
rect.size.width -= 24; rect.size.width -= 24;
rect.size.height -= 24; rect.size.height -= 24;
rect.origin.x += 12; rect.origin.x += 12;
rect.origin.y += 12; rect.origin.y += 12;
UISlider *slider = [item[@"type"] == typeLightTemp? [GBSlider alloc] : [UISlider alloc] initWithFrame:rect]; UISlider *slider = [type == typeLightTemp? [GBSlider alloc] : [UISlider alloc] initWithFrame:rect];
slider.continuous = true; slider.continuous = true;
slider.minimumValue = [item[@"min"] floatValue]; slider.minimumValue = [item[@"min"] floatValue];
slider.maximumValue = [item[@"max"] floatValue]; slider.maximumValue = [item[@"max"] floatValue];
slider.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; slider.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:slider]; [cell.contentView addSubview:slider];
slider.value = [[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]]; if (type == typeTurboSlider) {
slider.value = [[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]] ?: 2.0;
_turboSlider = slider;
if (![[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]]) {
slider.enabled = false;
}
}
else {
slider.value = [[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
@ -798,8 +857,19 @@ static id ValueForItem(NSDictionary *item)
} }
} }
__weak typeof(self) weakSelf = self;
id block = ^(){ id block = ^(){
[[NSUserDefaults standardUserDefaults] setDouble:slider.value forKey:item[@"pref"]]; [[NSUserDefaults standardUserDefaults] setDouble:slider.value forKey:item[@"pref"]];
if (type == typeTurboSlider) {
unsigned section = [indexPath indexAtPosition:0];
UITableViewHeaderFooterView *view = [weakSelf.tableView footerViewForSection:section];
view.textLabel.text = [weakSelf tableView:weakSelf.tableView titleForFooterInSection:section];
[UIView setAnimationsEnabled:false];
[weakSelf.tableView beginUpdates];
[view sizeToFit];
[weakSelf.tableView endUpdates];
[UIView setAnimationsEnabled:true];
}
}; };
objc_setAssociatedObject(cell, "RetainedBlock", block, OBJC_ASSOCIATION_RETAIN); objc_setAssociatedObject(cell, "RetainedBlock", block, OBJC_ASSOCIATION_RETAIN);
@ -813,13 +883,21 @@ static id ValueForItem(NSDictionary *item)
cell.separatorInset = UIEdgeInsetsZero; cell.separatorInset = UIEdgeInsetsZero;
} }
cell.imageView.image = item[@"image"]; cell.imageView.image = item[@"image"];
if (@available(iOS 19.0, *)) {
if (_iPadRoot) {
cell.textLabel.textColor = [UIColor colorWithDynamicProvider:^UIColor *(UITraitCollection *traitCollection) {
return cell.isSelected? [UIColor whiteColor] : [UIColor labelColor];
}];
}
}
return cell; return cell;
} }
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{ {
NSDictionary *item = [self itemForIndexPath:indexPath]; NSDictionary *item = [self itemForIndexPath:indexPath];
if (item[@"type"] == typeSubmenu || item[@"type"] == typeOptionSubmenu) { NSString *type = item[@"type"];
if (type == typeSubmenu || type == typeOptionSubmenu) {
UITableViewStyle style = UITableViewStyleGrouped; UITableViewStyle style = UITableViewStyleGrouped;
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
style = UITableViewStyleInsetGrouped; style = UITableViewStyleInsetGrouped;
@ -843,7 +921,7 @@ static id ValueForItem(NSDictionary *item)
} }
return indexPath; return indexPath;
} }
else if (item[@"type"] == typeRadio) { else if (type == typeRadio) {
if (item[@"setter"]) { if (item[@"setter"]) {
((void(^)(id))item[@"setter"])(item[@"value"]); ((void(^)(id))item[@"setter"])(item[@"value"]);
} }
@ -852,7 +930,7 @@ static id ValueForItem(NSDictionary *item)
} }
[self.tableView reloadData]; [self.tableView reloadData];
} }
else if (item[@"type"] == typeBlock) { else if (type == typeBlock) {
if (((bool(^)(GBSettingsViewController *))item[@"block"])(self)) { if (((bool(^)(GBSettingsViewController *))item[@"block"])(self)) {
return indexPath; return indexPath;
} }
@ -863,11 +941,13 @@ static id ValueForItem(NSDictionary *item)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{ {
NSDictionary *item = [self itemForIndexPath:indexPath]; NSDictionary *item = [self itemForIndexPath:indexPath];
if (item[@"type"] == typeSeparator) { NSString *type = item[@"type"];
if (type == typeSeparator) {
return 8; return 8;
} }
if (item[@"type"] == typeSlider || if (type == typeSlider ||
item[@"type"] == typeLightTemp) { type == typeLightTemp ||
type == typeTurboSlider) {
return 63; return 63;
} }
return [super tableView:tableView heightForRowAtIndexPath:indexPath]; return [super tableView:tableView heightForRowAtIndexPath:indexPath];

View File

@ -1,4 +1,19 @@
#import "GBSlider.h" #import "GBSlider.h"
#import <objc/runtime.h>
#if !__has_include(<UIKit/UISliderTrackConfiguration.h>)
/* Building with older SDKs */
API_AVAILABLE(ios(19.0))
@interface UISliderTrackConfiguration : NSObject
@property (nonatomic, readwrite) bool allowsTickValuesOnly;
@property (nonatomic, readwrite) float neutralValue;
+ (instancetype)configurationWithNumberOfTicks:(NSInteger)ticks;
@end
@interface UISlider (configuration)
@property(nonatomic, copy, nullable) UISliderTrackConfiguration *trackConfiguration API_AVAILABLE(ios(19.0));
@end
#endif
static inline void temperature_tint(double temperature, double *r, double *g, double *b) static inline void temperature_tint(double temperature, double *r, double *g, double *b)
{ {
@ -21,11 +36,15 @@ static inline void temperature_tint(double temperature, double *r, double *g, do
} }
@implementation GBSlider @implementation GBSlider
{
GBSliderStyle _style;
}
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
{ {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
[self addTarget:self action:@selector(valueChanged) forControlEvents:UIControlEventValueChanged]; [self addTarget:self action:@selector(valueChanged) forControlEvents:UIControlEventValueChanged];
self.style = GBSliderStyleTemperature;
return self; return self;
} }
@ -97,6 +116,10 @@ static inline void temperature_tint(double temperature, double *r, double *g, do
- (void)drawRect:(CGRect)rect - (void)drawRect:(CGRect)rect
{ {
bool solarium = false;
if (@available(iOS 19.0, *)) {
solarium = true;
}
CGSize size = self.bounds.size; CGSize size = self.bounds.size;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(2, round(size.height / 2 - 1.5), size.width - 4, 3) cornerRadius:4]; UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(2, round(size.height / 2 - 1.5), size.width - 4, 3) cornerRadius:4];
if (_style != GBSliderStyleHue) { if (_style != GBSliderStyleHue) {
@ -107,7 +130,7 @@ static inline void temperature_tint(double temperature, double *r, double *g, do
[path appendPath:[UIBezierPath bezierPathWithRoundedRect:CGRectMake(size.width - 3, 12, 3, size.height - 24) cornerRadius:4]]; [path appendPath:[UIBezierPath bezierPathWithRoundedRect:CGRectMake(size.width - 3, 12, 3, size.height - 24) cornerRadius:4]];
} }
if (_style == GBSliderStyleHue) { if (_style == GBSliderStyleHue) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(3, round(size.height / 2 - 1.5) + 1, size.width - 6, 1) cornerRadius:4]; UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(3, round(size.height / 2 - 1.5) + 1 - solarium, size.width - 6, solarium? 2 : 1) cornerRadius:8];
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context); CGContextSaveGState(context);
[path addClip]; [path addClip];
@ -124,11 +147,12 @@ static inline void temperature_tint(double temperature, double *r, double *g, do
}; };
CFArrayRef colorsArray = CFArrayCreate(NULL, (const void **)colors, 7, &kCFTypeArrayCallBacks); CFArrayRef colorsArray = CFArrayCreate(NULL, (const void **)colors, 7, &kCFTypeArrayCallBacks);
CGGradientRef gradient = CGGradientCreateWithColors(colorspace, colorsArray, NULL); CGGradientRef gradient = CGGradientCreateWithColors(colorspace, colorsArray, NULL);
unsigned spacing = solarium? 16 : 3;
CGContextDrawLinearGradient(context, CGContextDrawLinearGradient(context,
gradient, gradient,
(CGPoint){3, 0}, (CGPoint){spacing, 0},
(CGPoint){size.width - 3, 0}, (CGPoint){size.width - spacing, 0},
0); kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(gradient); CFRelease(gradient);
CFRelease(colorsArray); CFRelease(colorsArray);
CFRelease(colorspace); CFRelease(colorspace);
@ -139,11 +163,44 @@ static inline void temperature_tint(double temperature, double *r, double *g, do
green:120 / 255.0 green:120 / 255.0
blue:130 / 255.0 blue:130 / 255.0
alpha:70 / 255.0] set]; alpha:70 / 255.0] set];
[path fill]; if (!solarium) {
[path fill];
}
[super drawRect:rect]; [super drawRect:rect];
} }
- (void)setStyle:(GBSliderStyle)style
{
_style = style;
if (@available(iOS 19.0, *)) {
switch (_style) {
case GBSliderStyleTemperature:
case GBSliderStyleTicks: {
UISliderTrackConfiguration *conf = [objc_getClass("UISliderTrackConfiguration") configurationWithNumberOfTicks:3];
conf.allowsTickValuesOnly = false;
conf.neutralValue = 0.5;
self.trackConfiguration = conf;
self.maximumTrackTintColor = nil;
self.minimumTrackTintColor = nil;
break;
}
case GBSliderStyleHue: {
UISliderTrackConfiguration *conf = [objc_getClass("UISliderTrackConfiguration") configurationWithNumberOfTicks:0];
conf.allowsTickValuesOnly = false;
self.trackConfiguration = conf;
self.minimumTrackTintColor = [UIColor clearColor];
break;
}
}
}
}
- (GBSliderStyle)style
{
return _style;
}
- (void)setFrame:(CGRect)frame - (void)setFrame:(CGRect)frame
{ {
[super setFrame:frame]; [super setFrame:frame];

View File

@ -39,8 +39,8 @@
- (void)viewDidLoad - (void)viewDidLoad
{ {
[super viewDidLoad]; [super viewDidLoad];
self.view.bounds = CGRectMake(0, 0, 0x300, 0x300); UIView *root = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0x300, 0x300)];
UIView *root = self.view; [self.view addSubview:root];
for (unsigned i = 0; i < 9; i++) { for (unsigned i = 0; i < 9; i++) {
unsigned x = i % 3; unsigned x = i % 3;
unsigned y = i / 3; unsigned y = i / 3;
@ -114,6 +114,12 @@
[self presentViewController:controller animated:true completion:nil]; [self presentViewController:controller animated:true completion:nil];
} }
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.view.subviews.firstObject.frame = [self.view.safeAreaLayoutGuide layoutFrame];
}
- (NSString *)title - (NSString *)title
{ {
return @"Save States"; return @"Save States";

View File

@ -65,8 +65,8 @@
- (void)showPopup - (void)showPopup
{ {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Apply “%@” as the current theme?", _verticalLayout.theme.name] UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Apply Theme"
message:nil message:[NSString stringWithFormat:@"Apply “%@” as the current theme?", _verticalLayout.theme.name]
preferredStyle:[UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad? preferredStyle:[UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad?
UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet]; UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet];
if (false) { if (false) {

View File

@ -21,11 +21,85 @@
#import <sys/stat.h> #import <sys/stat.h>
#import <CoreMotion/CoreMotion.h> #import <CoreMotion/CoreMotion.h>
#import <dlfcn.h> #import <dlfcn.h>
#import <objc/runtime.h>
#if !__has_include(<UIKit/UISliderTrackConfiguration.h>)
/* Building with older SDKs */
typedef NS_ENUM(NSInteger, UIMenuSystemElementGroupPreference) {
UIMenuSystemElementGroupPreferenceAutomatic = 0,
UIMenuSystemElementGroupPreferenceRemoved,
UIMenuSystemElementGroupPreferenceIncluded,
};
API_AVAILABLE(ios(19.0))
@interface UIMainMenuSystemConfiguration : NSObject <NSCopying>
@property (nonatomic, assign) UIMenuSystemElementGroupPreference newScenePreference;
@property (nonatomic, assign) UIMenuSystemElementGroupPreference documentPreference;
@property (nonatomic, assign) UIMenuSystemElementGroupPreference printingPreference;
@property (nonatomic, assign) UIMenuSystemElementGroupPreference findingPreference;
@property (nonatomic, assign) UIMenuSystemElementGroupPreference toolbarPreference;
@property (nonatomic, assign) UIMenuSystemElementGroupPreference sidebarPreference;
@property (nonatomic, assign) UIMenuSystemElementGroupPreference inspectorPreference;
@property (nonatomic, assign) UIMenuSystemElementGroupPreference textFormattingPreference;
@end
API_AVAILABLE(ios(19.0))
@interface UIMainMenuSystem : UIMenuSystem
@property (class, nonatomic, readonly) UIMainMenuSystem *sharedSystem;
- (void)setBuildConfiguration:(UIMainMenuSystemConfiguration *)configuration buildHandler:(void(^)(NSObject<UIMenuBuilder> *builder))buildHandler;
@end
API_AVAILABLE(ios(19.0))
@interface NSObject(UIMenuBuilder)
- (void)insertElements:(NSArray<UIMenuElement *> *)childElements atStartOfMenuForIdentifier:(UIMenuIdentifier)parentIdentifier;
@end
#endif
static UIImage *CreateMenuImage(NSString *name)
{
static const unsigned size = 20;
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(size, size)];
CGRect destRect = {0,};
UIImage *source = [UIImage imageNamed:name];
CGSize sourceSize = source.size;
if (sourceSize.width > sourceSize.height) {
destRect.size.width = size;
destRect.size.height = sourceSize.height * size / sourceSize.width;
destRect.origin.y = (size - destRect.size.height) / 2;
}
else {
destRect.size.height = size;
destRect.size.width = sourceSize.width * size / sourceSize.height;
destRect.origin.x = (size - destRect.size.width) / 2;
}
UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *myContext) {
[source drawInRect:destRect];
}];
return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
API_AVAILABLE(ios(13.0))
@implementation UIKeyCommand (KeyCommandWithImage)
+ (instancetype)keyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)modifierFlags action:(SEL)action title:(NSString *)title image:(UIImage *)image
{
UIKeyCommand *ret = [self keyCommandWithInput:input modifierFlags:modifierFlags action:action];
ret.title = title;
ret.image = image;
return ret;
}
@end
@implementation GBViewController @implementation GBViewController
{ {
GB_gameboy_t _gb; GB_gameboy_t _gb;
GBView *_gbView; GBView *_gbView;
dispatch_queue_t _runQueue;
volatile bool _running; volatile bool _running;
volatile bool _stopping; volatile bool _stopping;
@ -35,6 +109,9 @@
bool _swappingROM; bool _swappingROM;
bool _skipAutoLoad; bool _skipAutoLoad;
bool _rapidA, _rapidB;
uint8_t _rapidACount, _rapidBCount;
UIInterfaceOrientation _orientation; UIInterfaceOrientation _orientation;
GBHorizontalLayout *_horizontalLayoutLeft; GBHorizontalLayout *_horizontalLayoutLeft;
GBHorizontalLayout *_horizontalLayoutRight; GBHorizontalLayout *_horizontalLayoutRight;
@ -190,6 +267,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[self addDefaultObserver:^(id newValue) { [self addDefaultObserver:^(id newValue) {
GB_set_rewind_length(gb, [newValue unsignedIntValue]); GB_set_rewind_length(gb, [newValue unsignedIntValue]);
} forKey:@"GBRewindLength"]; } forKey:@"GBRewindLength"];
[self addDefaultObserver:^(id newValue) {
GB_set_turbo_cap(gb, [newValue doubleValue]);
} forKey:@"GBTurboCap"];
[self addDefaultObserver:^(id newValue) { [self addDefaultObserver:^(id newValue) {
[[AVAudioSession sharedInstance] setCategory:[newValue isEqual:@"on"]? AVAudioSessionCategoryPlayback : AVAudioSessionCategorySoloAmbient [[AVAudioSession sharedInstance] setCategory:[newValue isEqual:@"on"]? AVAudioSessionCategoryPlayback : AVAudioSessionCategorySoloAmbient
mode:AVAudioSessionModeDefault mode:AVAudioSessionModeDefault
@ -236,6 +316,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
_window.rootViewController = self; _window.rootViewController = self;
[_window makeKeyAndVisible]; [_window makeKeyAndVisible];
_runQueue = dispatch_queue_create("SameBoy Emulation Queue", NULL);
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles" #pragma clang diagnostic ignored "-Warc-retain-cycles"
[self addDefaultObserver:^(id newValue) { [self addDefaultObserver:^(id newValue) {
@ -431,9 +513,93 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[self updateMirrorWindow]; [self updateMirrorWindow];
if (@available(iOS 26.0, *)) {
UIMainMenuSystemConfiguration *conf = [[objc_getClass("UIMainMenuSystemConfiguration") alloc] init];
conf.newScenePreference = UIMenuSystemElementGroupPreferenceRemoved;
conf.documentPreference = UIMenuSystemElementGroupPreferenceRemoved;
conf.printingPreference = UIMenuSystemElementGroupPreferenceRemoved;
conf.findingPreference = UIMenuSystemElementGroupPreferenceRemoved;
conf.toolbarPreference = UIMenuSystemElementGroupPreferenceRemoved;
conf.sidebarPreference = UIMenuSystemElementGroupPreferenceRemoved;
conf.inspectorPreference = UIMenuSystemElementGroupPreferenceRemoved;
conf.textFormattingPreference = UIMenuSystemElementGroupPreferenceRemoved;
UIMainMenuSystem *system = (id)[objc_getClass("UIMainMenuSystem") sharedSystem];
[system setBuildConfiguration:conf
buildHandler:^(id<UIMenuBuilder> builder) {
[builder removeMenuForIdentifier:UIMenuView]; // This menu's always empty
[builder removeMenuForIdentifier:UIMenuOpenRecent]; // This will list files to re-import, bad
[(id)builder insertElements:@[[UICommand commandWithTitle:@"About SameBoy"
image:nil
action:@selector(showAbout)
propertyList:nil]]
atStartOfMenuForIdentifier:UIMenuApplication];
UIMenu *emulationMenu = [UIMenu menuWithTitle:@"Emulation" children:@[
[UIKeyCommand keyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:@selector(reset) title:@"Reset" image:[UIImage systemImageNamed:@"arrow.2.circlepath"]],
[UICommand commandWithTitle:@"Change Model…" image:CreateMenuImage(@"ModelTemplate") action:@selector(changeModel) propertyList:nil],
[UICommand commandWithTitle:@"Save States…" image:[UIImage systemImageNamed:@"square.stack"] action:@selector(openStates) propertyList:nil],
[UIMenu menuWithTitle:@"Cheats" image:CreateMenuImage(@"CheatsTemplate") identifier:nil options:0 children:@[
[UIKeyCommand keyCommandWithInput:@"c" modifierFlags:UIKeyModifierCommand | UIKeyModifierShift action:@selector(toggleCheats) title:@"Enable Cheats" image:nil],
[UICommand commandWithTitle:@"Show Cheats…" image:nil action:@selector(openCheats) propertyList:nil],
]],
[UIMenu menuWithTitle:@"Connect" image:CreateMenuImage(@"LinkCableTemplate") identifier:nil options:0 children:@[
[UICommand commandWithTitle:@"None" image:nil action:@selector(disconnectCable) propertyList:nil],
[UICommand commandWithTitle:@"Printer" image:[UIImage systemImageNamed:@"printer"] action:@selector(connectPrinter) propertyList:nil],
]],
]];
[builder insertSiblingMenu:emulationMenu beforeMenuForIdentifier:UIMenuWindow];
}];
}
return true; return true;
} }
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == @selector(reset)) {
if (self.presentedViewController || ![GBROMManager sharedManager].currentROM) return false;
}
if (action == @selector(openStates) || action == @selector(changeModel) || action == @selector(openCheats) ||
action == @selector(toggleCheats) || action == @selector(disconnectCable) || action == @selector(connectPrinter)) {
if (![GBROMManager sharedManager].currentROM) return false;
}
return [super canPerformAction:action withSender:sender];
}
- (void)validateCommand:(UICommand *)command
{
if (command.action == @selector(toggleCheats)) {
command.state = GB_is_inited(&_gb) && GB_cheats_enabled(&_gb);
}
if (command.action == @selector(connectPrinter)) {
command.state = _printerConnected;
}
if (command.action == @selector(disconnectCable)) {
command.state = !_printerConnected;
}
[super validateCommand:command];
}
- (void)toggleCheats
{
GB_set_cheats_enabled(&_gb, !GB_cheats_enabled(&_gb));
}
- (void)orderFrontPreferencesPanel:(id)sender
{
[self openSettings];
}
- (void)open:(id)sender
{
[self openLibrary];
}
- (void)updateMirrorWindow - (void)updateMirrorWindow
{ {
if ([UIScreen screens].count == 1) { if ([UIScreen screens].count == 1) {
@ -526,6 +692,14 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
case GBStart: case GBStart:
GB_set_key_state(&_gb, (GB_key_t)gbButton, button.value > 0.25); GB_set_key_state(&_gb, (GB_key_t)gbButton, button.value > 0.25);
break; break;
case GBRapidA:
_rapidA = button.value > 0.25;
_rapidACount = 0;
break;
case GBRapidB:
_rapidB = button.value > 0.25;
_rapidBCount = 0;
break;
case GBTurbo: case GBTurbo:
if (button.value > analogThreshold) { if (button.value > analogThreshold) {
[self setRunMode:GBRunModeTurbo ignoreDynamicSpeed:!button.isAnalog]; [self setRunMode:GBRunModeTurbo ignoreDynamicSpeed:!button.isAnalog];
@ -552,7 +726,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[_backgroundView fadeOverlayOut]; [_backgroundView fadeOverlayOut];
} }
else { else {
if (self.runMode == GBRunModeRewind && _runModeFromController) { if ((self.runMode == GBRunModeRewind || self.runMode == GBRunModePaused) && _runModeFromController) {
[self setRunMode:GBRunModeNormal]; [self setRunMode:GBRunModeNormal];
_runModeFromController = false; _runModeFromController = false;
} }
@ -673,15 +847,31 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)saveStateToFile:(NSString *)file - (void)saveStateToFile:(NSString *)file
{ {
NSString *tempPath = [file stringByAppendingPathExtension:@"tmp"]; NSString *tempPath = [file stringByAppendingPathExtension:@"tmp"];
if (!GB_save_state(&_gb, tempPath.UTF8String)) { int error = GB_save_state(&_gb, tempPath.UTF8String);
if (!error) {
rename(tempPath.UTF8String, file.UTF8String); rename(tempPath.UTF8String, file.UTF8String);
NSData *data = [NSData dataWithBytes:_gbView.previousBuffer
length:GB_get_screen_width(&_gb) *
GB_get_screen_height(&_gb) *
sizeof(*_gbView.previousBuffer)];
UIImage *screenshot = [self imageFromData:data width:GB_get_screen_width(&_gb) height:GB_get_screen_height(&_gb)];
[UIImagePNGRepresentation(screenshot) writeToFile:[file stringByAppendingPathExtension:@"png"] atomically:false];
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Could not Save State"
message:[NSString stringWithFormat:@"An error occured while attempting to save: %s", strerror(error)]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Close"
style:UIAlertActionStyleCancel
handler:nil]];
UIViewController *top = self;
while (top.presentedViewController) {
top = top.presentedViewController;
}
[top presentViewController:alert animated:true completion:nil];
});
} }
NSData *data = [NSData dataWithBytes:_gbView.previousBuffer
length:GB_get_screen_width(&_gb) *
GB_get_screen_height(&_gb) *
sizeof(*_gbView.previousBuffer)];
UIImage *screenshot = [self imageFromData:data width:GB_get_screen_width(&_gb) height:GB_get_screen_height(&_gb)];
[UIImagePNGRepresentation(screenshot) writeToFile:[file stringByAppendingPathExtension:@"png"] atomically:false];
} }
- (bool)loadStateFromFile:(NSString *)file - (bool)loadStateFromFile:(NSString *)file
@ -704,49 +894,68 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
if (romManager.romFile) { if (romManager.romFile) {
if (!_skipAutoLoad) { if (!_skipAutoLoad) {
// Todo: display errors and warnings // Todo: display errors and warnings
if ([romManager.romFile.pathExtension.lowercaseString isEqualToString:@"isx"]) { bool needsStateLoad = false;
_romLoaded = GB_load_isx(&_gb, romManager.romFile.fileSystemRepresentation) == 0; if (![_lastSavedROM isEqual:[GBROMManager sharedManager].currentROM]) {
if ([romManager.romFile.pathExtension.lowercaseString isEqualToString:@"isx"]) {
_romLoaded = GB_load_isx(&_gb, romManager.romFile.fileSystemRepresentation) == 0;
}
else {
_romLoaded = GB_load_rom(&_gb, romManager.romFile.fileSystemRepresentation) == 0;
}
needsStateLoad = true;
if (@available(iOS 16.0, *)) {
dispatch_async(dispatch_get_main_queue(), ^{
[super setNeedsUpdateOfSupportedInterfaceOrientations];
});
}
} }
else { else if (access(romManager.romFile.fileSystemRepresentation, R_OK)) {
_romLoaded = GB_load_rom(&_gb, romManager.romFile.fileSystemRepresentation) == 0; _romLoaded = false;
} }
if (_romLoaded) { if (!_romLoaded) {
dispatch_async(dispatch_get_main_queue(), ^{
romManager.currentROM = nil;
});
}
if (!needsStateLoad) {
NSDate *date = nil;
[[NSURL fileURLWithPath:[GBROMManager sharedManager].autosaveStateFile] getResourceValue:&date
forKey:NSURLContentModificationDateKey
error:nil];
if (![_saveDate isEqual:date]) {
needsStateLoad = true;
}
}
if (_romLoaded && needsStateLoad) {
GB_reset(&_gb); GB_reset(&_gb);
GB_load_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation); GB_load_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation);
GB_remove_all_cheats(&_gb); GB_remove_all_cheats(&_gb);
GB_load_cheats(&_gb, [GBROMManager sharedManager].cheatsFile.UTF8String, false); GB_load_cheats(&_gb, [GBROMManager sharedManager].cheatsFile.UTF8String, false);
if (![self loadStateFromFile:[GBROMManager sharedManager].autosaveStateFile]) { if (![self loadStateFromFile:[GBROMManager sharedManager].autosaveStateFile]) {
// Newly played ROM, pick the best model if ([_lastSavedROM isEqual:[GBROMManager sharedManager].currentROM]) {
uint8_t *rom = GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, NULL, NULL); /* Something weird just happened: we didn't change a ROM, but we failed to load the
latest save state. Save over the existing file, it's probably corrupt in some
if ((rom[0x143] & 0x80)) { way. */
if (!GB_is_cgb(&_gb)) { [self saveStateToFile:[GBROMManager sharedManager].autosaveStateFile];
GB_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]); }
else {
// Newly played ROM, pick the best model
uint8_t *rom = GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, NULL, NULL);
if ((rom[0x143] & 0x80)) {
if (!GB_is_cgb(&_gb)) {
GB_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]);
}
}
else if ((rom[0x146] == 3) && !GB_is_sgb(&_gb)) {
GB_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]);
} }
} }
else if ((rom[0x146] == 3) && !GB_is_sgb(&_gb)) { GB_rewind_reset(&_gb);
GB_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]);
}
} }
} }
NSDate *date = nil;
@try {
[[NSURL fileURLWithPath:[GBROMManager sharedManager].autosaveStateFile] getResourceValue:&date
forKey:NSURLContentModificationDateKey
error:nil];
}
@catch (NSException *exception) {
/* fileURLWithPath: throws an exception on some crash logs. I don't know why or how to reproduce it,
but let's at least not crash. */
GB_rewind_reset(&_gb);
}
// Reset the rewind buffer only if we switched ROMs or had the save state change externally
if (![_lastSavedROM isEqual:[GBROMManager sharedManager].currentROM] ||
![_saveDate isEqual:date]) {
GB_rewind_reset(&_gb);
}
} }
} }
else { else {
@ -774,23 +983,48 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)reset - (void)reset
{ {
[self stop]; UIAlertControllerStyle style = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad?
_skipAutoLoad = true; UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet;
GB_reset(&_gb); UIAlertController *menu = [UIAlertController alertControllerWithTitle:@"Reset Emulation?"
[self start]; message:@"Unsaved progress will be lost."
preferredStyle:style];
[menu addAction:[UIAlertAction actionWithTitle:@"Reset"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self stop];
_skipAutoLoad = true;
GB_reset(&_gb);
[self start];
}]];
[menu addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:menu animated:true completion:nil];
} }
- (void)openLibrary - (void)openLibrary
{ {
[self presentViewController:[[GBLibraryViewController alloc] init] static __weak UIViewController *presentedController;
if (presentedController && self.presentedViewController == presentedController) return;
if (![self dismissViewControllerIfSafe]) return;
UIViewController *controller = [[GBLibraryViewController alloc] init];
presentedController = controller;
[self presentViewController:controller
animated:true animated:true
completion:nil]; completion:nil];
} }
- (void)changeModel - (void)changeModel
{ {
static __weak UIViewController *presentedController;
if (presentedController && self.presentedViewController == presentedController) return;
if (![self dismissViewControllerIfSafe]) return;
GBOptionViewController *controller = [[GBOptionViewController alloc] initWithHeader:@"Select a model to emulate"]; GBOptionViewController *controller = [[GBOptionViewController alloc] initWithHeader:@"Select a model to emulate"];
controller.footer = @"Changing the emulated model will reset the emulator"; controller.footer = @"Changing the emulated model will reset the emulator";
presentedController = controller;
GB_model_t currentModel = GB_get_model(&_gb); GB_model_t currentModel = GB_get_model(&_gb);
struct { struct {
@ -845,7 +1079,12 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)openStates - (void)openStates
{ {
static __weak UIViewController *presentedController;
if (presentedController && self.presentedViewController == presentedController) return;
if (![self dismissViewControllerIfSafe]) return;
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBStatesViewController alloc] init]]; UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBStatesViewController alloc] init]];
presentedController = controller;
UIVisualEffect *effect = [UIBlurEffect effectWithStyle:(UIBlurEffectStyle)UIBlurEffectStyleProminent]; UIVisualEffect *effect = [UIBlurEffect effectWithStyle:(UIBlurEffectStyle)UIBlurEffectStyleProminent];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect]; UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
effectView.frame = controller.view.bounds; effectView.frame = controller.view.bounds;
@ -863,23 +1102,41 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)openSettings - (void)openSettings
{ {
static __weak UIViewController *presentedController;
if (presentedController && self.presentedViewController == presentedController) return;
if (![self dismissViewControllerIfSafe]) return;
UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close" UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close"
style:UIBarButtonItemStylePlain style:UIBarButtonItemStylePlain
target:self target:self
action:@selector(dismissViewController)]; action:@selector(dismissViewController)];
[self presentViewController:[GBSettingsViewController settingsViewControllerWithLeftButton:close] UIViewController *controller = [GBSettingsViewController settingsViewControllerWithLeftButton:close];
presentedController = controller;
[self presentViewController:controller
animated:true animated:true
completion:nil]; completion:nil];
} }
- (void)showAbout - (void)showAbout
{ {
[self presentViewController:[[GBAboutController alloc] init] animated:true completion:nil]; static __weak UIViewController *presentedController;
if (presentedController && self.presentedViewController == presentedController) return;
if (![self dismissViewControllerIfSafe]) return;
UIViewController *controller = [[GBAboutController alloc] init];
presentedController = controller;
[self presentViewController:controller animated:true completion:nil];
} }
- (void)openCheats - (void)openCheats
{ {
static __weak UIViewController *presentedController;
if (presentedController && self.presentedViewController == presentedController) return;
if (![self dismissViewControllerIfSafe]) return;
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBCheatsController alloc] initWithGameBoy:&_gb]]; UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBCheatsController alloc] initWithGameBoy:&_gb]];
presentedController = controller;
UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close" UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close"
style:UIBarButtonItemStylePlain style:UIBarButtonItemStylePlain
target:self target:self
@ -905,9 +1162,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)setNeedsUpdateOfSupportedInterfaceOrientations - (void)setNeedsUpdateOfSupportedInterfaceOrientations
{ {
/* Hack. Some view controllers dismiss without calling the method above. */ /* Hack. Some view controllers dismiss without calling the method above. */
[super setNeedsUpdateOfSupportedInterfaceOrientations];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self start]; [self start];
[super setNeedsUpdateOfSupportedInterfaceOrientations];
}); });
} }
@ -916,6 +1173,22 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[self dismissViewControllerAnimated:true completion:nil]; [self dismissViewControllerAnimated:true completion:nil];
} }
- (bool)dismissViewControllerIfSafe
{
if (!self.presentedViewController) return true;
if (![self.presentedViewController isKindOfClass:[UIAlertController class]]) {
[self dismissViewController];
return true;
}
if ([self.presentedViewController isKindOfClass:[GBMenuViewController class]]) {
[self dismissViewController];
return true;
}
return false;
}
- (void)applicationWillResignActive:(UIApplication *)application - (void)applicationWillResignActive:(UIApplication *)application
{ {
[self stop]; [self stop];
@ -923,6 +1196,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
{ {
if (_orientation != UIInterfaceOrientationUnknown && !((1 << orientation) & self.supportedInterfaceOrientations)) return;
GBLayout *layout = nil; GBLayout *layout = nil;
_orientation = orientation; _orientation = orientation;
switch (orientation) { switch (orientation) {
@ -977,10 +1251,14 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
32, 32,
32); 32);
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
} }
- (UIInterfaceOrientationMask)supportedInterfaceOrientations - (UIInterfaceOrientationMask)supportedInterfaceOrientations
{ {
if (!self.shouldAutorotate && _orientation != UIInterfaceOrientationUnknown) {
return 1 << _orientation;
}
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskAll; return UIInterfaceOrientationMaskAll;
} }
@ -1132,14 +1410,22 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
GB_rewind_pop(&_gb); GB_rewind_pop(&_gb);
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"]) { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"]) {
if (!GB_rewind_pop(&_gb)) { if (!GB_rewind_pop(&_gb)) {
self.runMode = GBRunModePaused; dispatch_sync(dispatch_get_main_queue(), ^{
if (_runMode == GBRunModeRewind) {
self.runMode = GBRunModePaused;
}
});
_rewindOver = true; _rewindOver = true;
} }
} }
else { else {
for (unsigned i = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindSpeed"]; i--;) { for (unsigned i = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindSpeed"]; i--;) {
if (!GB_rewind_pop(&_gb)) { if (!GB_rewind_pop(&_gb)) {
self.runMode = GBRunModePaused; dispatch_sync(dispatch_get_main_queue(), ^{
if (_runMode == GBRunModeRewind) {
self.runMode = GBRunModePaused;
}
});
_rewindOver = true; _rewindOver = true;
} }
} }
@ -1168,7 +1454,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
- (UIImage *)imageFromData:(NSData *)data width:(unsigned)width height:(unsigned)height - (UIImage *)imageFromData:(NSData *)data width:(unsigned)width height:(unsigned)height
{ {
/* Convert the screenshot to a CGImageRef */ /* Convert the screenshot to a CGImageRef */
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, data.bytes, data.length, NULL); CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast; CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
@ -1257,7 +1543,9 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
if (_running) return; if (_running) return;
if (self.presentedViewController) return; if (self.presentedViewController) return;
_running = true; _running = true;
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start]; dispatch_async(_runQueue, ^{
[self run];
});
} }
- (void)stop - (void)stop
@ -1273,6 +1561,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
[_audioLock signal]; [_audioLock signal];
[_audioLock unlock]; [_audioLock unlock];
} }
dispatch_sync(_runQueue, ^{});
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
self.runMode = GBRunModeNormal; self.runMode = GBRunModeNormal;
[_backgroundView fadeOverlayOut]; [_backgroundView fadeOverlayOut];
@ -1331,6 +1620,14 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
[_gbView flip]; [_gbView flip];
GB_set_pixels_output(&_gb, _gbView.pixels); GB_set_pixels_output(&_gb, _gbView.pixels);
} }
if (_rapidA) {
_rapidACount++;
GB_set_key_state(&_gb, GB_KEY_A, !(_rapidACount & 2));
}
if (_rapidB) {
_rapidBCount++;
GB_set_key_state(&_gb, GB_KEY_B, !(_rapidBCount & 2));
}
_rewind = _runMode == GBRunModeRewind; _rewind = _runMode == GBRunModeRewind;
} }
@ -1452,7 +1749,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
[url startAccessingSecurityScopedResource]; [url startAccessingSecurityScopedResource];
[GBROMManager sharedManager].currentROM = [GBROMManager sharedManager].currentROM =
[[GBROMManager sharedManager] importROM:url.path [[GBROMManager sharedManager] importROM:url.path
keepOriginal:![url.path hasPrefix:tempDir] && !inPlace]; keepOriginal:![url.path hasPrefix:tempDir] && inPlace];
[url stopAccessingSecurityScopedResource]; [url stopAccessingSecurityScopedResource];
} }
return true; return true;
@ -1465,7 +1762,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
} }
[url startAccessingSecurityScopedResource]; [url startAccessingSecurityScopedResource];
[[GBROMManager sharedManager] importROM:url.path [[GBROMManager sharedManager] importROM:url.path
keepOriginal:![url.path hasPrefix:tempDir] && !inPlace]; keepOriginal:![url.path hasPrefix:tempDir] && inPlace];
[url stopAccessingSecurityScopedResource]; [url stopAccessingSecurityScopedResource];
} }
[self openLibrary]; [self openLibrary];
@ -1576,9 +1873,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
if (_runMode == GBRunModeNormal || _runMode == GBRunModeUnderclock || !([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"] && !ignoreDynamicSpeed)) { if (_runMode == GBRunModeNormal || _runMode == GBRunModeUnderclock || !([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"] && !ignoreDynamicSpeed)) {
if (_runMode == GBRunModeTurbo) { if (_runMode == GBRunModeTurbo) {
double multiplier = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboSpeed"]; GB_set_turbo_mode(&_gb, true, false);
GB_set_turbo_mode(&_gb, multiplier == 1, false);
GB_set_clock_multiplier(&_gb, multiplier);
} }
else if (_runMode == GBRunModeUnderclock) { else if (_runMode == GBRunModeUnderclock) {
GB_set_clock_multiplier(&_gb, 0.5); GB_set_clock_multiplier(&_gb, 0.5);
@ -1791,29 +2086,41 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
}); });
} }
- (void)disconnectCable
{
if (!_printerConnected) return;
_printerConnected = false;
_currentPrinterImageData = nil;
[UIView animateWithDuration:0.25 animations:^{
_printerButton.alpha = 0;
}];
[_printerSpinner stopAnimating];
GB_disconnect_serial(&_gb);
}
- (void)connectPrinter
{
if (_printerConnected) return;
_printerConnected = true;
GB_connect_printer(&_gb, printImage, printDone);
}
- (void)openConnectMenu - (void)openConnectMenu
{ {
UIAlertControllerStyle style = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad? UIAlertControllerStyle style = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad?
UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet; UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet;
GBCheckableAlertController *menu = [GBCheckableAlertController alertControllerWithTitle:@"Connect which accessory?" GBCheckableAlertController *menu = [GBCheckableAlertController alertControllerWithTitle:@"Connect Accessory"
message:nil message:@"Choose an accessory to connect."
preferredStyle:style]; preferredStyle:style];
[menu addAction:[UIAlertAction actionWithTitle:@"None" [menu addAction:[UIAlertAction actionWithTitle:@"None"
style:UIAlertActionStyleDefault style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) { handler:^(UIAlertAction *action) {
_printerConnected = false; [self disconnectCable];
_currentPrinterImageData = nil;
[UIView animateWithDuration:0.25 animations:^{
_printerButton.alpha = 0;
}];
[_printerSpinner stopAnimating];
GB_disconnect_serial(&_gb);
}]]; }]];
[menu addAction:[UIAlertAction actionWithTitle:@"Game Boy Printer" [menu addAction:[UIAlertAction actionWithTitle:@"Game Boy Printer"
style:UIAlertActionStyleDefault style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) { handler:^(UIAlertAction *action) {
_printerConnected = true; [self connectPrinter];
GB_connect_printer(&_gb, printImage, printDone);
}]]; }]];
menu.selectedAction = menu.actions[_printerConnected]; menu.selectedAction = menu.actions[_printerConnected];
[menu addAction:[UIAlertAction actionWithTitle:@"Cancel" [menu addAction:[UIAlertAction actionWithTitle:@"Cancel"
@ -1874,3 +2181,31 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
} }
@end @end
/* +[UIColor labelColor] is broken in some contexts in iOS 26 and despite being such a critical method
Apple isn't going to fix this in time. */
API_AVAILABLE(ios(19.0))
@implementation UIColor(SolariumBugs)
+ (UIColor *)_labelColor
{
return [UIColor colorWithDynamicProvider:^UIColor *(UITraitCollection *traitCollection) {
switch (traitCollection.userInterfaceStyle) {
case UIUserInterfaceStyleUnspecified:
case UIUserInterfaceStyleLight:
return [UIColor blackColor];
case UIUserInterfaceStyleDark:
return [UIColor whiteColor];
}
}];
}
+ (void)load
{
if (@available(iOS 19.0, *)) {
method_setImplementation(class_getClassMethod(self, @selector(labelColor)),
[self methodForSelector:@selector(_labelColor)]);
}
}
@end

Some files were not shown because too many files have changed in this diff Show More