Compare commits
92 Commits
v1.0.1-lib
...
master
Author | SHA1 | Date |
---|---|---|
![]() |
8a234a25a3 | |
![]() |
5b88346537 | |
![]() |
20e5e18122 | |
![]() |
b948a1f3fd | |
![]() |
8215c03d62 | |
![]() |
8b2af8adf1 | |
![]() |
00f772b29d | |
![]() |
4107198548 | |
![]() |
e997ce0ce7 | |
![]() |
58c946f249 | |
![]() |
0b57886491 | |
![]() |
2db60c2b3f | |
![]() |
f54bfae01f | |
![]() |
c40221abb6 | |
![]() |
eb38034b76 | |
![]() |
c6a968ed74 | |
![]() |
e69f6b8579 | |
![]() |
cfbc7b481a | |
![]() |
1dfcdffa71 | |
![]() |
5b983bc7ad | |
![]() |
634b90e4fc | |
![]() |
b31cca77be | |
![]() |
5b17b41e07 | |
![]() |
239e0462c3 | |
![]() |
8505f00cdf | |
![]() |
08d58aa992 | |
![]() |
8cce6f7b13 | |
![]() |
ec8baa6329 | |
![]() |
9bd84978cf | |
![]() |
aa0fe30d5c | |
![]() |
6d6aafe887 | |
![]() |
003e8914b1 | |
![]() |
d7e1672ae7 | |
![]() |
1157ff0f36 | |
![]() |
bdd4522ca9 | |
![]() |
4abb5f3539 | |
![]() |
aff7f1706c | |
![]() |
33d237706e | |
![]() |
a39efd31cf | |
![]() |
69bb4c3e95 | |
![]() |
ae2d68aaf3 | |
![]() |
e381ebd1ea | |
![]() |
34c29b052e | |
![]() |
4ebb108ae1 | |
![]() |
d1da91da7c | |
![]() |
490d63b26f | |
![]() |
be63d7eaa3 | |
![]() |
15588a065f | |
![]() |
d52a50353d | |
![]() |
5d70f93920 | |
![]() |
f3cbc1990e | |
![]() |
583c234953 | |
![]() |
3f744254fd | |
![]() |
7abedaed4c | |
![]() |
42ffbd18d0 | |
![]() |
8508eb7b7c | |
![]() |
00000971d7 | |
![]() |
58bd40b833 | |
![]() |
cbaf5c4c4a | |
![]() |
67d338164b | |
![]() |
7bf8145a91 | |
![]() |
e043279500 | |
![]() |
9d6f378d21 | |
![]() |
bfb1092cbb | |
![]() |
282140822e | |
![]() |
19a1e3ec1a | |
![]() |
1ad8bad18c | |
![]() |
9577cbce85 | |
![]() |
6dd2f609f2 | |
![]() |
976fe7a337 | |
![]() |
1400bd40e8 | |
![]() |
6a97192e8c | |
![]() |
42732b20eb | |
![]() |
f0a672c39e | |
![]() |
d211120312 | |
![]() |
6ab1be654b | |
![]() |
f706988171 | |
![]() |
bed9f8220c | |
![]() |
750112f6dd | |
![]() |
63a02d90bc | |
![]() |
795fba1320 | |
![]() |
1923c324d9 | |
![]() |
6a24b9206f | |
![]() |
30a8c4bf42 | |
![]() |
152e242485 | |
![]() |
c4e6161959 | |
![]() |
1951df3476 | |
![]() |
1cf84a5436 | |
![]() |
81c29fa371 | |
![]() |
8b27952680 | |
![]() |
3c58deb46f | |
![]() |
d267d83cec |
|
@ -11,7 +11,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, ubuntu-20.04]
|
||||
os: [macos-latest, ubuntu-latest, ubuntu-22.04]
|
||||
cc: [gcc, clang]
|
||||
include:
|
||||
- os: macos-latest
|
||||
|
|
8
BESS.md
|
@ -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:
|
||||
|
||||
| Offset | Content |
|
||||
|--------|---------------------------------------|
|
||||
| 0 | A four-letter ASCII identifier |
|
||||
| 4 | Length of the block, excluding header |
|
||||
|--------|-----------------------------------------------------------|
|
||||
| 0 | A four-letter ASCII identifier |
|
||||
| 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).
|
||||
|
||||
|
@ -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)
|
||||
* 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 END block with non-zero length
|
||||
* An END block with non-zero length
|
||||
|
|
BIN
Cocoa/CPU@2x.png
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 283 B After Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 202 B |
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 420 B |
|
@ -1,8 +1,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>
|
||||
<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"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -103,7 +103,7 @@
|
|||
</tableHeaderView>
|
||||
</scrollView>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -113,30 +113,30 @@
|
|||
<action selector="search:" target="-2" id="7pG-JY-vEF"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fTv-nr-5FT">
|
||||
<rect key="frame" x="6" y="96" width="124" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fTv-nr-5FT">
|
||||
<rect key="frame" x="6" y="95" width="124" height="17"/>
|
||||
<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"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vMd-zb-8jT">
|
||||
<rect key="frame" x="6" y="67" width="124" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vMd-zb-8jT">
|
||||
<rect key="frame" x="6" y="64" width="124" height="17"/>
|
||||
<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"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="e5r-qR-pg7">
|
||||
<items>
|
||||
<menuItem title="8-Bit" state="on" id="al4-Jb-OJB"/>
|
||||
|
@ -151,7 +151,7 @@
|
|||
<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">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<menu key="menu" id="Kg9-Rd-1GQ">
|
||||
<items>
|
||||
<menuItem title="Any" id="cEg-eI-4hb"/>
|
||||
|
@ -173,7 +173,7 @@
|
|||
<action selector="conditionChanged:" target="-2" id="KF9-vz-yNC"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<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">
|
||||
|
@ -189,8 +189,8 @@
|
|||
<outlet property="delegate" destination="-2" id="1bO-hp-igc"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XN7-BO-THS">
|
||||
<rect key="frame" x="136" y="64" width="324" height="21"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XN7-BO-THS">
|
||||
<rect key="frame" x="136" y="62" width="324" height="21"/>
|
||||
<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">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -205,7 +205,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -215,7 +215,7 @@
|
|||
<action selector="reset:" target="-2" id="KCy-Ob-tlg"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Status" id="CM3-4U-qao">
|
||||
|
@ -225,7 +225,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -235,10 +235,10 @@
|
|||
<action selector="addCheat:" target="-2" id="7ax-kM-TeV"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="veO-Qn-0Sz">
|
||||
<rect key="frame" x="6" y="126" width="124" height="16"/>
|
||||
<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="17"/>
|
||||
<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"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
|
|
Before Width: | Height: | Size: 231 B After Width: | Height: | Size: 132 B |
Before Width: | Height: | Size: 435 B After Width: | Height: | Size: 461 B |
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 120 B |
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 448 B |
|
@ -3,6 +3,7 @@
|
|||
#import "GBImageView.h"
|
||||
#import "GBSplitView.h"
|
||||
#import "GBVisualizerView.h"
|
||||
#import "GBCPUView.h"
|
||||
#import "GBOSDView.h"
|
||||
#import "GBDebuggerButton.h"
|
||||
|
||||
|
@ -84,6 +85,8 @@ enum model {
|
|||
@property IBOutlet NSScrollView *debuggerScrollView;
|
||||
@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;
|
||||
- (void) performAtomicBlock: (void (^)())block;
|
||||
|
|
293
Cocoa/Document.m
|
@ -288,6 +288,7 @@ static void debuggerReloadCallback(GB_gameboy_t *gb)
|
|||
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_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_input_callback(&_gb, (GB_input_callback_t) consoleInput);
|
||||
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) {
|
||||
[weakSelf updateFonts];
|
||||
}];
|
||||
|
||||
[self observeStandardDefaultsKey:@"GBTurboCap" withBlock:^(NSNumber *value) {
|
||||
if (!_master) {
|
||||
GB_set_turbo_cap(gb, value.doubleValue);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateMinSize
|
||||
|
@ -359,11 +366,29 @@ static void debuggerReloadCallback(GB_gameboy_t *gb)
|
|||
|
||||
- (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) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_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) {
|
||||
[self.view flip];
|
||||
if (_borderModeChanged) {
|
||||
|
@ -730,11 +755,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
if (old_width != GB_get_screen_width(&_gb)) {
|
||||
[self.view screenSizeChanged];
|
||||
}
|
||||
|
||||
[self updateMinSize];
|
||||
|
||||
|
||||
[self start];
|
||||
if (_gbsTracks) {
|
||||
[self changeGBSTrack:sender];
|
||||
}
|
||||
|
||||
if (_hexController) {
|
||||
/* Verify bank sanity, especially when switching models. */
|
||||
|
@ -859,7 +885,7 @@ again:;
|
|||
{
|
||||
[super windowControllerDidLoadNib:aController];
|
||||
// Interface Builder bug?
|
||||
[self.consoleWindow setContentSize:self.consoleWindow.minSize];
|
||||
[self.consoleWindow setContentSize:self.consoleWindow.frame.size];
|
||||
/* Close Open Panels, if any */
|
||||
for (NSWindow *window in [[NSApplication sharedApplication] windows]) {
|
||||
if ([window isKindOfClass:[NSOpenPanel class]]) {
|
||||
|
@ -897,9 +923,16 @@ again:;
|
|||
[self.vramWindow setFrame:vram_window_rect display:true animate:false];
|
||||
|
||||
|
||||
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]];
|
||||
if (@available(macOS 11.0, *)) {
|
||||
self.consoleWindow.subtitle = [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;
|
||||
|
||||
|
@ -1152,6 +1185,17 @@ again:;
|
|||
if (@available(macOS 10.10, *)) {
|
||||
_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
|
||||
|
@ -1316,6 +1360,7 @@ static bool is_path_writeable(const char *path)
|
|||
[self.vramWindow close];
|
||||
[self.printerFeedWindow close];
|
||||
[self.cheatsWindow close];
|
||||
[_cheatSearchController.window close];
|
||||
[super close];
|
||||
}
|
||||
|
||||
|
@ -1325,6 +1370,8 @@ static bool is_path_writeable(const char *path)
|
|||
GB_debugger_break(&_gb);
|
||||
[self start];
|
||||
[self.consoleWindow makeKeyAndOrderFront:nil];
|
||||
double secondUsage = GB_debugger_get_second_cpu_usage(&_gb);
|
||||
_cpuCounter.stringValue = [NSString stringWithFormat:@"%.2f%%", secondUsage * 100];
|
||||
[self.consoleInput becomeFirstResponder];
|
||||
}
|
||||
|
||||
|
@ -1406,6 +1453,9 @@ static bool is_path_writeable(const char *path)
|
|||
else if ([anItem action] == @selector(decreaseWindowSize:)) {
|
||||
return [self newRect:NULL forWindow:_mainWindow action:GBWindowResizeActionDecrease];
|
||||
}
|
||||
else if ([anItem action] == @selector(reloadROM:)) {
|
||||
return !_gbsTracks;
|
||||
}
|
||||
|
||||
return [super validateUserInterfaceItem:anItem];
|
||||
}
|
||||
|
@ -1547,7 +1597,9 @@ enum GBWindowResizeAction
|
|||
[self reloadVRAMData: nil];
|
||||
|
||||
[textView.textStorage appendAttributedString:_pendingConsoleOutput];
|
||||
[textView scrollToEndOfDocument:nil];
|
||||
if (!_logToSideView) {
|
||||
[textView scrollToEndOfDocument:nil];
|
||||
}
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) {
|
||||
[self.consoleWindow orderFront:nil];
|
||||
}
|
||||
|
@ -1605,7 +1657,9 @@ enum GBWindowResizeAction
|
|||
|
||||
- (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
|
||||
|
@ -1955,124 +2009,130 @@ enum GBWindowResizeAction
|
|||
|
||||
- (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:^{
|
||||
uint16_t addr;
|
||||
uint16_t bank;
|
||||
if (GB_debugger_evaluate(&_gb, [[sender stringValue] UTF8String], &addr, &bank)) {
|
||||
return;
|
||||
if (GB_debugger_evaluate(&_gb, [expression UTF8String], &addr, &bank)) {
|
||||
fail = true;
|
||||
}
|
||||
|
||||
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) {
|
||||
NSBeep();
|
||||
[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
|
||||
{
|
||||
NSString *expression = [sender stringValue];
|
||||
__block uint16_t addr, bank;
|
||||
__block bool fail = false;
|
||||
NSString *error = [self captureOutputForBlock:^{
|
||||
uint16_t addr, bank;
|
||||
if (GB_debugger_evaluate(&_gb, [[sender stringValue] UTF8String], &addr, &bank)) {
|
||||
if (GB_debugger_evaluate(&_gb, [expression UTF8String], &addr, &bank)) {
|
||||
fail = true;
|
||||
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) {
|
||||
NSBeep();
|
||||
[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
|
||||
|
@ -2110,9 +2170,8 @@ enum GBWindowResizeAction
|
|||
}
|
||||
byteArray.selectedBank = bank;
|
||||
_statusRep.bankForDescription = bank;
|
||||
if (bank != (uint16_t)-1) {
|
||||
[self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]];
|
||||
}
|
||||
[self.memoryBankInput setStringValue:(bank == (uint16_t)-1)? @"" :
|
||||
[NSString stringWithFormat:@"$%x", byteArray.selectedBank]];
|
||||
|
||||
[_hexController reloadData];
|
||||
for (NSView *view in self.memoryView.subviews) {
|
||||
|
@ -2452,9 +2511,16 @@ enum GBWindowResizeAction
|
|||
- (void)setFileURL:(NSURL *)fileURL
|
||||
{
|
||||
[super setFileURL:fileURL];
|
||||
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[fileURL path] lastPathComponent]];
|
||||
self.memoryWindow.title = [NSString stringWithFormat:@"Memory – %@", [[fileURL path] lastPathComponent]];
|
||||
self.vramWindow.title = [NSString stringWithFormat:@"VRAM Viewer – %@", [[fileURL path] lastPathComponent]];
|
||||
if (@available(macOS 11.0, *)) {
|
||||
self.consoleWindow.subtitle = [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]];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview;
|
||||
|
@ -2526,6 +2592,8 @@ enum GBWindowResizeAction
|
|||
}
|
||||
GB_set_turbo_mode(&_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);
|
||||
_slave = partner;
|
||||
partner->_master = self;
|
||||
GB_set_turbo_cap(&partner->_gb, 0);
|
||||
_linkOffset = 0;
|
||||
GB_set_serial_transfer_bit_start_callback(&_gb, _linkCableBitStart);
|
||||
GB_set_serial_transfer_bit_start_callback(&partner->_gb, _linkCableBitStart);
|
||||
|
|
|
@ -1,8 +1,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>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -15,6 +15,8 @@
|
|||
<outlet property="consoleInput" destination="l22-S8-uji" id="Heu-am-YgB"/>
|
||||
<outlet property="consoleOutput" destination="doS-dM-hnl" id="Gn5-ju-Wb0"/>
|
||||
<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="debuggerBackstepButton" destination="E87-Uq-f2l" id="PI7-Wu-f0v"/>
|
||||
<outlet property="debuggerContinueButton" destination="ybQ-jy-NgI" id="fRF-S3-xSh"/>
|
||||
|
@ -54,7 +56,7 @@
|
|||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<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"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
|
||||
<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">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" HUD="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"/>
|
||||
<value key="minSize" type="size" width="921" height="400"/>
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
</box>
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<size key="minSize" width="591" height="349"/>
|
||||
<size key="minSize" width="591" height="429"/>
|
||||
<size key="maxSize" width="1160" height="10000000"/>
|
||||
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<allowedInputSourceLocales>
|
||||
|
@ -136,7 +138,7 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<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"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
|
@ -160,7 +162,7 @@
|
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -173,7 +175,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -185,7 +187,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -197,7 +199,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -209,7 +211,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -229,7 +231,7 @@
|
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
</box>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -245,11 +247,11 @@
|
|||
</subviews>
|
||||
</customView>
|
||||
<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"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="329" height="63"/>
|
||||
|
@ -279,22 +281,22 @@
|
|||
</scroller>
|
||||
</scrollView>
|
||||
<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"/>
|
||||
</box>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<size key="minSize" width="329" height="338"/>
|
||||
<size key="minSize" width="329" height="339"/>
|
||||
<size key="maxSize" width="1160" height="10000000"/>
|
||||
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<allowedInputSourceLocales>
|
||||
|
@ -309,10 +311,38 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<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"/>
|
||||
</scroller>
|
||||
</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>
|
||||
</customView>
|
||||
</subviews>
|
||||
|
@ -340,20 +370,20 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="528" height="320"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</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>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="WUk-8p-S6B"/>
|
||||
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="E3z-um-6KG"/>
|
||||
<toolbarItem implicitItemIdentifier="4F6AAE25-1E9D-4111-9E5B-91F0792E56CD" label="Address Space" paletteLabel="Address Space" id="VTy-lj-K0H">
|
||||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="100" height="25"/>
|
||||
<size key="maxSize" width="130" height="25"/>
|
||||
<size key="minSize" width="160" height="25"/>
|
||||
<size key="maxSize" width="160" height="25"/>
|
||||
<popUpButton key="view" verticalHuggingPriority="750" id="vfJ-vu-gqJ">
|
||||
<rect key="frame" x="0.0" y="14" width="128" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bpD-j9-omo">
|
||||
<rect key="frame" x="0.0" y="14" width="160" height="25"/>
|
||||
<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" customClass="GBToolbarPopUpButtonCell">
|
||||
<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">
|
||||
<items>
|
||||
<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">
|
||||
<nil key="toolTip"/>
|
||||
<size key="minSize" width="64" height="22"/>
|
||||
<size key="maxSize" width="64" height="22"/>
|
||||
<textField key="view" verticalHuggingPriority="750" id="rdV-q6-hc6">
|
||||
<size key="maxSize" width="80" height="22"/>
|
||||
<textField key="view" focusRingType="none" verticalHuggingPriority="750" id="rdV-q6-hc6">
|
||||
<rect key="frame" x="0.0" y="14" width="64" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" id="JCn-Y1-eHS">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<action selector="hexUpdateBank:" target="-2" id="Mx9-WI-wgO"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</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"/>
|
||||
<size key="minSize" width="96" height="22"/>
|
||||
<size key="maxSize" width="128" height="22"/>
|
||||
<textField key="view" verticalHuggingPriority="750" id="EJd-jG-hmH">
|
||||
<rect key="frame" x="0.0" y="14" width="96" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" borderStyle="bezel" bezelStyle="round" id="vg5-Nn-abb">
|
||||
<size key="minSize" width="160" height="22"/>
|
||||
<size key="maxSize" width="160" height="22"/>
|
||||
<textField key="view" focusRingType="none" verticalHuggingPriority="750" id="EJd-jG-hmH">
|
||||
<rect key="frame" x="0.0" y="14" width="160" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
||||
<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"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<action selector="hexGoTo:" target="-2" id="7WG-8C-SK8"/>
|
||||
|
@ -422,7 +450,7 @@
|
|||
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
|
@ -430,7 +458,7 @@
|
|||
<rect key="frame" x="0.0" y="406" width="512" height="5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" id="umk-4r-VNg">
|
||||
|
@ -563,7 +591,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -581,7 +609,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -649,21 +677,19 @@
|
|||
</tabView>
|
||||
</subviews>
|
||||
</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>
|
||||
<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"/>
|
||||
<size key="minSize" width="100" height="25"/>
|
||||
<size key="maxSize" width="268" height="25"/>
|
||||
<segmentedControl key="view" verticalHuggingPriority="750" id="Aul-vO-dCK">
|
||||
<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">
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment label="Tileset" selected="YES"/>
|
||||
<segment label="Tilemap" tag="1"/>
|
||||
<segment label="Tilemap"/>
|
||||
<segment label="Objects"/>
|
||||
<segment label="Palettes"/>
|
||||
</segments>
|
||||
|
@ -686,7 +712,7 @@
|
|||
</connections>
|
||||
<point key="canvasLocation" x="182" y="760"/>
|
||||
</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"/>
|
||||
<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"/>
|
||||
|
@ -702,7 +728,7 @@
|
|||
</imageView>
|
||||
</subviews>
|
||||
</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>
|
||||
<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"/>
|
||||
|
@ -754,7 +780,7 @@
|
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" id="mzf-yu-RID">
|
||||
<rect key="frame" x="1" y="1" width="604" height="257"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="hqi-ob-NW9">
|
||||
<rect key="frame" x="20" y="51" width="176" height="19"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="hqi-ob-NW9">
|
||||
<rect key="frame" x="20" y="52" width="176" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="To value:" id="Ycx-oE-aA4">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -844,7 +870,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -854,8 +880,8 @@
|
|||
<action selector="updateCheat:" target="v7q-gT-jHT" id="kNc-cj-bmF"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C6E-oI-hDC">
|
||||
<rect key="frame" x="22" y="112" width="276" height="21"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C6E-oI-hDC">
|
||||
<rect key="frame" x="22" y="113" width="276" height="21"/>
|
||||
<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">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -866,7 +892,7 @@
|
|||
<outlet property="delegate" destination="v7q-gT-jHT" id="zyw-h0-hRP"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<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">
|
||||
|
@ -881,7 +907,7 @@
|
|||
<outlet property="delegate" destination="v7q-gT-jHT" id="79v-33-R1X"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<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">
|
||||
|
@ -896,7 +922,7 @@
|
|||
<outlet property="delegate" destination="v7q-gT-jHT" id="P69-nT-oOt"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<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">
|
||||
|
@ -911,8 +937,8 @@
|
|||
<outlet property="delegate" destination="v7q-gT-jHT" id="6RH-dg-SL7"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="uFo-ly-Veq">
|
||||
<rect key="frame" x="18" y="82" width="178" height="19"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="uFo-ly-Veq">
|
||||
<rect key="frame" x="18" y="83" width="178" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Change byte at address:" id="xwa-TF-eY1">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -922,8 +948,8 @@
|
|||
</textField>
|
||||
</subviews>
|
||||
</customView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r5T-ol-Dod">
|
||||
<rect key="frame" x="316" y="115" width="270" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r5T-ol-Dod">
|
||||
<rect key="frame" x="316" y="116" width="270" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Import GameShark or Game Genie cheat:" id="0mf-EN-cKc">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -931,7 +957,7 @@
|
|||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</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"/>
|
||||
<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">
|
||||
|
@ -946,7 +972,7 @@
|
|||
<action selector="selectText:" target="KHj-uX-Wbk" id="11z-0U-tMA"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<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">
|
||||
|
@ -959,7 +985,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
@ -970,7 +996,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<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"/>
|
||||
</box>
|
||||
</subviews>
|
||||
|
@ -1000,7 +1026,7 @@
|
|||
<image name="HelpTemplate" width="14" height="14"/>
|
||||
<image name="InterruptTemplate" width="14" height="14"/>
|
||||
<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="StepTemplate" width="14" height="14"/>
|
||||
<image name="printer" catalog="system" width="18" height="16"/>
|
||||
|
|
|
@ -8,9 +8,15 @@
|
|||
#import <JoyKit/JoyKit.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <mach-o/dyld.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#define UPDATE_SERVER "https://sameboy.github.io"
|
||||
|
||||
@interface NSToolbarItem(private)
|
||||
- (NSButton *)_view;
|
||||
@end
|
||||
|
||||
static uint32_t color_to_int(NSColor *color)
|
||||
{
|
||||
color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
|
||||
|
@ -40,7 +46,14 @@ static uint32_t color_to_int(NSColor *color)
|
|||
- (void) applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
// 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];
|
||||
for (unsigned i = 0; i < GBKeyboardButtonCount; i++) {
|
||||
|
@ -93,6 +106,9 @@ static uint32_t color_to_int(NSColor *color)
|
|||
@"GBDebuggerFont": hasSFMono? @"SF Mono" : @"Menlo",
|
||||
@"GBDebuggerFontSize": @12,
|
||||
|
||||
@"GBColorPalette": @1,
|
||||
@"GBTurboCap": @0,
|
||||
|
||||
// Default themes
|
||||
@"GBThemes": @{
|
||||
@"Canyon": @{
|
||||
|
@ -326,9 +342,8 @@ static uint32_t color_to_int(NSColor *color)
|
|||
#ifndef UPDATE_SUPPORT
|
||||
[_preferencesWindow.toolbar removeItemAtIndex:4];
|
||||
#endif
|
||||
if (@available(macOS 11.0, *)) {
|
||||
[_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:0];
|
||||
[_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:_preferencesWindow.toolbar.items.count];
|
||||
for (unsigned i = _preferencesWindow.toolbar.items.count; i--;) {
|
||||
[_preferencesWindow.toolbar.items[i] _view].imageScaling = NSImageScaleNone;
|
||||
}
|
||||
}
|
||||
[_preferencesWindow makeKeyAndOrderFront:self];
|
||||
|
@ -388,7 +403,12 @@ static uint32_t color_to_int(NSColor *color)
|
|||
else {
|
||||
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.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: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;
|
||||
atexit_b(^{
|
||||
execl(executablePath.UTF8String, executablePath.UTF8String, "--update-launch", NULL);
|
||||
|
@ -763,4 +791,27 @@ static uint32_t color_to_int(NSColor *color)
|
|||
- (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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBCPUView : NSView
|
||||
- (void)addSample:(double)sample;
|
||||
@end
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBColorTextCell : NSTextFieldCell
|
||||
|
||||
@end
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBDisabledButton : NSButton
|
||||
|
||||
@end
|
|
@ -0,0 +1,8 @@
|
|||
#import "GBDisabledButton.h"
|
||||
|
||||
@implementation GBDisabledButton
|
||||
- (void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
|
||||
}
|
||||
@end
|
|
@ -136,7 +136,7 @@
|
|||
if (!_gb) {
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -46,17 +46,29 @@
|
|||
-(void)drawKnob:(NSRect)knobRect
|
||||
{
|
||||
[super drawKnob:knobRect];
|
||||
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;
|
||||
NSBezierPath *path = nil;
|
||||
if (@available(macos 26.0, *)) {
|
||||
NSRect peekRect = knobRect;
|
||||
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;
|
||||
if (!self.enabled) {
|
||||
color = [color colorWithAlphaComponent:0.5];
|
||||
}
|
||||
[color setFill];
|
||||
NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:peekRect];
|
||||
[path fill];
|
||||
[[NSColor colorWithWhite:0 alpha:0.25] setStroke];
|
||||
[path setLineWidth:0.5];
|
||||
|
|
|
@ -44,7 +44,8 @@
|
|||
}
|
||||
|
||||
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 y = 0; y < 2; y++) {
|
||||
NSRect rect = parent.scrollRect;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
static GBJoyConManager *manager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[self alloc] _init];
|
||||
manager = [[super allocWithZone:nil] _init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
@ -32,6 +32,16 @@
|
|||
return ret;
|
||||
}
|
||||
|
||||
+ (instancetype)allocWithZone:(struct _NSZone *)zone
|
||||
{
|
||||
return [self sharedInstance];
|
||||
}
|
||||
|
||||
+ (instancetype)alloc
|
||||
{
|
||||
return [self sharedInstance];
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self.class sharedInstance];
|
||||
|
|
|
@ -399,10 +399,16 @@ static double blend(double from, double to, double position)
|
|||
[sender.window.sheetParent endSheet:sender.window];
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
+ (instancetype)alloc
|
||||
{
|
||||
static id singleton = nil;
|
||||
if (singleton) return singleton;
|
||||
return (singleton = [super init]);
|
||||
return (singleton = [super allocWithZone:nil]);
|
||||
}
|
||||
|
||||
+ (instancetype)allocWithZone:(struct _NSZone *)zone
|
||||
{
|
||||
return [self alloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -1,8 +1,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>
|
||||
<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"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -22,44 +22,44 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="512" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ypt-t4-Mf3">
|
||||
<rect key="frame" x="131" y="0.0" width="96" height="24"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ypt-t4-Mf3">
|
||||
<rect key="frame" x="131" y="0.0" width="95" height="24"/>
|
||||
<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"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KkX-Z8-Sqi">
|
||||
<rect key="frame" x="226" y="0.0" width="96" height="24"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KkX-Z8-Sqi">
|
||||
<rect key="frame" x="226" y="0.0" width="95" height="24"/>
|
||||
<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"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jDk-Ej-4yI">
|
||||
<rect key="frame" x="321" y="0.0" width="96" height="24"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jDk-Ej-4yI">
|
||||
<rect key="frame" x="321" y="0.0" width="95" height="24"/>
|
||||
<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"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7PI-YE-fTk">
|
||||
<rect key="frame" x="416" y="0.0" width="96" height="24"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7PI-YE-fTk">
|
||||
<rect key="frame" x="416" y="0.0" width="95" height="24"/>
|
||||
<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"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NOK-yI-LKh">
|
||||
<rect key="frame" x="4" y="0.0" width="121" height="20"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NOK-yI-LKh">
|
||||
<rect key="frame" x="8" y="0.0" width="121" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Background 0" id="qM4-cY-SDE">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
@property IBOutlet NSPopUpButton *colorPalettePopupButton;
|
||||
@property IBOutlet NSPopUpButton *hotkey1PopupButton;
|
||||
@property IBOutlet NSPopUpButton *hotkey2PopupButton;
|
||||
@property IBOutlet NSButton *turboCapButton;
|
||||
@property IBOutlet NSSlider *turboCapSlider;
|
||||
@property IBOutlet NSTextField *turboCapLabel;
|
||||
|
||||
@property IBOutlet GBTitledPopUpButton *fontPopupButton;
|
||||
@property IBOutlet NSStepper *fontSizeStepper;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
- (NSWindowToolbarStyle)toolbarStyle
|
||||
{
|
||||
return NSWindowToolbarStyleExpanded;
|
||||
return NSWindowToolbarStylePreference;
|
||||
}
|
||||
|
||||
- (void)close
|
||||
|
@ -342,6 +342,13 @@ static inline NSString *keyEquivalentString(NSMenuItem *item)
|
|||
|
||||
_fontSizeStepper.intValue = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"];
|
||||
[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
|
||||
|
@ -555,4 +562,35 @@ static inline NSString *keyEquivalentString(NSMenuItem *item)
|
|||
[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
|
||||
|
|
|
@ -1,8 +1,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>
|
||||
<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"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -25,7 +25,7 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="332" height="221"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
|
||||
<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"/>
|
||||
</textFieldCell>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Author" id="IgT-r1-T38">
|
||||
|
@ -59,7 +59,7 @@
|
|||
<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">
|
||||
<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"/>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
|
@ -67,7 +67,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<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"/>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="momentary" id="cmq-I8-cFL">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -94,7 +94,7 @@
|
|||
</customView>
|
||||
</subviews>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Copyright" id="nM9-oF-OV9">
|
||||
|
|
|
@ -11,23 +11,23 @@
|
|||
|
||||
@implementation GBTerminalTextFieldCell
|
||||
{
|
||||
GBTerminalTextView *field_editor;
|
||||
GBTerminalTextView *_fieldEditor;
|
||||
}
|
||||
|
||||
- (NSTextView *)fieldEditorForView:(NSTextField *)controlView
|
||||
{
|
||||
if (field_editor) {
|
||||
field_editor.gb = self.gb;
|
||||
return field_editor;
|
||||
if (_fieldEditor) {
|
||||
_fieldEditor.gb = self.gb;
|
||||
return _fieldEditor;
|
||||
}
|
||||
field_editor = [[GBTerminalTextView alloc] init];
|
||||
[field_editor setFieldEditor:true];
|
||||
field_editor.gb = self.gb;
|
||||
field_editor->_field = (NSTextField *)controlView;
|
||||
_fieldEditor = [[GBTerminalTextView alloc] init];
|
||||
[_fieldEditor setFieldEditor:true];
|
||||
_fieldEditor.gb = self.gb;
|
||||
_fieldEditor->_field = (NSTextField *)controlView;
|
||||
((NSTextFieldCell *)controlView.cell).textInset =
|
||||
field_editor.textContainerInset =
|
||||
NSMakeSize(0, 2);
|
||||
return field_editor;
|
||||
_fieldEditor.textContainerInset =
|
||||
NSMakeSize(7, 2);
|
||||
return _fieldEditor;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBToolbarFieldCell : NSSearchFieldCell
|
||||
|
||||
@end
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBToolbarPopUpButtonCell : NSPopUpButtonCell
|
||||
|
||||
@end
|
|
@ -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
|
|
@ -148,6 +148,9 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
[self observeStandardDefaultsKey:@"GBAspectRatioUnkept" withBlock:^(id newValue) {
|
||||
[weakSelf setFrame:weakSelf.superview.frame];
|
||||
}];
|
||||
[self observeStandardDefaultsKey:@"GBForceIntegerScale" withBlock:^(id newValue) {
|
||||
[weakSelf setFrame:weakSelf.superview.frame];
|
||||
}];
|
||||
[self observeStandardDefaultsKey:@"JoyKitDefaultControllers" withBlock:^(id newValue) {
|
||||
[weakSelf reassignControllers];
|
||||
}];
|
||||
|
@ -282,7 +285,9 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
|
||||
- (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"]) {
|
||||
double ratio = frame.size.width / frame.size.height;
|
||||
double width = GB_get_screen_width(_gb);
|
||||
|
@ -300,6 +305,19 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
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];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBWindow : NSWindow
|
||||
|
||||
@end
|
|
@ -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
|
BIN
Cocoa/Icon.png
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
@ -113,7 +113,9 @@
|
|||
<key>CFBundleExecutable</key>
|
||||
<string>SameBoy</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon.icns</string>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.github.liji32.sameboy</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 281 B |
Before Width: | Height: | Size: 689 B After Width: | Height: | Size: 461 B |
|
@ -1,8 +1,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>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.9"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
|
@ -82,7 +82,7 @@
|
|||
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<connections>
|
||||
<action selector="cartSwap:" target="-1" id="iXS-Ol-IS0"/>
|
||||
|
@ -102,7 +102,7 @@
|
|||
</menu>
|
||||
</menuItem>
|
||||
<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>
|
||||
<action selector="newCartridgeInstance:" target="-1" id="GJc-xU-ZEr"/>
|
||||
</connections>
|
||||
|
@ -158,7 +158,7 @@
|
|||
</connections>
|
||||
</menuItem>
|
||||
<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"/>
|
||||
<menu key="submenu" title="Find" id="4R6-IU-Jq6">
|
||||
<items>
|
||||
|
@ -197,23 +197,23 @@
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Emulation" id="HyV-fh-RgO">
|
||||
<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>
|
||||
<action selector="reset:" target="-1" id="DKW-Bd-h3v"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<connections>
|
||||
<action selector="reset:" target="-1" id="VV1-VP-L7g"/>
|
||||
</connections>
|
||||
</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>
|
||||
<action selector="reloadROM:" target="-1" id="BpN-8V-Csg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Pause" keyEquivalent="p" id="4K4-hw-R7Q">
|
||||
<menuItem title="Pause" secondaryImage="pause" catalog="system" keyEquivalent="p" id="4K4-hw-R7Q">
|
||||
<connections>
|
||||
<action selector="togglePause:" target="-1" id="osW-wt-QAa"/>
|
||||
</connections>
|
||||
|
@ -263,7 +263,7 @@
|
|||
</menu>
|
||||
</menuItem>
|
||||
<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"/>
|
||||
<menu key="submenu" title="Save State" id="Mxx-u1-M9D">
|
||||
<items>
|
||||
|
@ -320,7 +320,7 @@
|
|||
</items>
|
||||
</menu>
|
||||
</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"/>
|
||||
<menu key="submenu" title="Load State" id="l9D-Ej-sh2">
|
||||
<items>
|
||||
|
@ -388,7 +388,7 @@
|
|||
</menu>
|
||||
</menuItem>
|
||||
<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>
|
||||
<action selector="saveScreenshot:" target="-1" id="gJd-ml-J8p"/>
|
||||
</connections>
|
||||
|
@ -405,12 +405,12 @@
|
|||
</connections>
|
||||
</menuItem>
|
||||
<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>
|
||||
<action selector="toggleAudioRecording:" target="-1" id="YE5-mi-Yzd"/>
|
||||
</connections>
|
||||
</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>
|
||||
<action selector="mute:" target="-1" id="eK3-ea-ExJ"/>
|
||||
</connections>
|
||||
|
@ -433,7 +433,7 @@
|
|||
<action selector="showCheats:" target="-1" id="stn-Ei-aFE"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<connections>
|
||||
<action selector="showCheatSearch:" target="-1" id="eI1-x0-ykn"/>
|
||||
|
@ -442,9 +442,9 @@
|
|||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Connectivity" id="IcW-ZC-4wb">
|
||||
<menuItem title="Connect" id="IcW-ZC-4wb">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Connectivity" id="BDM-Cv-BOm">
|
||||
<menu key="submenu" title="Connect" id="BDM-Cv-BOm">
|
||||
<items>
|
||||
<menuItem title="None" id="SiH-Q4-OBY">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
|
@ -452,7 +452,7 @@
|
|||
<action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Game Link Cable & Infrared" id="V4S-Fo-xJK">
|
||||
<menuItem title="Game Link Cable & Infrared" secondaryImage="cable.connector.horizontal" catalog="system" id="V4S-Fo-xJK">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Game Link Cable & Infrared" id="6sJ-Wz-QLj">
|
||||
<connections>
|
||||
|
@ -463,13 +463,13 @@
|
|||
<action selector="nop:" target="-3" id="Bpa-0C-lkN"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<connections>
|
||||
<action selector="connectPrinter:" target="-1" id="tl1-CL-tAw"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Workboy" id="lo9-CX-BJj">
|
||||
<menuItem title="Workboy" secondaryImage="keyboard" catalog="system" id="lo9-CX-BJj">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="connectWorkboy:" target="-1" id="6vS-bq-wAX"/>
|
||||
|
@ -482,14 +482,14 @@
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Develop" id="UVb-cc-at0">
|
||||
<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"/>
|
||||
<connections>
|
||||
<action selector="toggleDeveloperMode:" target="-3" id="PIc-o3-bzb"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<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"/>
|
||||
<connections>
|
||||
<action selector="showConsoleWindow:" target="-1" id="mFf-4i-jLG"/>
|
||||
|
@ -501,14 +501,14 @@
|
|||
</connections>
|
||||
</menuItem>
|
||||
<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"/>
|
||||
<connections>
|
||||
<action selector="interrupt:" target="-1" id="ZmB-wG-fTs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<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"/>
|
||||
<menu key="submenu" title="Audio Channels" id="l22-4p-yTK">
|
||||
<items>
|
||||
|
@ -540,13 +540,13 @@
|
|||
</menu>
|
||||
</menuItem>
|
||||
<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"/>
|
||||
<connections>
|
||||
<action selector="toggleDisplayBackground:" target="-1" id="p5b-1n-SPR"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<connections>
|
||||
<action selector="toggleDisplayObjects:" target="-1" id="8ie-ey-739"/>
|
||||
|
@ -620,4 +620,24 @@
|
|||
<point key="canvasLocation" x="140" y="260"/>
|
||||
</menu>
|
||||
</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>
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#import <objc/runtime.h>
|
||||
|
||||
@interface NSTextFieldCell ()
|
||||
- (CGRect)_textLayerDrawingRectForCellFrame:(CGRect)rect;
|
||||
@property NSSize textInset;
|
||||
- (bool)_isEditingInView:(NSView *)view;
|
||||
@end
|
||||
|
||||
@implementation NSTextFieldCell (Inset)
|
||||
|
@ -19,21 +19,59 @@
|
|||
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;
|
||||
ret.origin.x += inset.width;
|
||||
ret.origin.y += inset.height;
|
||||
ret.size.width -= inset.width;
|
||||
ret.size.height -= inset.height;
|
||||
return ret;
|
||||
if (self.drawsBackground) {
|
||||
[self.backgroundColor setFill];
|
||||
if ([self _isEditingInView:controlView]) {
|
||||
NSRectFill(cellFrame);
|
||||
}
|
||||
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
|
||||
{
|
||||
method_exchangeImplementations(class_getInstanceMethod(self, @selector(_textLayerDrawingRectForCellFrame:)),
|
||||
class_getInstanceMethod(self, @selector(_textLayerDrawingRectForCellFrameHook:)));
|
||||
method_exchangeImplementations(class_getInstanceMethod(self, @selector(drawWithFrame:inView:)),
|
||||
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
|
||||
|
|
|
@ -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>
|
|
@ -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
|
Before Width: | Height: | Size: 527 B After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 407 B After Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 569 B |
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 801 B After Width: | Height: | Size: 543 B |
121
Core/apu.c
|
@ -6,7 +6,7 @@
|
|||
#include <stdlib.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 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));
|
||||
memset(master, 0, master_size * sizeof(*master));
|
||||
|
||||
const unsigned sine_size = 256 * GB_BAND_LIMITED_PHASES + 2;
|
||||
const unsigned max_harmonic = sine_size / 2 / GB_BAND_LIMITED_PHASES;
|
||||
nounroll for (unsigned harmonic = 1; harmonic <= max_harmonic; harmonic += 2) {
|
||||
double amplitude = 1.0 / harmonic / 2;
|
||||
double to_angle = M_PI * 2 / sine_size * harmonic;
|
||||
nounroll for (unsigned i = 0; i < master_size; i++) {
|
||||
master[i] += sin(((signed)(i + 1) - (signed)master_size / 2) * to_angle) * amplitude;
|
||||
}
|
||||
const double lowpass = 15.0 / 16.0; // 1.0 means using Nyquist as the exact cutoff
|
||||
const double to_angle = M_PI / GB_BAND_LIMITED_PHASES * lowpass;
|
||||
double sum = 0;
|
||||
nounroll for (signed i = 0; i < master_size; i++) {
|
||||
// Exact Blackman window
|
||||
const double a0 = 7938 / 18608.0;
|
||||
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 (unsigned i = 0; i < master_size - 1; i++) {
|
||||
master[i] += master[master_size - 1];
|
||||
master[i] /= master[master_size - 1] * 2;
|
||||
nounroll for (signed i = 0; i < master_size; i++) {
|
||||
master[i] /= sum;
|
||||
}
|
||||
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 prev = 0;
|
||||
nounroll for (unsigned i = 0; i < GB_BAND_LIMITED_WIDTH; i++) {
|
||||
int32_t cur = master[(GB_BAND_LIMITED_PHASES - 1 - phase) + i * GB_BAND_LIMITED_PHASES] * GB_BAND_LIMITED_ONE;
|
||||
int32_t delta = cur - prev;
|
||||
error = error - delta;
|
||||
prev = cur;
|
||||
band_limited_steps[phase][i] = delta;
|
||||
nounroll for (signed i = 0; i < GB_BAND_LIMITED_WIDTH; i++) {
|
||||
double sum = 0;
|
||||
nounroll for (signed j = 0; j < GB_BAND_LIMITED_PHASES; j++) {
|
||||
signed index = i * GB_BAND_LIMITED_PHASES - phase + j;
|
||||
if (index >= 0) {
|
||||
sum += master[index];
|
||||
}
|
||||
}
|
||||
int32_t cur = sum * GB_BAND_LIMITED_ONE;
|
||||
error -= cur;
|
||||
band_limited_steps[phase][i] = cur;
|
||||
}
|
||||
|
||||
// Make sure the deltas sum to 1.0
|
||||
band_limited_steps[phase][GB_BAND_LIMITED_WIDTH / 2 - 1] += error / 2;
|
||||
band_limited_steps[phase][0] += error - (error / 2);
|
||||
band_limited_steps[phase][GB_BAND_LIMITED_WIDTH / 2] += error;
|
||||
}
|
||||
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;
|
||||
phase = phase & (GB_BAND_LIMITED_PHASES - 1);
|
||||
|
||||
GB_sample_t delta = {
|
||||
struct {
|
||||
signed left, right;
|
||||
} delta = {
|
||||
.left = input->left - band_limited->input.left,
|
||||
.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;
|
||||
|
||||
GB_sample_t delta = {
|
||||
struct {
|
||||
signed left, right;
|
||||
} delta = {
|
||||
.left = input->left - band_limited->input.left,
|
||||
.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->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[] = {
|
||||
|
@ -164,7 +206,7 @@ static void update_sample(GB_gameboy_t *gb, GB_channel_t index, int8_t value, un
|
|||
if (index == GB_WAVE) {
|
||||
/* For some reason, channel 3 is inverted on the AGB, and has a different "silence" value */
|
||||
value ^= 0xF;
|
||||
silence = 7;
|
||||
silence = 7 * 2;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
GB_sample_t output = {
|
||||
.left = (0xF - (left? value : silence) * 2 + bias) * left_volume,
|
||||
.right = (0xF - (right? value : silence) * 2 + bias) * right_volume
|
||||
.left = (0xF - (left? value * 2 + bias : silence)) * left_volume,
|
||||
.right = (0xF - (right? value * 2 + bias : silence)) * right_volume
|
||||
};
|
||||
|
||||
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 {
|
||||
band_limited_update(&gb->apu_output.band_limited[index],
|
||||
&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 {
|
||||
band_limited_update(&gb->apu_output.band_limited[index],
|
||||
&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;
|
||||
}
|
||||
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;
|
||||
|
||||
|
@ -964,6 +1012,8 @@ restart:;
|
|||
|
||||
if (gb->apu_output.sample_rate) {
|
||||
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) {
|
||||
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) {
|
||||
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.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 {
|
||||
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.highpass_rate = pow(0.999958, cycles_per_sample);
|
||||
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)
|
||||
|
|
11
Core/apu.h
|
@ -5,8 +5,10 @@
|
|||
#include <stdio.h>
|
||||
#include "defs.h"
|
||||
|
||||
#define GB_BAND_LIMITED_WIDTH 16
|
||||
#define GB_BAND_LIMITED_PHASES 512
|
||||
#define GB_BAND_LIMITED_WIDTH 64
|
||||
#define GB_BAND_LIMITED_PHASES 256
|
||||
|
||||
#define GB_QUICK_MULTIPLY_COUNT 64
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
#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;
|
||||
uint8_t pos;
|
||||
GB_sample_t input;
|
||||
GB_sample_t last_output;
|
||||
unsigned silence_detection;
|
||||
} GB_band_limited_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -180,6 +184,9 @@ typedef struct {
|
|||
unsigned max_cycles_per_sample;
|
||||
|
||||
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];
|
||||
double dac_discharge[GB_N_CHANNELS];
|
||||
bool channel_muted[GB_N_CHANNELS];
|
||||
|
|
|
@ -73,7 +73,7 @@ bool GB_cheat_search_filter(GB_gameboy_t *gb, const char *expression, GB_cheat_s
|
|||
}
|
||||
skip:;
|
||||
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;
|
||||
}
|
||||
else if (new_data == gb->mbc_ram + gb->mbc_ram_size - 1) {
|
||||
|
|
|
@ -155,15 +155,19 @@ const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const cha
|
|||
uint8_t dummy;
|
||||
/* GameShark */
|
||||
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 value;
|
||||
#endif
|
||||
uint16_t address;
|
||||
if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) {
|
||||
if (bank >= 0x80) {
|
||||
bank &= 0xF;
|
||||
}
|
||||
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[8] = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
uint32_t old_value = 0;
|
||||
uint32_t value = 0;
|
||||
#else
|
||||
uint8_t old_value;
|
||||
uint8_t value;
|
||||
#endif
|
||||
uint16_t address;
|
||||
if (strlen(stripped_cheat) != 8 && strlen(stripped_cheat) != 6) {
|
||||
return NULL;
|
||||
|
|
|
@ -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];
|
||||
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) {
|
||||
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) {
|
||||
if (prefer_name) {
|
||||
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)
|
||||
{
|
||||
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];
|
||||
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_SUBTRACT_FLAG)? 'N' : '-',
|
||||
(gb->f & GB_ZERO_FLAG)? 'Z' : '-');
|
||||
GB_log(gb, "BC = %s\n", value_to_string(gb, gb->bc, false, false));
|
||||
GB_log(gb, "DE = %s\n", value_to_string(gb, gb->de, false, false));
|
||||
GB_log(gb, "HL = %s\n", value_to_string(gb, gb->hl, false, false));
|
||||
GB_log(gb, "SP = %s\n", value_to_string(gb, gb->sp, false, false));
|
||||
GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, 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, 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, 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");
|
||||
return true;
|
||||
}
|
||||
|
@ -1462,10 +1466,10 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!modifiers || !modifiers[0]) {
|
||||
modifiers = "a";
|
||||
if (!modifiers) {
|
||||
modifiers = "";
|
||||
}
|
||||
else if (modifiers[1]) {
|
||||
else if (modifiers[0] && modifiers[1]) {
|
||||
print_usage(gb, command);
|
||||
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);
|
||||
if (!error) {
|
||||
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':
|
||||
GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false, false));
|
||||
break;
|
||||
|
@ -1503,6 +1513,7 @@ static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
|
|||
break;
|
||||
}
|
||||
default:
|
||||
print_usage(gb, command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1728,6 +1739,49 @@ static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debu
|
|||
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)
|
||||
{
|
||||
|
@ -2158,7 +2212,7 @@ static const debugger_command_t commands[] = {
|
|||
{"backtrace", 2, backtrace, "Display the current call stack"},
|
||||
{"bt", 2, }, /* Alias */
|
||||
{"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).",
|
||||
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
|
||||
{"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 "
|
||||
"used. Use 'keep' as an argument to display ticks without reseeting "
|
||||
"the count.", "(keep)", .argument_completer = keep_completer},
|
||||
{"usage", 2, usage, "Display CPU usage"},
|
||||
{"cartridge", 2, mbc, "Display information about the MBC and cartridge"},
|
||||
{"mbc", 3, }, /* Alias */
|
||||
{"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:
|
||||
GB_debugger_break(gb);
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
@ -2533,7 +2588,7 @@ next_command:
|
|||
unsigned breakpoint_id = 0;
|
||||
if (gb->breakpoints && !gb->debug_stopped && (breakpoint_id = should_break(gb, gb->pc, false))) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -2544,7 +2599,7 @@ next_command:
|
|||
bool should_delete_state = true;
|
||||
if (jump_to_result == JUMP_TO_BREAK) {
|
||||
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->non_trivial_jump_breakpoint_occured = false;
|
||||
}
|
||||
|
@ -2554,7 +2609,7 @@ next_command:
|
|||
}
|
||||
else {
|
||||
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_rewind_push(gb);
|
||||
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) {
|
||||
bank = bank_for_addr(gb, addr);
|
||||
}
|
||||
if ((addr >> 12) == 0xC) {
|
||||
bank = 0;
|
||||
}
|
||||
if (exact_match) {
|
||||
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;
|
||||
|
|
|
@ -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_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
|
||||
internal void GB_debugger_run(GB_gameboy_t *gb);
|
||||
internal void GB_debugger_handle_async_commands(GB_gameboy_t *gb);
|
||||
|
|
|
@ -4,13 +4,18 @@
|
|||
#define GB_unlikely(x) __builtin_expect((bool)(x), 0)
|
||||
#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
|
||||
|
||||
// "Keyword" definitions
|
||||
#define likely(x) GB_likely(x)
|
||||
#define unlikely(x) GB_unlikely(x)
|
||||
#define inline_const GB_inline_const
|
||||
#define typeof __typeof__
|
||||
|
||||
#if !defined(MIN)
|
||||
#define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
|
||||
|
|
|
@ -176,13 +176,34 @@ void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
|
|||
gb->cycles_since_vblank_callback = 0;
|
||||
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! */
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
GB_sgb_render(gb);
|
||||
}
|
||||
|
||||
if (gb->turbo) {
|
||||
#ifndef GB_DISABLE_DEBUGGER
|
||||
if (unlikely(gb->backstep_instructions)) return;
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -2470,3 +2491,8 @@ double GB_get_usual_frame_rate(GB_gameboy_t *gb)
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ typedef enum {
|
|||
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_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;
|
||||
|
||||
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;
|
||||
|
||||
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_palette(GB_gameboy_t *gb, const GB_palette_t *palette);
|
||||
const GB_palette_t *GB_get_palette(GB_gameboy_t *gb);
|
||||
|
|
|
@ -1222,8 +1222,10 @@ uint64_t GB_run_frame(GB_gameboy_t *gb)
|
|||
/* Configure turbo temporarily, the user wants to handle FPS capping manually. */
|
||||
bool old_turbo = gb->turbo;
|
||||
bool old_dont_skip = gb->turbo_dont_skip;
|
||||
double old_turbo_cap = gb->turbo_cap_multiplier;
|
||||
gb->turbo = true;
|
||||
gb->turbo_dont_skip = true;
|
||||
gb->turbo_cap_multiplier = 0;
|
||||
|
||||
gb->cycles_since_last_sync = 0;
|
||||
while (true) {
|
||||
|
@ -1234,6 +1236,7 @@ uint64_t GB_run_frame(GB_gameboy_t *gb)
|
|||
}
|
||||
gb->turbo = old_turbo;
|
||||
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 */
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
gb->disable_rendering = disabled;
|
||||
|
|
12
Core/gb.h
|
@ -697,6 +697,7 @@ struct GB_gameboy_internal_s {
|
|||
|
||||
/* Timing */
|
||||
uint64_t last_sync;
|
||||
uint64_t last_render;
|
||||
uint64_t cycles_since_last_sync; // In 8MHz units
|
||||
GB_rtc_mode_t rtc_mode;
|
||||
uint32_t rtc_second_length;
|
||||
|
@ -782,6 +783,14 @@ struct GB_gameboy_internal_s {
|
|||
|
||||
/* Callbacks */
|
||||
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
|
||||
|
||||
#ifndef GB_DISABLE_REWIND
|
||||
|
@ -822,6 +831,8 @@ struct GB_gameboy_internal_s {
|
|||
/* Misc */
|
||||
bool turbo;
|
||||
bool turbo_dont_skip;
|
||||
double turbo_cap_multiplier;
|
||||
bool enable_skipped_frame_vblank_callbacks;
|
||||
bool disable_rendering;
|
||||
uint8_t boot_rom[0x900];
|
||||
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);
|
||||
|
||||
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_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
errno = 0;
|
||||
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, dma )) goto error;
|
||||
|
@ -838,8 +839,10 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
|
|||
goto error;
|
||||
}
|
||||
|
||||
return 0;;
|
||||
error:
|
||||
return 0;
|
||||
if (errno == 0) return EIO;
|
||||
return errno;
|
||||
}
|
||||
|
||||
int GB_save_state(GB_gameboy_t *gb, const char *path)
|
||||
|
|
|
@ -47,10 +47,10 @@ bool GB_timing_sync_turbo(GB_gameboy_t *gb)
|
|||
#endif
|
||||
if (!gb->turbo_dont_skip) {
|
||||
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;
|
||||
}
|
||||
gb->last_sync = nanoseconds;
|
||||
gb->last_render = nanoseconds;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -63,23 +63,32 @@ void GB_timing_sync(GB_gameboy_t *gb)
|
|||
/* Prevent syncing if not enough time has passed.*/
|
||||
if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return;
|
||||
|
||||
unsigned target_clock_rate;
|
||||
if (gb->turbo) {
|
||||
gb->cycles_since_last_sync = 0;
|
||||
if (gb->update_input_hint_callback) {
|
||||
gb->update_input_hint_callback(gb);
|
||||
if (gb->turbo_cap_multiplier) {
|
||||
target_clock_rate = GB_get_clock_rate(gb) * gb->turbo_cap_multiplier;
|
||||
}
|
||||
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 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);
|
||||
gb->last_sync += target_nanoseconds;
|
||||
}
|
||||
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,
|
||||
// just skip this sync and let it even out
|
||||
return;
|
||||
|
@ -472,6 +481,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||
|
||||
#ifndef GB_DISABLE_DEBUGGER
|
||||
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
|
||||
|
||||
// Not affected by speed boost
|
||||
|
|
|
@ -473,7 +473,7 @@ static inline Class preferredByteArrayClass(void) {
|
|||
}
|
||||
|
||||
- (void)_setSingleSelectedContentsRange:(HFRange)newSelection {
|
||||
HFASSERT(HFRangeIsSubrangeOfRange(newSelection, HFRangeMake(0, [self contentsLength])));
|
||||
if (!HFRangeIsSubrangeOfRange(newSelection, HFRangeMake(0, [self contentsLength]))) return;
|
||||
BOOL selectionChanged;
|
||||
if ([selectedContentsRanges count] == 1) {
|
||||
selectionChanged = ! HFRangeEqualsRange([selectedContentsRanges[0] HFRange], newSelection);
|
||||
|
|
54
Makefile
|
@ -36,10 +36,14 @@ else
|
|||
DEFAULT := sdl
|
||||
endif
|
||||
|
||||
|
||||
NULL := /dev/null
|
||||
ifeq ($(PLATFORM),windows32)
|
||||
ifneq ($(shell echo /dev/null*),/dev/null)
|
||||
# Windows shell is not "aware" of /dev/null, use NUL and pray
|
||||
NULL := NUL
|
||||
endif
|
||||
endif
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
ifneq ($(shell which xdg-open 2> $(NULL))$(FREEDESKTOP),)
|
||||
|
@ -96,15 +100,8 @@ else
|
|||
CPPP_FLAGS += -UGB_DISABLE_CHEAT_SEARCH
|
||||
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
|
||||
|
||||
|
||||
include version.mk
|
||||
COPYRIGHT_YEAR := $(shell grep -oE "20[2-9][0-9]" LICENSE)
|
||||
export VERSION
|
||||
|
@ -117,6 +114,12 @@ LIBDIR := build/lib
|
|||
PKGCONF_DIR := $(LIBDIR)/pkgconfig
|
||||
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
|
||||
|
||||
ifdef DATA_DIR
|
||||
|
@ -132,6 +135,8 @@ CC := clang
|
|||
endif
|
||||
endif
|
||||
|
||||
IBTOOL ?= ibtool
|
||||
|
||||
# Find libraries with pkg-config if available.
|
||||
ifneq (, $(shell which pkg-config 2> $(NULL)))
|
||||
# But not on macOS, it's annoying, and not on Haiku, where OpenGL is broken
|
||||
|
@ -210,9 +215,10 @@ CFLAGS += -DUPDATE_SUPPORT
|
|||
endif
|
||||
|
||||
ifeq (,$(PKG_CONFIG))
|
||||
ifneq ($(PLATFORM),windows32)
|
||||
SDL_CFLAGS := $(shell sdl2-config --cflags)
|
||||
SDL_LDFLAGS := $(shell sdl2-config --libs) -lpthread
|
||||
|
||||
endif
|
||||
ifeq ($(PLATFORM),Darwin)
|
||||
SDL_LDFLAGS += -framework AppKit
|
||||
endif
|
||||
|
@ -282,11 +288,13 @@ sdl: $(BIN)/SDL/xaudio2_9redist.dll
|
|||
endif
|
||||
else
|
||||
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),OpenBSD)
|
||||
LDFLAGS += -ldl
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),_ios)
|
||||
OBJ := build/obj-ios
|
||||
|
@ -429,7 +437,8 @@ SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES))
|
|||
TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES))
|
||||
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
|
||||
|
||||
|
@ -541,9 +550,10 @@ $(OBJ)/installer: iOS/installer.m
|
|||
# Cocoa Port
|
||||
|
||||
$(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/Info.plist \
|
||||
Cocoa/SameBoy.entitlements \
|
||||
Misc/registers.sym \
|
||||
$(BIN)/SameBoy.app/Contents/Resources/dmg_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 \
|
||||
Shaders
|
||||
$(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/@COPYRIGHT_YEAR/$(COPYRIGHT_YEAR)/" < Cocoa/License.html > $(BIN)/SameBoy.app/Contents/Resources/Credits.html
|
||||
$(MKDIR) -p $(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/
|
||||
ifeq ($(CONF), release)
|
||||
$(CODESIGN) $@
|
||||
endif
|
||||
$(CODESIGN) $@ --entitlements Cocoa/SameBoy.entitlements
|
||||
|
||||
# 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
|
||||
|
||||
$(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
|
||||
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
|
||||
|
||||
|
@ -666,11 +674,11 @@ $(BIN)/SDL/sameboy.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.
|
|||
|
||||
$(BIN)/SDL/sameboy_debugger.txt:
|
||||
echo Looking for sameboy_debugger.exe? > $@
|
||||
echo\>> $@
|
||||
echo >> $@
|
||||
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 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. >> $@
|
||||
|
||||
ifneq ($(USE_WINDRES),)
|
||||
|
@ -683,13 +691,13 @@ $(OBJ)/%.res: %.rc
|
|||
rc /fo $@ /dVERSION=\"$(VERSION)\" /dCOPYRIGHT_YEAR=\"$(COPYRIGHT_YEAR)\" $^
|
||||
|
||||
%.o: %.res
|
||||
cvtres /OUT:"$@" $^
|
||||
cvtres /MACHINE:X64 /OUT:"$@" $^
|
||||
endif
|
||||
|
||||
# Copy required DLL files for the Windows port
|
||||
$(BIN)/SDL/%.dll:
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
@$(eval MATCH := $(shell where $$LIB:$(notdir $@)))
|
||||
@$(eval MATCH := $(shell where "$(lib)":$(notdir $@)))
|
||||
cp "$(MATCH)" $@
|
||||
|
||||
# Tester
|
||||
|
@ -781,7 +789,7 @@ install: $(BIN)/XdgThumbnailer/sameboy-thumbnailer sdl $(shell find FreeDesktop)
|
|||
install -d $(DESTDIR)$(DATA_DIR)/BootROMs
|
||||
install -d $(DESTDIR)$(PREFIX)/bin
|
||||
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
|
||||
|
||||
(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; \
|
||||
done
|
||||
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
|
||||
for size in 16x16 32x32 64x64 128x128 256x256 512x512; do \
|
||||
install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/$$size/apps; \
|
||||
|
|
|
@ -47,8 +47,9 @@ SameBoy requires the following tools and libraries to build:
|
|||
|
||||
On Windows, SameBoy also requires:
|
||||
* Visual Studio (For headers, etc.)
|
||||
* [GnuWin](http://gnuwin32.sourceforge.net/)
|
||||
* 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](https://git-scm.com/downloads/win) or another distribution of basic Unix utilities
|
||||
* 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:
|
||||
* `cocoa` (Default for macOS)
|
||||
|
|
|
@ -161,6 +161,10 @@ typedef struct {
|
|||
/* v1.0.1 */
|
||||
bool disable_rounded_corners; // Windows only
|
||||
bool use_faux_analog_inputs;
|
||||
|
||||
/* v1.0.2 */
|
||||
int8_t vsync_mode;
|
||||
uint8_t turbo_cap;
|
||||
};
|
||||
} configuration_t;
|
||||
|
||||
|
|
95
SDL/gui.c
|
@ -1173,6 +1173,45 @@ static const char *current_agb_revision_string(unsigned index)
|
|||
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[] = {
|
||||
{"Emulated Model:", cycle_model, current_model_string, cycle_model_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},
|
||||
{"Rewind Length:", cycle_rewind, current_rewind_string, cycle_rewind_backwards},
|
||||
{"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},
|
||||
{NULL,}
|
||||
};
|
||||
|
@ -1598,6 +1638,40 @@ static const char *current_osd_mode(unsigned index)
|
|||
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
|
||||
|
||||
// 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},
|
||||
{"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards},
|
||||
{"On-Screen Display:", toggle_osd, current_osd_mode, toggle_osd},
|
||||
{"Vsync Mode:", cycle_vsync, current_vsync_mode, cycle_vsync_backwards},
|
||||
#ifdef _WIN32
|
||||
{"Window Corners:", toggle_corners, current_corner_mode, toggle_corners},
|
||||
#endif
|
||||
|
@ -2316,6 +2391,10 @@ void run_gui(bool is_running)
|
|||
case SDL_SCANCODE_LEFT:
|
||||
case SDL_SCANCODE_UP:
|
||||
case SDL_SCANCODE_DOWN:
|
||||
case SDL_SCANCODE_H:
|
||||
case SDL_SCANCODE_J:
|
||||
case SDL_SCANCODE_K:
|
||||
case SDL_SCANCODE_L:
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -2703,12 +2782,16 @@ void run_gui(bool is_running)
|
|||
}
|
||||
}
|
||||
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++;
|
||||
mouse_scroling = false;
|
||||
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--;
|
||||
mouse_scroling = false;
|
||||
should_render = true;
|
||||
|
@ -2732,11 +2815,15 @@ void run_gui(bool is_running)
|
|||
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);
|
||||
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);
|
||||
should_render = true;
|
||||
}
|
||||
|
|
26
SDL/main.c
|
@ -277,6 +277,7 @@ static void open_menu(void)
|
|||
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
|
||||
GB_set_rewind_length(&gb, configuration.rewind_length);
|
||||
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)) {
|
||||
signed current_window_width, current_window_height;
|
||||
SDL_GetWindowSize(window, ¤t_window_width, ¤t_window_height);
|
||||
|
@ -381,6 +382,7 @@ static void handle_events(GB_gameboy_t *gb)
|
|||
GB_audio_clear_queue();
|
||||
turbo_down = event.type == SDL_JOYBUTTONDOWN;
|
||||
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) {
|
||||
underclock_down = event.type == SDL_JOYBUTTONDOWN;
|
||||
|
@ -391,6 +393,7 @@ static void handle_events(GB_gameboy_t *gb)
|
|||
rewind_paused = false;
|
||||
}
|
||||
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) {
|
||||
open_menu();
|
||||
|
@ -610,6 +613,7 @@ static void handle_events(GB_gameboy_t *gb)
|
|||
turbo_down = event.type == SDL_KEYDOWN;
|
||||
GB_audio_clear_queue();
|
||||
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]) {
|
||||
rewind_down = event.type == SDL_KEYDOWN;
|
||||
|
@ -617,6 +621,7 @@ static void handle_events(GB_gameboy_t *gb)
|
|||
rewind_paused = false;
|
||||
}
|
||||
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]) {
|
||||
underclock_down = event.type == SDL_KEYDOWN;
|
||||
|
@ -737,15 +742,23 @@ static void debugger_reset(int ignore)
|
|||
#endif
|
||||
|
||||
static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
|
||||
{
|
||||
{
|
||||
if (turbo_down) {
|
||||
static unsigned skip = 0;
|
||||
skip++;
|
||||
if (skip == GB_audio_get_frequency() / 8) {
|
||||
skip = 0;
|
||||
}
|
||||
if (skip > GB_audio_get_frequency() / 16) {
|
||||
return;
|
||||
if (configuration.turbo_cap) {
|
||||
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_rewind_length(&gb, configuration.rewind_length);
|
||||
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_apu_set_sample_callback(&gb, gb_audio_callback);
|
||||
|
||||
|
@ -1382,7 +1396,9 @@ int main(int argc, char **argv)
|
|||
signal(SIGUSR1, debugger_reset);
|
||||
#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 function will not be called if the process is terminated in any way, anyhow.
|
||||
atexit(SDL_Quit);
|
||||
|
@ -1536,6 +1552,8 @@ int main(int argc, char **argv)
|
|||
}
|
||||
#endif
|
||||
|
||||
SDL_GL_SetSwapInterval(configuration.vsync_mode);
|
||||
|
||||
if (filename == NULL) {
|
||||
stop_on_start = false;
|
||||
run_gui(false);
|
||||
|
|
19
build-faq.md
|
@ -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`.
|
||||
|
||||
### 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
|
||||
|
||||
|
@ -40,18 +40,5 @@ set lib=%lib%;C:\SDL2\lib\x64
|
|||
set include=%include%;C:\SDL2\include
|
||||
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.
|
||||
|
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 20 KiB |
BIN
iOS/Assets.car
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 339 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -24,7 +24,8 @@
|
|||
if (section == 0) return 1;
|
||||
size_t 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;
|
||||
}
|
||||
|
||||
|
@ -91,11 +92,11 @@
|
|||
}
|
||||
else {
|
||||
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]];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
[self presentViewController:alertController animated:true completion:nil];
|
||||
}
|
||||
|
||||
+ (UIBarButtonItem *)buttonWithLabel:(NSString *)label
|
||||
|
@ -119,9 +120,17 @@
|
|||
[button sizeToFit];
|
||||
CGRect frame = button.frame;
|
||||
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;
|
||||
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];
|
||||
}
|
||||
|
@ -153,9 +162,7 @@
|
|||
UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url]
|
||||
applicationActivities:nil];
|
||||
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
controller.popoverPresentationController.barButtonItem = self.toolbarItems.firstObject;
|
||||
}
|
||||
controller.popoverPresentationController.barButtonItem = self.toolbarItems.firstObject;
|
||||
|
||||
[self presentViewController:controller
|
||||
animated:true
|
||||
|
@ -178,28 +185,38 @@
|
|||
hasSFSymbols = true;
|
||||
}
|
||||
|
||||
self.toolbarItems = @[
|
||||
hasSFSymbols?
|
||||
[self.class buttonWithLabel:nil
|
||||
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"
|
||||
UIBarButtonItem *export = hasSFSymbols?
|
||||
[self.class buttonWithLabel:nil
|
||||
imageWithName:@"square.and.arrow.up"
|
||||
target:self
|
||||
action:@selector(importCheats)],
|
||||
[self.class buttonWithLabel:@"Add"
|
||||
imageWithName:@"plus"
|
||||
target:self
|
||||
action:@selector(addCheat)],
|
||||
|
||||
];
|
||||
action:@selector(exportCheats)] :
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
|
||||
target:self
|
||||
action:@selector(exportCheats)];
|
||||
|
||||
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;
|
||||
return self;
|
||||
|
|
|
@ -12,6 +12,9 @@ static double StatusBarHeight(void)
|
|||
UIEdgeInsets insets = window.safeAreaInsets;
|
||||
ret = MAX(MAX(insets.left, insets.right), MAX(insets.top, insets.bottom)) ?: 20;
|
||||
[window setHidden:true];
|
||||
if (!ret && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
ret = 32; // iPadOS is buggy af
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
|
@ -47,7 +50,7 @@ static bool HasHomeBar(void)
|
|||
}
|
||||
|
||||
_minY = StatusBarHeight() * _factor;
|
||||
_cutout = _minY <= 24 * _factor? 0 : _minY;
|
||||
_cutout = (_minY <= 24 * _factor || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)? 0 : _minY;
|
||||
|
||||
if (HasHomeBar()) {
|
||||
_homeBar = 21 * _factor;
|
||||
|
|
|
@ -2,4 +2,6 @@
|
|||
|
||||
@interface GBMenuViewController : UIAlertController
|
||||
+ (instancetype)menu;
|
||||
@property (nonatomic) NSInteger selectedButtonIndex;
|
||||
@property (nonatomic, strong) NSArray<UIButton *> *menuButtons;
|
||||
@end
|
||||
|
|
|
@ -29,6 +29,7 @@ static NSString *const tips[] = {
|
|||
{
|
||||
UILabel *_tipLabel;
|
||||
UIVisualEffectView *_effectView;
|
||||
NSMutableArray<UIButton *> *_buttons;
|
||||
}
|
||||
|
||||
+ (instancetype)menu
|
||||
|
@ -41,6 +42,8 @@ static NSString *const tips[] = {
|
|||
[ret addAction:[UIAlertAction actionWithTitle:@"Close"
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:nil]];
|
||||
ret.selectedButtonIndex = -1;
|
||||
ret->_buttons = [[NSMutableArray alloc] init];
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -50,6 +53,7 @@ static NSString *const tips[] = {
|
|||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:true];
|
||||
if (_effectView) return;
|
||||
static const struct {
|
||||
NSString *label;
|
||||
NSString *image;
|
||||
|
@ -80,6 +84,7 @@ static NSString *const tips[] = {
|
|||
button.frame = CGRectMake(round(width * x), height * y, round(width), height);
|
||||
button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
|
||||
[self.view addSubview:button];
|
||||
[_buttons addObject:button];
|
||||
|
||||
if (!buttons[i].selector) {
|
||||
button.enabled = false;
|
||||
|
@ -102,10 +107,23 @@ static NSString *const tips[] = {
|
|||
[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.masksToBounds = true;
|
||||
[self.view.superview addSubview:_effectView];
|
||||
[self.view.window addSubview:_effectView];
|
||||
_tipLabel = [[UILabel alloc] init];
|
||||
unsigned tipIndex = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBTipIndex"];
|
||||
_tipLabel.text = tips[tipIndex % (sizeof(tips) / sizeof(tips[0]))];
|
||||
|
@ -113,20 +131,23 @@ static NSString *const tips[] = {
|
|||
_tipLabel.textColor = [UIColor labelColor];
|
||||
}
|
||||
_tipLabel.font = [UIFont systemFontOfSize:14];
|
||||
_tipLabel.alpha = 0.8;
|
||||
_tipLabel.alpha = 0;
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:tipIndex + 1 forKey:@"GBTipIndex"];
|
||||
_tipLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
_tipLabel.numberOfLines = 3;
|
||||
[_effectView.contentView addSubview:_tipLabel];
|
||||
[self layoutTip];
|
||||
_effectView.alpha = 0;
|
||||
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_effectView.alpha = 1.0;
|
||||
_effectView.effect = effect;
|
||||
_tipLabel.alpha = 0.8;
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void)layoutTip
|
||||
{
|
||||
[_effectView.superview addSubview:_effectView];
|
||||
UIView *view = self.view.superview;
|
||||
CGSize outerSize = view.frame.size;
|
||||
CGSize size = [_tipLabel textRectForBounds:(CGRect){{0, 0},
|
||||
|
@ -135,8 +156,12 @@ static NSString *const tips[] = {
|
|||
limitedToNumberOfLines:3].size;
|
||||
size.width = ceil(size.width);
|
||||
_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) {
|
||||
{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}
|
||||
};
|
||||
}
|
||||
|
@ -145,7 +170,10 @@ static NSString *const tips[] = {
|
|||
- (void)viewWillDisappear:(BOOL)animated
|
||||
{
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_effectView.alpha = 0;
|
||||
_effectView.effect = nil;
|
||||
_tipLabel.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[_effectView removeFromSuperview];
|
||||
}];
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
@ -189,4 +217,116 @@ static NSString *const tips[] = {
|
|||
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
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
@property (readonly) NSString *batterySaveFile;
|
||||
@property (readonly) NSString *autosaveStateFile;
|
||||
@property (readonly) NSString *cheatsFile;
|
||||
@property (readonly) NSArray <NSString *> *forbiddenNames;
|
||||
|
||||
@property (readonly) NSString *localRoot;
|
||||
- (NSString *)stateFile:(unsigned)index;
|
||||
|
|
|
@ -25,12 +25,26 @@
|
|||
if (!self) return nil;
|
||||
self.currentROM = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBLastROM"];
|
||||
_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;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)forbiddenNames
|
||||
{
|
||||
return @[@"Inbox", @"Boot ROMs"];
|
||||
}
|
||||
|
||||
- (void)setCurrentROM:(NSString *)currentROM
|
||||
{
|
||||
_romFile = nil;
|
||||
if ([self.forbiddenNames containsObject:currentROM]) {
|
||||
currentROM = nil;
|
||||
}
|
||||
_currentROM = currentROM;
|
||||
bool foundROM = self.romFile;
|
||||
|
||||
|
@ -58,6 +72,9 @@
|
|||
|
||||
- (NSString *)romDirectoryForROM:(NSString *)romFile
|
||||
{
|
||||
if ([self.forbiddenNames containsObject:romFile]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self.localRoot stringByAppendingPathComponent:romFile];
|
||||
}
|
||||
|
@ -71,12 +88,13 @@
|
|||
|
||||
- (NSString *)romFileForROM:(NSString *)rom
|
||||
{
|
||||
if ([rom isEqualToString:@"Inbox"]) return nil;
|
||||
if ([rom isEqualToString:@"Boot ROMs"]) return nil;
|
||||
if (rom == _currentROM) {
|
||||
return self.romFile;
|
||||
}
|
||||
|
||||
if ([self.forbiddenNames containsObject:rom]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self romFileForDirectory:[self romDirectoryForROM:rom]];
|
||||
}
|
||||
|
@ -143,6 +161,9 @@
|
|||
|
||||
- (NSString *)makeNameUnique:(NSString *)name
|
||||
{
|
||||
if ([self.forbiddenNames containsObject:name]) {
|
||||
name = @"Imported ROM";
|
||||
}
|
||||
NSString *root = self.localRoot;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[root stringByAppendingPathComponent:name]]) return name;
|
||||
|
||||
|
@ -195,9 +216,11 @@
|
|||
[[NSFileManager defaultManager] removeItemAtPath:romFolder error:nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Remove the Inbox directory if empty after import
|
||||
rmdir([self.localRoot stringByAppendingPathComponent:@"Inbox"].UTF8String);
|
||||
|
||||
return friendlyName;
|
||||
}
|
||||
|
||||
|
|
|
@ -247,8 +247,19 @@
|
|||
|
||||
if ([newName containsString:@"/"]) {
|
||||
[self.tableView reloadData];
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"You can't use a name that contains “/”. Please choose another name."
|
||||
message:nil
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Invalid Name"
|
||||
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];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"OK"
|
||||
style:UIAlertActionStyleCancel
|
||||
|
|
|
@ -15,11 +15,10 @@ typedef enum {
|
|||
GBTurbo,
|
||||
GBRewind,
|
||||
GBUnderclock,
|
||||
GBRapidA,
|
||||
GBRapidB,
|
||||
// GBHotkey1, // Todo
|
||||
// GBHotkey2, // Todo
|
||||
GBTotalButtonCount,
|
||||
GBKeyboardButtonCount = GBUnderclock + 1,
|
||||
GBPerPlayerButtonCount = GBStart + 1,
|
||||
GBUnusedButton = 0xFF,
|
||||
} GBButton;
|
||||
|
||||
|
|
|
@ -15,13 +15,34 @@ static NSString const *typeCheck = @"check";
|
|||
static NSString const *typeDisabled = @"disabled";
|
||||
static NSString const *typeSeparator = @"separator";
|
||||
static NSString const *typeSlider = @"slider";
|
||||
static NSString const *typeLightTemp = @"typeLightTemp";
|
||||
static NSString const *typeLightTemp = @"lightTemp";
|
||||
static NSString const *typeTurboSlider = @"turboSlider";
|
||||
|
||||
@implementation GBSettingsViewController
|
||||
{
|
||||
NSArray<NSDictionary *> *_structure;
|
||||
UINavigationController *_detailsNavigation;
|
||||
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
|
||||
|
@ -48,17 +69,32 @@ static NSString const *typeLightTemp = @"typeLightTemp";
|
|||
]
|
||||
},
|
||||
@{
|
||||
@"header": @"Turbo Speed",
|
||||
@"header": @"Turbo Speed Cap",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"200%", @"value": @2,},
|
||||
@{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"400%", @"value": @4,},
|
||||
@{@"type": typeRadio, @"pref": @"GBTurboSpeed", @"title": @"Uncapped", @"value": @1,},
|
||||
@{@"type": typeCheck, @"pref": @"GBTurboCap", @"title": @"Cap Turbo Speed", @"block": ^void(GBSettingsViewController *controller) {
|
||||
if ([[NSUserDefaults standardUserDefaults] floatForKey:@"GBTurboCap"] == 1.0) {
|
||||
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 *(){
|
||||
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": @"OmniScale", @"value": @"OmniScale"},
|
||||
@{@"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",
|
||||
@"type": typeSubmenu,
|
||||
@"submenu": emulationMenu,
|
||||
@"image": [UIImage imageNamed:@"emulationSettings"],
|
||||
@"image": [self settingsImageNamed:@"emulationSettings"],
|
||||
},
|
||||
@{
|
||||
@"title": @"Video",
|
||||
@"type": typeSubmenu,
|
||||
@"submenu": videoMenu,
|
||||
@"image": [UIImage imageNamed:@"videoSettings"],
|
||||
@"image": [self settingsImageNamed:@"videoSettings"],
|
||||
},
|
||||
@{
|
||||
@"title": @"Audio",
|
||||
@"type": typeSubmenu,
|
||||
@"submenu": audioMenu,
|
||||
@"image": [UIImage imageNamed:@"audioSettings"],
|
||||
@"image": [self settingsImageNamed:@"audioSettings"],
|
||||
},
|
||||
@{
|
||||
@"title": @"Controls",
|
||||
@"type": typeSubmenu,
|
||||
@"submenu": controlsMenu,
|
||||
@"image": [UIImage imageNamed:@"controlsSettings"],
|
||||
@"image": [self settingsImageNamed:@"controlsSettings"],
|
||||
},
|
||||
@{
|
||||
@"title": @"Themes",
|
||||
@"type": typeSubmenu,
|
||||
@"class": [GBThemesViewController class],
|
||||
@"image": [UIImage imageNamed:@"themeSettings"],
|
||||
@"image": [self settingsImageNamed:@"themeSettings"],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -416,12 +452,20 @@ static NSString const *typeLightTemp = @"typeLightTemp";
|
|||
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];
|
||||
blank.view.backgroundColor = root.view.backgroundColor;
|
||||
root->_detailsNavigation = [[UINavigationController alloc] initWithRootViewController:blank];
|
||||
split.viewControllers = @[controller, root->_detailsNavigation];
|
||||
split.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
|
||||
root->_iPadRoot = true;
|
||||
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": @"Select", @"value": @(GBSelect)},
|
||||
@{@"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": @"Rewind", @"value": @(GBRewind)},
|
||||
@{@"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
|
||||
{
|
||||
NSDictionary *item = [self itemForIndexPath:indexPath];
|
||||
|
||||
|
||||
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
|
||||
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.selectionStyle = UITableViewCellSelectionStyleBlue;
|
||||
if (item[@"type"] == typeOptionSubmenu) {
|
||||
if (type == typeOptionSubmenu) {
|
||||
for (NSDictionary *section in item[@"submenu"]) {
|
||||
for (NSDictionary *item in section[@"items"]) {
|
||||
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"]];
|
||||
}
|
||||
}
|
||||
else if (item[@"type"] == typeRadio) {
|
||||
else if (type == typeRadio) {
|
||||
id settingValue = ValueForItem(item);
|
||||
id itemValue = item[@"value"];
|
||||
if (settingValue == itemValue || [settingValue isEqual:itemValue]) {
|
||||
cell.accessoryType = UITableViewCellAccessoryCheckmark;
|
||||
}
|
||||
}
|
||||
else if (item[@"type"] == typeCheck) {
|
||||
else if (type == typeCheck) {
|
||||
UISwitch *button = [[UISwitch alloc] init];
|
||||
cell.accessoryView = button;
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:item[@"pref"]]) {
|
||||
|
@ -747,6 +793,9 @@ static id ValueForItem(NSDictionary *item)
|
|||
__weak typeof(self) weakSelf = self;
|
||||
id block = ^(){
|
||||
[[NSUserDefaults standardUserDefaults] setBool:button.on forKey:item[@"pref"]];
|
||||
if (item[@"block"]) {
|
||||
((void(^)(GBSettingsViewController *))item[@"block"])(self);
|
||||
}
|
||||
unsigned section = [indexPath indexAtPosition:0];
|
||||
UITableViewHeaderFooterView *view = [weakSelf.tableView footerViewForSection: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];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
else if (item[@"type"] == typeDisabled) {
|
||||
else if (type == typeDisabled) {
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
cell.textLabel.textColor = [UIColor separatorColor];
|
||||
|
@ -770,24 +819,34 @@ static id ValueForItem(NSDictionary *item)
|
|||
cell.textLabel.textColor = [UIColor colorWithWhite:0 alpha:0.75];
|
||||
}
|
||||
}
|
||||
else if (item[@"type"] == typeSeparator) {
|
||||
else if (type == typeSeparator) {
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.separatorInset = UIEdgeInsetsZero;
|
||||
}
|
||||
else if (item[@"type"] == typeSlider ||
|
||||
item[@"type"] == typeLightTemp) {
|
||||
else if (type == typeSlider ||
|
||||
type == typeLightTemp ||
|
||||
type == typeTurboSlider) {
|
||||
CGRect rect = cell.contentView.bounds;
|
||||
rect.size.width -= 24;
|
||||
rect.size.height -= 24;
|
||||
rect.origin.x += 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.minimumValue = [item[@"min"] floatValue];
|
||||
slider.maximumValue = [item[@"max"] floatValue];
|
||||
slider.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[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;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
|
@ -798,8 +857,19 @@ static id ValueForItem(NSDictionary *item)
|
|||
}
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
id block = ^(){
|
||||
[[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);
|
||||
|
||||
|
@ -813,13 +883,21 @@ static id ValueForItem(NSDictionary *item)
|
|||
cell.separatorInset = UIEdgeInsetsZero;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)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;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
style = UITableViewStyleInsetGrouped;
|
||||
|
@ -843,7 +921,7 @@ static id ValueForItem(NSDictionary *item)
|
|||
}
|
||||
return indexPath;
|
||||
}
|
||||
else if (item[@"type"] == typeRadio) {
|
||||
else if (type == typeRadio) {
|
||||
if (item[@"setter"]) {
|
||||
((void(^)(id))item[@"setter"])(item[@"value"]);
|
||||
}
|
||||
|
@ -852,7 +930,7 @@ static id ValueForItem(NSDictionary *item)
|
|||
}
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
else if (item[@"type"] == typeBlock) {
|
||||
else if (type == typeBlock) {
|
||||
if (((bool(^)(GBSettingsViewController *))item[@"block"])(self)) {
|
||||
return indexPath;
|
||||
}
|
||||
|
@ -863,11 +941,13 @@ static id ValueForItem(NSDictionary *item)
|
|||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSDictionary *item = [self itemForIndexPath:indexPath];
|
||||
if (item[@"type"] == typeSeparator) {
|
||||
NSString *type = item[@"type"];
|
||||
if (type == typeSeparator) {
|
||||
return 8;
|
||||
}
|
||||
if (item[@"type"] == typeSlider ||
|
||||
item[@"type"] == typeLightTemp) {
|
||||
if (type == typeSlider ||
|
||||
type == typeLightTemp ||
|
||||
type == typeTurboSlider) {
|
||||
return 63;
|
||||
}
|
||||
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
|
||||
|
|
|
@ -1,4 +1,19 @@
|
|||
#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)
|
||||
{
|
||||
|
@ -21,11 +36,15 @@ static inline void temperature_tint(double temperature, double *r, double *g, do
|
|||
}
|
||||
|
||||
@implementation GBSlider
|
||||
{
|
||||
GBSliderStyle _style;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
[self addTarget:self action:@selector(valueChanged) forControlEvents:UIControlEventValueChanged];
|
||||
self.style = GBSliderStyleTemperature;
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -97,6 +116,10 @@ static inline void temperature_tint(double temperature, double *r, double *g, do
|
|||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
bool solarium = false;
|
||||
if (@available(iOS 19.0, *)) {
|
||||
solarium = true;
|
||||
}
|
||||
CGSize size = self.bounds.size;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(2, round(size.height / 2 - 1.5), size.width - 4, 3) cornerRadius:4];
|
||||
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]];
|
||||
}
|
||||
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();
|
||||
CGContextSaveGState(context);
|
||||
[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);
|
||||
CGGradientRef gradient = CGGradientCreateWithColors(colorspace, colorsArray, NULL);
|
||||
unsigned spacing = solarium? 16 : 3;
|
||||
CGContextDrawLinearGradient(context,
|
||||
gradient,
|
||||
(CGPoint){3, 0},
|
||||
(CGPoint){size.width - 3, 0},
|
||||
0);
|
||||
(CGPoint){spacing, 0},
|
||||
(CGPoint){size.width - spacing, 0},
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
CFRelease(gradient);
|
||||
CFRelease(colorsArray);
|
||||
CFRelease(colorspace);
|
||||
|
@ -139,11 +163,44 @@ static inline void temperature_tint(double temperature, double *r, double *g, do
|
|||
green:120 / 255.0
|
||||
blue:130 / 255.0
|
||||
alpha:70 / 255.0] set];
|
||||
[path fill];
|
||||
if (!solarium) {
|
||||
[path fill];
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
[super setFrame:frame];
|
||||
|
|
|
@ -39,8 +39,8 @@
|
|||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
self.view.bounds = CGRectMake(0, 0, 0x300, 0x300);
|
||||
UIView *root = self.view;
|
||||
UIView *root = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0x300, 0x300)];
|
||||
[self.view addSubview:root];
|
||||
for (unsigned i = 0; i < 9; i++) {
|
||||
unsigned x = i % 3;
|
||||
unsigned y = i / 3;
|
||||
|
@ -114,6 +114,12 @@
|
|||
[self presentViewController:controller animated:true completion:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
self.view.subviews.firstObject.frame = [self.view.safeAreaLayoutGuide layoutFrame];
|
||||
}
|
||||
|
||||
- (NSString *)title
|
||||
{
|
||||
return @"Save States";
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
|
||||
- (void)showPopup
|
||||
{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Apply “%@” as the current theme?", _verticalLayout.theme.name]
|
||||
message:nil
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Apply Theme"
|
||||
message:[NSString stringWithFormat:@"Apply “%@” as the current theme?", _verticalLayout.theme.name]
|
||||
preferredStyle:[UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad?
|
||||
UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet];
|
||||
if (false) {
|
||||
|
|
|
@ -21,11 +21,85 @@
|
|||
#import <sys/stat.h>
|
||||
#import <CoreMotion/CoreMotion.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
|
||||
{
|
||||
GB_gameboy_t _gb;
|
||||
GBView *_gbView;
|
||||
dispatch_queue_t _runQueue;
|
||||
|
||||
volatile bool _running;
|
||||
volatile bool _stopping;
|
||||
|
@ -35,6 +109,9 @@
|
|||
bool _swappingROM;
|
||||
bool _skipAutoLoad;
|
||||
|
||||
bool _rapidA, _rapidB;
|
||||
uint8_t _rapidACount, _rapidBCount;
|
||||
|
||||
UIInterfaceOrientation _orientation;
|
||||
GBHorizontalLayout *_horizontalLayoutLeft;
|
||||
GBHorizontalLayout *_horizontalLayoutRight;
|
||||
|
@ -190,6 +267,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_rewind_length(gb, [newValue unsignedIntValue]);
|
||||
} forKey:@"GBRewindLength"];
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_turbo_cap(gb, [newValue doubleValue]);
|
||||
} forKey:@"GBTurboCap"];
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
[[AVAudioSession sharedInstance] setCategory:[newValue isEqual:@"on"]? AVAudioSessionCategoryPlayback : AVAudioSessionCategorySoloAmbient
|
||||
mode:AVAudioSessionModeDefault
|
||||
|
@ -236,6 +316,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
_window.rootViewController = self;
|
||||
[_window makeKeyAndVisible];
|
||||
|
||||
_runQueue = dispatch_queue_create("SameBoy Emulation Queue", NULL);
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-retain-cycles"
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
|
@ -431,9 +513,93 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
|
||||
[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;
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
if ([UIScreen screens].count == 1) {
|
||||
|
@ -526,6 +692,14 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
case GBStart:
|
||||
GB_set_key_state(&_gb, (GB_key_t)gbButton, button.value > 0.25);
|
||||
break;
|
||||
case GBRapidA:
|
||||
_rapidA = button.value > 0.25;
|
||||
_rapidACount = 0;
|
||||
break;
|
||||
case GBRapidB:
|
||||
_rapidB = button.value > 0.25;
|
||||
_rapidBCount = 0;
|
||||
break;
|
||||
case GBTurbo:
|
||||
if (button.value > analogThreshold) {
|
||||
[self setRunMode:GBRunModeTurbo ignoreDynamicSpeed:!button.isAnalog];
|
||||
|
@ -552,7 +726,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
[_backgroundView fadeOverlayOut];
|
||||
}
|
||||
else {
|
||||
if (self.runMode == GBRunModeRewind && _runModeFromController) {
|
||||
if ((self.runMode == GBRunModeRewind || self.runMode == GBRunModePaused) && _runModeFromController) {
|
||||
[self setRunMode:GBRunModeNormal];
|
||||
_runModeFromController = false;
|
||||
}
|
||||
|
@ -673,15 +847,31 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
- (void)saveStateToFile:(NSString *)file
|
||||
{
|
||||
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);
|
||||
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
|
||||
|
@ -704,49 +894,68 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
if (romManager.romFile) {
|
||||
if (!_skipAutoLoad) {
|
||||
// Todo: display errors and warnings
|
||||
if ([romManager.romFile.pathExtension.lowercaseString isEqualToString:@"isx"]) {
|
||||
_romLoaded = GB_load_isx(&_gb, romManager.romFile.fileSystemRepresentation) == 0;
|
||||
bool needsStateLoad = false;
|
||||
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 {
|
||||
_romLoaded = GB_load_rom(&_gb, romManager.romFile.fileSystemRepresentation) == 0;
|
||||
else if (access(romManager.romFile.fileSystemRepresentation, R_OK)) {
|
||||
_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_load_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation);
|
||||
GB_remove_all_cheats(&_gb);
|
||||
GB_load_cheats(&_gb, [GBROMManager sharedManager].cheatsFile.UTF8String, false);
|
||||
if (![self loadStateFromFile:[GBROMManager sharedManager].autosaveStateFile]) {
|
||||
// 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"]);
|
||||
if ([_lastSavedROM isEqual:[GBROMManager sharedManager].currentROM]) {
|
||||
/* 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
|
||||
way. */
|
||||
[self saveStateToFile:[GBROMManager sharedManager].autosaveStateFile];
|
||||
}
|
||||
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_switch_model_and_reset(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]);
|
||||
}
|
||||
GB_rewind_reset(&_gb);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -774,23 +983,48 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
|
||||
- (void)reset
|
||||
{
|
||||
[self stop];
|
||||
_skipAutoLoad = true;
|
||||
GB_reset(&_gb);
|
||||
[self start];
|
||||
UIAlertControllerStyle style = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad?
|
||||
UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet;
|
||||
UIAlertController *menu = [UIAlertController alertControllerWithTitle:@"Reset Emulation?"
|
||||
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
|
||||
{
|
||||
[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
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
- (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"];
|
||||
controller.footer = @"Changing the emulated model will reset the emulator";
|
||||
presentedController = controller;
|
||||
|
||||
GB_model_t currentModel = GB_get_model(&_gb);
|
||||
struct {
|
||||
|
@ -845,7 +1079,12 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
|
||||
- (void)openStates
|
||||
{
|
||||
static __weak UIViewController *presentedController;
|
||||
if (presentedController && self.presentedViewController == presentedController) return;
|
||||
if (![self dismissViewControllerIfSafe]) return;
|
||||
|
||||
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBStatesViewController alloc] init]];
|
||||
presentedController = controller;
|
||||
UIVisualEffect *effect = [UIBlurEffect effectWithStyle:(UIBlurEffectStyle)UIBlurEffectStyleProminent];
|
||||
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
|
||||
effectView.frame = controller.view.bounds;
|
||||
|
@ -863,23 +1102,41 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
|
||||
- (void)openSettings
|
||||
{
|
||||
static __weak UIViewController *presentedController;
|
||||
if (presentedController && self.presentedViewController == presentedController) return;
|
||||
if (![self dismissViewControllerIfSafe]) return;
|
||||
|
||||
UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close"
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(dismissViewController)];
|
||||
[self presentViewController:[GBSettingsViewController settingsViewControllerWithLeftButton:close]
|
||||
UIViewController *controller = [GBSettingsViewController settingsViewControllerWithLeftButton:close];
|
||||
presentedController = controller;
|
||||
[self presentViewController:controller
|
||||
animated:true
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
- (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
|
||||
{
|
||||
static __weak UIViewController *presentedController;
|
||||
if (presentedController && self.presentedViewController == presentedController) return;
|
||||
if (![self dismissViewControllerIfSafe]) return;
|
||||
|
||||
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBCheatsController alloc] initWithGameBoy:&_gb]];
|
||||
presentedController = controller;
|
||||
UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close"
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
|
@ -905,9 +1162,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
- (void)setNeedsUpdateOfSupportedInterfaceOrientations
|
||||
{
|
||||
/* Hack. Some view controllers dismiss without calling the method above. */
|
||||
[super setNeedsUpdateOfSupportedInterfaceOrientations];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self start];
|
||||
[super setNeedsUpdateOfSupportedInterfaceOrientations];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -916,6 +1173,22 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
[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
|
||||
{
|
||||
[self stop];
|
||||
|
@ -923,6 +1196,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
|
||||
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
|
||||
{
|
||||
if (_orientation != UIInterfaceOrientationUnknown && !((1 << orientation) & self.supportedInterfaceOrientations)) return;
|
||||
GBLayout *layout = nil;
|
||||
_orientation = orientation;
|
||||
switch (orientation) {
|
||||
|
@ -977,10 +1251,14 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
32,
|
||||
32);
|
||||
|
||||
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
if (!self.shouldAutorotate && _orientation != UIInterfaceOrientationUnknown) {
|
||||
return 1 << _orientation;
|
||||
}
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
return UIInterfaceOrientationMaskAll;
|
||||
}
|
||||
|
@ -1132,14 +1410,22 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
GB_rewind_pop(&_gb);
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"]) {
|
||||
if (!GB_rewind_pop(&_gb)) {
|
||||
self.runMode = GBRunModePaused;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
if (_runMode == GBRunModeRewind) {
|
||||
self.runMode = GBRunModePaused;
|
||||
}
|
||||
});
|
||||
_rewindOver = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (unsigned i = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindSpeed"]; i--;) {
|
||||
if (!GB_rewind_pop(&_gb)) {
|
||||
self.runMode = GBRunModePaused;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
if (_runMode == GBRunModeRewind) {
|
||||
self.runMode = GBRunModePaused;
|
||||
}
|
||||
});
|
||||
_rewindOver = true;
|
||||
}
|
||||
}
|
||||
|
@ -1168,7 +1454,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
- (UIImage *)imageFromData:(NSData *)data width:(unsigned)width height:(unsigned)height
|
||||
{
|
||||
/* Convert the screenshot to a CGImageRef */
|
||||
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, data.bytes, data.length, NULL);
|
||||
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data);
|
||||
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
|
||||
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
|
||||
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
|
||||
|
@ -1257,7 +1543,9 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
if (_running) return;
|
||||
if (self.presentedViewController) return;
|
||||
_running = true;
|
||||
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
|
||||
dispatch_async(_runQueue, ^{
|
||||
[self run];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
|
@ -1273,6 +1561,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
[_audioLock signal];
|
||||
[_audioLock unlock];
|
||||
}
|
||||
dispatch_sync(_runQueue, ^{});
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.runMode = GBRunModeNormal;
|
||||
[_backgroundView fadeOverlayOut];
|
||||
|
@ -1331,6 +1620,14 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
[_gbView flip];
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1452,7 +1749,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
[url startAccessingSecurityScopedResource];
|
||||
[GBROMManager sharedManager].currentROM =
|
||||
[[GBROMManager sharedManager] importROM:url.path
|
||||
keepOriginal:![url.path hasPrefix:tempDir] && !inPlace];
|
||||
keepOriginal:![url.path hasPrefix:tempDir] && inPlace];
|
||||
[url stopAccessingSecurityScopedResource];
|
||||
}
|
||||
return true;
|
||||
|
@ -1465,7 +1762,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
}
|
||||
[url startAccessingSecurityScopedResource];
|
||||
[[GBROMManager sharedManager] importROM:url.path
|
||||
keepOriginal:![url.path hasPrefix:tempDir] && !inPlace];
|
||||
keepOriginal:![url.path hasPrefix:tempDir] && inPlace];
|
||||
[url stopAccessingSecurityScopedResource];
|
||||
}
|
||||
[self openLibrary];
|
||||
|
@ -1576,9 +1873,7 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
|
||||
if (_runMode == GBRunModeNormal || _runMode == GBRunModeUnderclock || !([[NSUserDefaults standardUserDefaults] boolForKey:@"GBDynamicSpeed"] && !ignoreDynamicSpeed)) {
|
||||
if (_runMode == GBRunModeTurbo) {
|
||||
double multiplier = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBTurboSpeed"];
|
||||
GB_set_turbo_mode(&_gb, multiplier == 1, false);
|
||||
GB_set_clock_multiplier(&_gb, multiplier);
|
||||
GB_set_turbo_mode(&_gb, true, false);
|
||||
}
|
||||
else if (_runMode == GBRunModeUnderclock) {
|
||||
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
|
||||
{
|
||||
UIAlertControllerStyle style = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad?
|
||||
UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet;
|
||||
GBCheckableAlertController *menu = [GBCheckableAlertController alertControllerWithTitle:@"Connect which accessory?"
|
||||
message:nil
|
||||
GBCheckableAlertController *menu = [GBCheckableAlertController alertControllerWithTitle:@"Connect Accessory"
|
||||
message:@"Choose an accessory to connect."
|
||||
preferredStyle:style];
|
||||
[menu addAction:[UIAlertAction actionWithTitle:@"None"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
_printerConnected = false;
|
||||
_currentPrinterImageData = nil;
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_printerButton.alpha = 0;
|
||||
}];
|
||||
[_printerSpinner stopAnimating];
|
||||
GB_disconnect_serial(&_gb);
|
||||
[self disconnectCable];
|
||||
}]];
|
||||
[menu addAction:[UIAlertAction actionWithTitle:@"Game Boy Printer"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
_printerConnected = true;
|
||||
GB_connect_printer(&_gb, printImage, printDone);
|
||||
[self connectPrinter];
|
||||
}]];
|
||||
menu.selectedAction = menu.actions[_printerConnected];
|
||||
[menu addAction:[UIAlertAction actionWithTitle:@"Cancel"
|
||||
|
@ -1874,3 +2181,31 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
}
|
||||
|
||||
@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
|
||||
|
|