Update SameBoy to 1.0.1
SameBoy has unreachable() with parentheses now, as opposed to unreachable without them in nall. To work around the conflict, include SameBoy stuff as the first thing in its respective header, and undef unreachable afterwards.
|
@ -0,0 +1,12 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <Core/gb.h>
|
||||
|
||||
@interface GBAudioClient : NSObject
|
||||
@property (nonatomic, strong) void (^renderBlock)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer);
|
||||
@property (nonatomic, readonly) UInt32 rate;
|
||||
@property (nonatomic, readonly, getter=isPlaying) bool playing;
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
- (id)initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
|
||||
andSampleRate:(UInt32) rate;
|
||||
@end
|
|
@ -0,0 +1,134 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import "GBAudioClient.h"
|
||||
|
||||
static OSStatus render(
|
||||
GBAudioClient *self,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData)
|
||||
|
||||
{
|
||||
GB_sample_t *buffer = (GB_sample_t *)ioData->mBuffers[0].mData;
|
||||
|
||||
self.renderBlock(self.rate, inNumberFrames, buffer);
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
@implementation GBAudioClient
|
||||
{
|
||||
AudioComponentInstance audioUnit;
|
||||
}
|
||||
|
||||
- (id)initWithRendererBlock:(void (^)(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer)) block
|
||||
andSampleRate:(UInt32) rate
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Configure the search parameters to find the default playback output unit
|
||||
// (called the kAudioUnitSubType_RemoteIO on iOS but
|
||||
// kAudioUnitSubType_DefaultOutput on Mac OS X)
|
||||
AudioComponentDescription defaultOutputDescription;
|
||||
defaultOutputDescription.componentType = kAudioUnitType_Output;
|
||||
#if TARGET_OS_IPHONE
|
||||
defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#else
|
||||
defaultOutputDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
#endif
|
||||
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
defaultOutputDescription.componentFlags = 0;
|
||||
defaultOutputDescription.componentFlagsMask = 0;
|
||||
|
||||
// Get the default playback output unit
|
||||
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
|
||||
if (!defaultOutput) {
|
||||
NSLog(@"Can't find default output");
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Create a new unit based on this that we'll use for output
|
||||
OSErr err = AudioComponentInstanceNew(defaultOutput, &audioUnit);
|
||||
if (!audioUnit) {
|
||||
NSLog(@"Error creating unit: %hd", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Set our tone rendering function on the unit
|
||||
AURenderCallbackStruct input;
|
||||
input.inputProc = (void*)render;
|
||||
input.inputProcRefCon = (__bridge void *)(self);
|
||||
err = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&input,
|
||||
sizeof(input));
|
||||
if (err) {
|
||||
NSLog(@"Error setting callback: %hd", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription streamFormat;
|
||||
streamFormat.mSampleRate = rate;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags =
|
||||
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
|
||||
streamFormat.mBytesPerPacket = 4;
|
||||
streamFormat.mFramesPerPacket = 1;
|
||||
streamFormat.mBytesPerFrame = 4;
|
||||
streamFormat.mChannelsPerFrame = 2;
|
||||
streamFormat.mBitsPerChannel = 2 * 8;
|
||||
err = AudioUnitSetProperty (audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&streamFormat,
|
||||
sizeof(AudioStreamBasicDescription));
|
||||
|
||||
if (err) {
|
||||
NSLog(@"Error setting stream format: %hd", err);
|
||||
return nil;
|
||||
}
|
||||
err = AudioUnitInitialize(audioUnit);
|
||||
if (err) {
|
||||
NSLog(@"Error initializing unit: %hd", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.renderBlock = block;
|
||||
_rate = rate;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) start
|
||||
{
|
||||
OSErr err = AudioOutputUnitStart(audioUnit);
|
||||
if (err) {
|
||||
NSLog(@"Error starting unit: %hd", err);
|
||||
return;
|
||||
}
|
||||
_playing = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
-(void) stop
|
||||
{
|
||||
AudioOutputUnitStop(audioUnit);
|
||||
_playing = false;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
[self stop];
|
||||
AudioUnitUninitialize(audioUnit);
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,36 @@
|
|||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#define NSView UIView
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
|
||||
#import <Core/gb.h>
|
||||
|
||||
typedef enum {
|
||||
GB_FRAME_BLENDING_MODE_DISABLED,
|
||||
GB_FRAME_BLENDING_MODE_SIMPLE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE_ODD,
|
||||
} GB_frame_blending_mode_t;
|
||||
|
||||
@interface GBViewBase : NSView
|
||||
{
|
||||
@public
|
||||
GB_gameboy_t *_gb;
|
||||
}
|
||||
|
||||
@property (nonatomic) GB_gameboy_t *gb;
|
||||
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
|
||||
@property (nonatomic, strong) NSView *internalView;
|
||||
- (void) flip;
|
||||
- (uint32_t *) pixels;
|
||||
- (void)screenSizeChanged;
|
||||
- (void) createInternalView;
|
||||
- (uint32_t *)currentBuffer;
|
||||
- (uint32_t *)previousBuffer;
|
||||
- (instancetype)mirroredView;
|
||||
@end
|
|
@ -0,0 +1,121 @@
|
|||
#import "GBViewBase.h"
|
||||
|
||||
@implementation GBViewBase
|
||||
{
|
||||
uint32_t *_imageBuffers[3];
|
||||
unsigned _currentBuffer;
|
||||
GB_frame_blending_mode_t _frameBlendingMode;
|
||||
bool _oddFrame;
|
||||
GBViewBase *_parent;
|
||||
__weak GBViewBase *_child;
|
||||
}
|
||||
|
||||
- (void)screenSizeChanged
|
||||
{
|
||||
if (_parent) return;
|
||||
if (_imageBuffers[0]) free(_imageBuffers[0]);
|
||||
if (_imageBuffers[1]) free(_imageBuffers[1]);
|
||||
if (_imageBuffers[2]) free(_imageBuffers[2]);
|
||||
|
||||
size_t buffer_size = sizeof(_imageBuffers[0][0]) * GB_get_screen_width(_gb) * GB_get_screen_height(_gb);
|
||||
|
||||
_imageBuffers[0] = calloc(1, buffer_size);
|
||||
_imageBuffers[1] = calloc(1, buffer_size);
|
||||
_imageBuffers[2] = calloc(1, buffer_size);
|
||||
}
|
||||
|
||||
- (void)flip
|
||||
{
|
||||
if (_parent) return;
|
||||
_currentBuffer = (_currentBuffer + 1) % self.numberOfBuffers;
|
||||
_oddFrame = GB_is_odd_frame(_gb);
|
||||
[_child flip];
|
||||
}
|
||||
|
||||
- (unsigned) numberOfBuffers
|
||||
{
|
||||
assert(!_parent);
|
||||
return _frameBlendingMode? 3 : 2;
|
||||
}
|
||||
|
||||
- (void) createInternalView
|
||||
{
|
||||
assert(false && "createInternalView must not be inherited");
|
||||
}
|
||||
|
||||
- (uint32_t *)currentBuffer
|
||||
{
|
||||
if (GB_unlikely(_parent)) {
|
||||
return [_parent currentBuffer];
|
||||
}
|
||||
return _imageBuffers[_currentBuffer];
|
||||
}
|
||||
|
||||
- (uint32_t *)previousBuffer
|
||||
{
|
||||
if (GB_unlikely(_parent)) {
|
||||
return [_parent previousBuffer];
|
||||
}
|
||||
return _imageBuffers[(_currentBuffer + 2) % self.numberOfBuffers];
|
||||
}
|
||||
|
||||
- (uint32_t *) pixels
|
||||
{
|
||||
assert(!_parent);
|
||||
return _imageBuffers[(_currentBuffer + 1) % self.numberOfBuffers];
|
||||
}
|
||||
|
||||
- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode
|
||||
{
|
||||
_frameBlendingMode = frameBlendingMode;
|
||||
[self setNeedsDisplay];
|
||||
[_child setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (GB_frame_blending_mode_t)frameBlendingMode
|
||||
{
|
||||
if (GB_unlikely(_parent)) {
|
||||
return [_parent frameBlendingMode];
|
||||
}
|
||||
if (_frameBlendingMode == GB_FRAME_BLENDING_MODE_ACCURATE) {
|
||||
if (!_gb || GB_is_sgb(_gb)) {
|
||||
return GB_FRAME_BLENDING_MODE_SIMPLE;
|
||||
}
|
||||
return _oddFrame ? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN;
|
||||
}
|
||||
return _frameBlendingMode;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_parent) return;
|
||||
free(_imageBuffers[0]);
|
||||
free(_imageBuffers[1]);
|
||||
free(_imageBuffers[2]);
|
||||
}
|
||||
|
||||
#if !TARGET_OS_IPHONE
|
||||
- (void)setNeedsDisplay
|
||||
{
|
||||
[self setNeedsDisplay:true];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)setGb:(GB_gameboy_t *)gb
|
||||
{
|
||||
assert(!_parent);
|
||||
_gb = gb;
|
||||
if (_child) {
|
||||
_child->_gb = gb;
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)mirroredView
|
||||
{
|
||||
if (_child) return _child;
|
||||
GBViewBase *ret = [[self.class alloc] initWithFrame:self.bounds];
|
||||
ret->_parent = self;
|
||||
ret->_gb = _gb;
|
||||
return _child = ret;
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,11 @@
|
|||
#import <TargetConditionals.h>
|
||||
#import <MetalKit/MetalKit.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#import "../iOS/GBView.h"
|
||||
#else
|
||||
#import "../Cocoa/GBView.h"
|
||||
#endif
|
||||
|
||||
@interface GBViewMetal : GBView<MTKViewDelegate>
|
||||
+ (bool) isSupported;
|
||||
@end
|
|
@ -0,0 +1,266 @@
|
|||
#import <CoreImage/CoreImage.h>
|
||||
#import "GBViewMetal.h"
|
||||
#pragma clang diagnostic ignored "-Wpartial-availability"
|
||||
#if !TARGET_OS_IPHONE
|
||||
#import "../Cocoa/NSObject+DefaultsObserver.h"
|
||||
#endif
|
||||
|
||||
static const vector_float2 rect[] =
|
||||
{
|
||||
{-1, -1},
|
||||
{ 1, -1},
|
||||
{-1, 1},
|
||||
{ 1, 1},
|
||||
};
|
||||
|
||||
@implementation GBViewMetal
|
||||
{
|
||||
id<MTLDevice> _device;
|
||||
id<MTLTexture> _texture, _previousTexture;
|
||||
id<MTLBuffer> _vertices;
|
||||
id<MTLRenderPipelineState> _pipelineState;
|
||||
id<MTLCommandQueue> _commandQueue;
|
||||
id<MTLBuffer> _frameBlendingModeBuffer;
|
||||
id<MTLBuffer> _outputResolutionBuffer;
|
||||
vector_float2 _outputResolution;
|
||||
id<MTLCommandBuffer> _commandBuffer;
|
||||
bool _waitedForFrame;
|
||||
_Atomic unsigned _pendingFrames;
|
||||
}
|
||||
|
||||
+ (bool)isSupported
|
||||
{
|
||||
#if TARGET_OS_IPHONE
|
||||
return true;
|
||||
#else
|
||||
if (MTLCopyAllDevices) {
|
||||
return [MTLCopyAllDevices() count];
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
- (void) allocateTextures
|
||||
{
|
||||
if (!_device) return;
|
||||
|
||||
MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init];
|
||||
|
||||
texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
|
||||
|
||||
texture_descriptor.width = GB_get_screen_width(self.gb);
|
||||
texture_descriptor.height = GB_get_screen_height(self.gb);
|
||||
|
||||
_texture = [_device newTextureWithDescriptor:texture_descriptor];
|
||||
_previousTexture = [_device newTextureWithDescriptor:texture_descriptor];
|
||||
|
||||
}
|
||||
|
||||
- (void)createInternalView
|
||||
{
|
||||
MTKView *view = [[MTKView alloc] initWithFrame:self.frame device:(_device = MTLCreateSystemDefaultDevice())];
|
||||
view.delegate = self;
|
||||
self.internalView = view;
|
||||
view.paused = true;
|
||||
view.enableSetNeedsDisplay = true;
|
||||
view.framebufferOnly = false;
|
||||
|
||||
_vertices = [_device newBufferWithBytes:rect
|
||||
length:sizeof(rect)
|
||||
options:MTLResourceStorageModeShared];
|
||||
|
||||
static const GB_frame_blending_mode_t default_blending_mode = GB_FRAME_BLENDING_MODE_DISABLED;
|
||||
_frameBlendingModeBuffer = [_device newBufferWithBytes:&default_blending_mode
|
||||
length:sizeof(default_blending_mode)
|
||||
options:MTLResourceStorageModeShared];
|
||||
|
||||
_outputResolutionBuffer = [_device newBufferWithBytes:&_outputResolution
|
||||
length:sizeof(_outputResolution)
|
||||
options:MTLResourceStorageModeShared];
|
||||
|
||||
_outputResolution = (simd_float2){view.drawableSize.width, view.drawableSize.height};
|
||||
/* TODO: NSObject+DefaultsObserver can replace the less flexible `addDefaultObserver` in iOS */
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadShader) name:@"GBFilterChanged" object:nil];
|
||||
[self loadShader];
|
||||
#else
|
||||
[self observeStandardDefaultsKey:@"GBFilter" selector:@selector(loadShader)];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)loadShader
|
||||
{
|
||||
NSError *error = nil;
|
||||
NSString *shader_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"MasterShader"
|
||||
ofType:@"metal"
|
||||
inDirectory:@"Shaders"]
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil];
|
||||
|
||||
NSString *shader_name = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
|
||||
NSString *scaler_source = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:shader_name
|
||||
ofType:@"fsh"
|
||||
inDirectory:@"Shaders"]
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil];
|
||||
|
||||
shader_source = [shader_source stringByReplacingOccurrencesOfString:@"{filter}"
|
||||
withString:scaler_source];
|
||||
|
||||
MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
|
||||
options.fastMathEnabled = true;
|
||||
id<MTLLibrary> library = [_device newLibraryWithSource:shader_source
|
||||
options:options
|
||||
error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error: %@", error);
|
||||
if (!library) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLFunction> vertex_function = [library newFunctionWithName:@"vertex_shader"];
|
||||
id<MTLFunction> fragment_function = [library newFunctionWithName:@"fragment_shader"];
|
||||
|
||||
// Set up a descriptor for creating a pipeline state object
|
||||
MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||
pipeline_state_descriptor.vertexFunction = vertex_function;
|
||||
pipeline_state_descriptor.fragmentFunction = fragment_function;
|
||||
pipeline_state_descriptor.colorAttachments[0].pixelFormat = ((MTKView *)self.internalView).colorPixelFormat;
|
||||
|
||||
error = nil;
|
||||
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
|
||||
error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Failed to created pipeline state, error %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
_commandQueue = [_device newCommandQueue];
|
||||
}
|
||||
|
||||
- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
|
||||
{
|
||||
_outputResolution = (vector_float2){size.width, size.height};
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[(MTKView *)self.internalView draw];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)drawInMTKView:(MTKView *)view
|
||||
{
|
||||
#if !TARGET_OS_IPHONE
|
||||
if (!(view.window.occlusionState & NSWindowOcclusionStateVisible)) return;
|
||||
#endif
|
||||
if (!self.gb) return;
|
||||
if (_texture.width != GB_get_screen_width(self.gb) ||
|
||||
_texture.height != GB_get_screen_height(self.gb)) {
|
||||
[self allocateTextures];
|
||||
}
|
||||
|
||||
MTLRegion region = {
|
||||
{0, 0, 0}, // MTLOrigin
|
||||
{_texture.width, _texture.height, 1} // MTLSize
|
||||
};
|
||||
|
||||
/* Don't start rendering if the previous frame hasn't finished yet. Either wait, or skip the frame */
|
||||
if (_commandBuffer && _commandBuffer.status != MTLCommandBufferStatusCompleted) {
|
||||
if (_waitedForFrame) return;
|
||||
[_commandBuffer waitUntilCompleted];
|
||||
_waitedForFrame = true;
|
||||
}
|
||||
else {
|
||||
_waitedForFrame = false;
|
||||
}
|
||||
|
||||
GB_frame_blending_mode_t mode = [self frameBlendingMode];
|
||||
|
||||
[_texture replaceRegion:region
|
||||
mipmapLevel:0
|
||||
withBytes:[self currentBuffer]
|
||||
bytesPerRow:_texture.width * 4];
|
||||
|
||||
if (mode) {
|
||||
[_previousTexture replaceRegion:region
|
||||
mipmapLevel:0
|
||||
withBytes:[self previousBuffer]
|
||||
bytesPerRow:_texture.width * 4];
|
||||
}
|
||||
|
||||
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
|
||||
_commandBuffer = [_commandQueue commandBuffer];
|
||||
|
||||
if (renderPassDescriptor) {
|
||||
*(GB_frame_blending_mode_t *)[_frameBlendingModeBuffer contents] = mode;
|
||||
*(vector_float2 *)[_outputResolutionBuffer contents] = _outputResolution;
|
||||
|
||||
id<MTLRenderCommandEncoder> renderEncoder =
|
||||
[_commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
||||
|
||||
[renderEncoder setViewport:(MTLViewport){0.0, 0.0,
|
||||
_outputResolution.x,
|
||||
_outputResolution.y,
|
||||
-1.0, 1.0}];
|
||||
|
||||
[renderEncoder setRenderPipelineState:_pipelineState];
|
||||
|
||||
[renderEncoder setVertexBuffer:_vertices
|
||||
offset:0
|
||||
atIndex:0];
|
||||
|
||||
[renderEncoder setFragmentBuffer:_frameBlendingModeBuffer
|
||||
offset:0
|
||||
atIndex:0];
|
||||
|
||||
[renderEncoder setFragmentBuffer:_outputResolutionBuffer
|
||||
offset:0
|
||||
atIndex:1];
|
||||
|
||||
[renderEncoder setFragmentTexture:_texture
|
||||
atIndex:0];
|
||||
|
||||
[renderEncoder setFragmentTexture:_previousTexture
|
||||
atIndex:1];
|
||||
|
||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
|
||||
vertexStart:0
|
||||
vertexCount:4];
|
||||
|
||||
[renderEncoder endEncoding];
|
||||
|
||||
[_commandBuffer presentDrawable:view.currentDrawable];
|
||||
}
|
||||
|
||||
|
||||
[_commandBuffer commit];
|
||||
}
|
||||
|
||||
- (void)flip
|
||||
{
|
||||
[super flip];
|
||||
if (_pendingFrames == 2) return;
|
||||
_pendingFrames++;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[(MTKView *)self.internalView draw];
|
||||
_pendingFrames--;
|
||||
});
|
||||
}
|
||||
|
||||
#if !TARGET_OS_IPHONE
|
||||
- (NSImage *)renderToImage
|
||||
{
|
||||
CIImage *ciImage = [CIImage imageWithMTLTexture:[[(MTKView *)self.internalView currentDrawable] texture]
|
||||
options:@{
|
||||
kCIImageColorSpace: (__bridge_transfer id)CGColorSpaceCreateDeviceRGB(),
|
||||
kCIImageProperties: [NSNull null]
|
||||
}];
|
||||
ciImage = [ciImage imageByApplyingTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1),
|
||||
0, ciImage.extent.size.height)];
|
||||
CIContext *context = [CIContext context];
|
||||
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
|
||||
NSImage *ret = [[NSImage alloc] initWithCGImage:cgImage size:self.internalView.bounds.size];
|
||||
CGImageRelease(cgImage);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
|
@ -89,7 +89,7 @@ The values of memory-mapped registers should be written 'as-is' to memory as if
|
|||
* Unused register bits have Don't-Care values which should be ignored
|
||||
* If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode
|
||||
* Bit 2 determines DMG mode. A value of 0x04 usually denotes DMG mode, while a value of `0x80` usually denotes CGB mode.
|
||||
* Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect
|
||||
* Object priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect
|
||||
* If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored.
|
||||
* BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid.
|
||||
* Implementations should not start a serial transfer when writing the value of SB
|
||||
|
@ -176,6 +176,48 @@ The length of this block is 0x11 bytes long and it follows the following structu
|
|||
| 0x0E | Scheduled alarm time days (16-bit) |
|
||||
| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) |
|
||||
|
||||
#### TPP1 block
|
||||
The TPP1 block uses the `'TPP1'` identifier, and is an optional block that is used while emulating a TPP1 cartridge to store RTC information. This block can be omitted if the ROM header does not specify the inclusion of a RTC.
|
||||
|
||||
The length of this block is 0x11 bytes long and it follows the following structure:
|
||||
|
||||
| Offset | Content |
|
||||
|--------|-------------------------------------------------------|
|
||||
| 0x00 | UNIX timestamp at the time of the save state (64-bit) |
|
||||
| 0x08 | The current RTC data (4 bytes) |
|
||||
| 0x0C | The latched RTC data (4 bytes) |
|
||||
| 0x10 | The value of the MR4 register (8-bits) |
|
||||
|
||||
|
||||
#### MBC7 block
|
||||
The MBC7 block uses the `'MBC7'` identifier, and is an optional block that is used while emulating an MBC7 cartridge to store the EEPROM communication state and motion control state.
|
||||
|
||||
The length of this block is 0xA bytes long and it follows the following structure:
|
||||
|
||||
| Offset | Content |
|
||||
|--------|-------------------------------------------------------|
|
||||
| 0x00 | Flags (8-bits) |
|
||||
| 0x01 | Argument bits left (8-bits) |
|
||||
| 0x02 | Current EEPROM command (16-bits) |
|
||||
| 0x04 | Pending bits to read (16-bits) |
|
||||
| 0x06 | Latched gyro X value (16-bits) |
|
||||
| 0x08 | Latched gyro Y value (16-bits) |
|
||||
|
||||
The meaning of the individual bits in flags are:
|
||||
* Bit 0: Latch ready; set after writing `0x55` to `0xAX0X` and reset after writing `0xAA` to `0xAX1X`
|
||||
* Bit 1: EEPROM DO line
|
||||
* Bit 2: EEPROM DI line
|
||||
* Bit 3: EEPROM CLK line
|
||||
* Bit 4: EEPROM CS line
|
||||
* Bit 5: EEPROM write enable; set after an `EWEN` command, reset after an `EWDS` command
|
||||
* Bits 6-7: Unused.
|
||||
|
||||
The current EEPROM command field has bits pushed to its LSB first, padded with zeros. For example, if the ROM clocked a single `1` bit, this field should contain `0b1`; if the ROM later clocks a `0` bit, this field should contain `0b10`.
|
||||
|
||||
If the currently transmitted command has an argument, the "Argument bits left" field should contain the number argument bits remaining. Otherwise, it should contain 0.
|
||||
|
||||
The "Pending bits to read" field contains the pending bits waiting to be shifted into the DO signal, MSB first, padded with ones.
|
||||
|
||||
#### SGB block
|
||||
|
||||
The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing.
|
||||
|
|
Before Width: | Height: | Size: 479 B After Width: | Height: | Size: 477 B |
|
@ -1,2 +1,2 @@
|
|||
AGB EQU 1
|
||||
include "cgb_boot.asm"
|
||||
DEF AGB = 1
|
||||
include "cgb_boot.asm"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
DEF CGB0 = 1
|
||||
include "cgb_boot.asm"
|
|
@ -1,2 +1,2 @@
|
|||
FAST EQU 1
|
||||
include "cgb_boot.asm"
|
||||
DEF FAST = 1
|
||||
include "cgb_boot.asm"
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
; SameBoy DMG bootstrap ROM
|
||||
; Todo: use friendly names for HW registers instead of magic numbers
|
||||
SECTION "BootCode", ROM0[$0]
|
||||
|
||||
include "sameboot.inc"
|
||||
|
||||
SECTION "BootCode", ROM0[$0000]
|
||||
Start:
|
||||
; Init stack pointer
|
||||
ld sp, $fffe
|
||||
ld sp, $FFFE
|
||||
|
||||
; Clear memory VRAM
|
||||
ld hl, $8000
|
||||
ld hl, _VRAM
|
||||
xor a
|
||||
|
||||
.clearVRAMLoop
|
||||
ldi [hl], a
|
||||
|
@ -14,24 +17,25 @@ Start:
|
|||
jr z, .clearVRAMLoop
|
||||
|
||||
; Init Audio
|
||||
ld a, $80
|
||||
ldh [$26], a
|
||||
ldh [$11], a
|
||||
ld a, $f3
|
||||
ldh [$12], a
|
||||
ldh [$25], a
|
||||
ld a, AUDENA_ON
|
||||
ldh [rNR52], a
|
||||
assert AUDENA_ON == AUDLEN_DUTY_50
|
||||
ldh [rNR11], a
|
||||
ld a, $F3
|
||||
ldh [rNR12], a ; Envelope $F, decreasing, sweep $3
|
||||
ldh [rNR51], a ; Channels 1+2+3+4 left, channels 1+2 right
|
||||
ld a, $77
|
||||
ldh [$24], a
|
||||
ldh [rNR50], a ; Volume $7, left and right
|
||||
|
||||
; Init BG palette
|
||||
ld a, $54
|
||||
ldh [$47], a
|
||||
ld a, %01_01_01_00
|
||||
ldh [rBGP], a
|
||||
|
||||
; Load logo from ROM.
|
||||
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
|
||||
; Tiles are ordered left to right, top to bottom.
|
||||
ld de, $104 ; Logo start
|
||||
ld hl, $8010 ; This is where we load the tiles in VRAM
|
||||
ld de, NintendoLogo
|
||||
ld hl, _VRAM + $10 ; This is where we load the tiles in VRAM
|
||||
|
||||
.loadLogoLoop
|
||||
ld a, [de] ; Read 2 rows
|
||||
|
@ -40,88 +44,92 @@ Start:
|
|||
call DoubleBitsAndWriteRow
|
||||
inc de
|
||||
ld a, e
|
||||
xor $34 ; End of logo
|
||||
xor LOW(NintendoLogoEnd)
|
||||
jr nz, .loadLogoLoop
|
||||
|
||||
; Load trademark symbol
|
||||
ld de, TrademarkSymbol
|
||||
ld c,$08
|
||||
ld c, TrademarkSymbolEnd - TrademarkSymbol
|
||||
.loadTrademarkSymbolLoop:
|
||||
ld a,[de]
|
||||
ld a, [de]
|
||||
inc de
|
||||
ldi [hl],a
|
||||
ldi [hl], a
|
||||
inc hl
|
||||
dec c
|
||||
jr nz, .loadTrademarkSymbolLoop
|
||||
|
||||
; Set up tilemap
|
||||
ld a,$19 ; Trademark symbol
|
||||
ld [$9910], a ; ... put in the superscript position
|
||||
ld hl,$992f ; Bottom right corner of the logo
|
||||
ld c,$c ; Tiles in a logo row
|
||||
ld a, $19 ; Trademark symbol tile ID
|
||||
ld [_SCRN0 + 8 * SCRN_VX_B + 16], a ; ... put in the superscript position
|
||||
ld hl, _SCRN0 + 9 * SCRN_VX_B + 15 ; Bottom right corner of the logo
|
||||
ld c, 12 ; Tiles in a logo row
|
||||
.tilemapLoop
|
||||
dec a
|
||||
jr z, .tilemapDone
|
||||
ldd [hl], a
|
||||
dec c
|
||||
jr nz, .tilemapLoop
|
||||
ld l,$0f ; Jump to top row
|
||||
ld l, $0F ; Jump to top row
|
||||
jr .tilemapLoop
|
||||
.tilemapDone
|
||||
|
||||
ld a, 30
|
||||
ldh [$ff42], a
|
||||
|
||||
; Turn on LCD
|
||||
ld a, $91
|
||||
ldh [$40], a
|
||||
ldh [rSCY], a
|
||||
|
||||
ld d, (-119) & $FF
|
||||
; Turn on LCD
|
||||
ld a, LCDCF_ON | LCDCF_BLK01 | LCDCF_BGON
|
||||
ldh [rLCDC], a
|
||||
|
||||
ld d, LOW(-119)
|
||||
ld c, 15
|
||||
|
||||
|
||||
.animate
|
||||
call WaitFrame
|
||||
ld a, d
|
||||
sra a
|
||||
sra a
|
||||
ldh [$ff42], a
|
||||
ldh [rSCY], a
|
||||
ld a, d
|
||||
add c
|
||||
ld d, a
|
||||
ld a, c
|
||||
cp 8
|
||||
jr nz, .noPaletteChange
|
||||
ld a, $A8
|
||||
ldh [$47], a
|
||||
ld a, %10_10_10_00
|
||||
ldh [rBGP], a
|
||||
.noPaletteChange
|
||||
dec c
|
||||
jr nz, .animate
|
||||
ld a, $fc
|
||||
ldh [$47], a
|
||||
|
||||
ld a, %11_11_11_00
|
||||
ldh [rBGP], a
|
||||
|
||||
; Play first sound
|
||||
ld a, $83
|
||||
call PlaySound
|
||||
ld b, 5
|
||||
call WaitBFrames
|
||||
; Play second sound
|
||||
ld a, $c1
|
||||
ld a, $C1
|
||||
call PlaySound
|
||||
|
||||
|
||||
|
||||
|
||||
; Wait ~1 second
|
||||
ld b, 60
|
||||
call WaitBFrames
|
||||
|
||||
|
||||
; Set registers to match the original DMG boot
|
||||
ld hl, $01B0
|
||||
IF DEF(MGB)
|
||||
lb hl, BOOTUP_A_MGB, %10110000
|
||||
ELSE
|
||||
lb hl, BOOTUP_A_DMG, %10110000
|
||||
ENDC
|
||||
push hl
|
||||
pop af
|
||||
ld hl, $014D
|
||||
ld bc, $0013
|
||||
ld de, $00D8
|
||||
|
||||
ld hl, HeaderChecksum
|
||||
lb bc, 0, LOW(rNR13) ; $0013
|
||||
lb de, 0, $D8 ; $00D8
|
||||
|
||||
; Boot the game
|
||||
jp BootGame
|
||||
|
||||
|
@ -148,7 +156,7 @@ DoubleBitsAndWriteRow:
|
|||
|
||||
WaitFrame:
|
||||
push hl
|
||||
ld hl, $FF0F
|
||||
ld hl, rIF
|
||||
res 0, [hl]
|
||||
.wait
|
||||
bit 0, [hl]
|
||||
|
@ -163,15 +171,26 @@ WaitBFrames:
|
|||
ret
|
||||
|
||||
PlaySound:
|
||||
ldh [$13], a
|
||||
ld a, $87
|
||||
ldh [$14], a
|
||||
ldh [rNR13], a
|
||||
ld a, AUDHIGH_RESTART | $7
|
||||
ldh [rNR14], a
|
||||
ret
|
||||
|
||||
|
||||
TrademarkSymbol:
|
||||
db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
|
||||
pusho
|
||||
opt b.X
|
||||
db %..XXXX..
|
||||
db %.X....X.
|
||||
db %X.XXX..X
|
||||
db %X.X..X.X
|
||||
db %X.XXX..X
|
||||
db %X.X..X.X
|
||||
db %.X....X.
|
||||
db %..XXXX..
|
||||
popo
|
||||
TrademarkSymbolEnd:
|
||||
|
||||
SECTION "BootGame", ROM0[$fe]
|
||||
SECTION "BootGame", ROM0[$00FE]
|
||||
BootGame:
|
||||
ldh [$50], a
|
||||
ldh [rBANK], a ; unmap boot ROM
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
DEF MGB = 1
|
||||
include "dmg_boot.asm"
|
|
@ -5,7 +5,7 @@
|
|||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
void opts(uint8_t byte, uint8_t *options)
|
||||
static void opts(uint8_t byte, uint8_t *options)
|
||||
{
|
||||
*(options++) = byte | ((byte << 1) & 0xff);
|
||||
*(options++) = byte & (byte << 1);
|
||||
|
@ -13,7 +13,7 @@ void opts(uint8_t byte, uint8_t *options)
|
|||
*(options++) = byte & (byte >> 1);
|
||||
}
|
||||
|
||||
void write_all(int fd, const void *buf, size_t count) {
|
||||
static void write_all(int fd, const void *buf, size_t count) {
|
||||
while (count) {
|
||||
ssize_t written = write(fd, buf, count);
|
||||
if (written < 0) {
|
||||
|
@ -25,7 +25,7 @@ void write_all(int fd, const void *buf, size_t count) {
|
|||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
int main(void)
|
||||
{
|
||||
static uint8_t source[0x4000];
|
||||
size_t size = read(STDIN_FILENO, &source, sizeof(source));
|
||||
|
@ -87,7 +87,7 @@ int main()
|
|||
prev[1] = byte;
|
||||
if (bits >= 8) {
|
||||
uint8_t outctl = control >> (bits - 8);
|
||||
assert(outctl != 1);
|
||||
assert(outctl != 1); // 1 is reserved as the end byte
|
||||
write_all(STDOUT_FILENO, &outctl, 1);
|
||||
write_all(STDOUT_FILENO, literals, literals_size);
|
||||
bits -= 8;
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
IF !DEF(SAMEBOY_INC)
|
||||
DEF SAMEBOY_INC EQU 1
|
||||
|
||||
include "hardware.inc"
|
||||
|
||||
DEF rKEY0 EQU $FF4C
|
||||
DEF rBANK EQU $FF50
|
||||
|
||||
DEF rJOYP EQU rP1
|
||||
|
||||
|
||||
MACRO lb ; r16, high, low
|
||||
ld \1, LOW(\2) << 8 | LOW(\3)
|
||||
ENDM
|
||||
|
||||
|
||||
MACRO header_section ; name, address
|
||||
PUSHS
|
||||
SECTION "\1", ROM0[\2]
|
||||
\1:
|
||||
POPS
|
||||
ENDM
|
||||
header_section EntryPoint, $0100
|
||||
header_section NintendoLogo, $0104
|
||||
header_section NintendoLogoEnd, $0134
|
||||
header_section Title, $0134
|
||||
header_section ManufacturerCode, $013F
|
||||
header_section CGBFlag, $0143
|
||||
header_section NewLicenseeCode, $0144
|
||||
header_section SGBFlag, $0146
|
||||
header_section CartridgeType, $0147
|
||||
header_section ROMSize, $0148
|
||||
header_section RAMSize, $0149
|
||||
header_section DestinationCode, $014A
|
||||
header_section OldLicenseeCode, $014B
|
||||
header_section MaskRomVersion, $014C
|
||||
header_section HeaderChecksum, $014D
|
||||
header_section GlobalChecksum, $014E
|
||||
|
||||
ENDC
|
|
@ -1,2 +1,2 @@
|
|||
SGB2 EQU 1
|
||||
include "sgb_boot.asm"
|
||||
DEF SGB2 = 1
|
||||
include "sgb_boot.asm"
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
; SameBoy SGB bootstrap ROM
|
||||
; Todo: use friendly names for HW registers instead of magic numbers
|
||||
SECTION "BootCode", ROM0[$0]
|
||||
|
||||
include "sameboot.inc"
|
||||
|
||||
SECTION "BootCode", ROM0[$0000]
|
||||
Start:
|
||||
; Init stack pointer
|
||||
ld sp, $fffe
|
||||
ld sp, $FFFE
|
||||
|
||||
; Clear memory VRAM
|
||||
ld hl, $8000
|
||||
ld hl, _VRAM
|
||||
xor a
|
||||
|
||||
.clearVRAMLoop
|
||||
ldi [hl], a
|
||||
|
@ -14,24 +17,25 @@ Start:
|
|||
jr z, .clearVRAMLoop
|
||||
|
||||
; Init Audio
|
||||
ld a, $80
|
||||
ldh [$26], a
|
||||
ldh [$11], a
|
||||
ld a, $f3
|
||||
ldh [$12], a
|
||||
ldh [$25], a
|
||||
ld a, AUDENA_ON
|
||||
ldh [rNR52], a
|
||||
assert AUDENA_ON == AUDLEN_DUTY_50
|
||||
ldh [rNR11], a
|
||||
ld a, $F3
|
||||
ldh [rNR12], a ; Envelope $F, decreasing, sweep $3
|
||||
ldh [rNR51], a ; Channels 1+2+3+4 left, channels 1+2 right
|
||||
ld a, $77
|
||||
ldh [$24], a
|
||||
ldh [rNR50], a ; Volume $7, left and right
|
||||
|
||||
; Init BG palette to white
|
||||
ld a, $0
|
||||
ldh [$47], a
|
||||
ld a, %00_00_00_00
|
||||
ldh [rBGP], a
|
||||
|
||||
; Load logo from ROM.
|
||||
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
|
||||
; Tiles are ordered left to right, top to bottom.
|
||||
ld de, $104 ; Logo start
|
||||
ld hl, $8010 ; This is where we load the tiles in VRAM
|
||||
ld de, NintendoLogo
|
||||
ld hl, _VRAM + $10 ; This is where we load the tiles in VRAM
|
||||
|
||||
.loadLogoLoop
|
||||
ld a, [de] ; Read 2 rows
|
||||
|
@ -40,111 +44,124 @@ Start:
|
|||
call DoubleBitsAndWriteRow
|
||||
inc de
|
||||
ld a, e
|
||||
xor $34 ; End of logo
|
||||
xor LOW(NintendoLogoEnd)
|
||||
jr nz, .loadLogoLoop
|
||||
|
||||
; Load trademark symbol
|
||||
ld de, TrademarkSymbol
|
||||
ld c,$08
|
||||
ld c, TrademarkSymbolEnd - TrademarkSymbol
|
||||
.loadTrademarkSymbolLoop:
|
||||
ld a,[de]
|
||||
ld a, [de]
|
||||
inc de
|
||||
ldi [hl],a
|
||||
ldi [hl], a
|
||||
inc hl
|
||||
dec c
|
||||
jr nz, .loadTrademarkSymbolLoop
|
||||
|
||||
; Set up tilemap
|
||||
ld a,$19 ; Trademark symbol
|
||||
ld [$9910], a ; ... put in the superscript position
|
||||
ld hl,$992f ; Bottom right corner of the logo
|
||||
ld c,$c ; Tiles in a logo row
|
||||
ld a, $19 ; Trademark symbol tile ID
|
||||
ld [_SCRN0 + 8 * SCRN_VX_B + 16], a ; ... put in the superscript position
|
||||
ld hl, _SCRN0 + 9 * SCRN_VX_B + 15 ; Bottom right corner of the logo
|
||||
ld c, 12 ; Tiles in a logo row
|
||||
.tilemapLoop
|
||||
dec a
|
||||
jr z, .tilemapDone
|
||||
ldd [hl], a
|
||||
dec c
|
||||
jr nz, .tilemapLoop
|
||||
ld l,$0f ; Jump to top row
|
||||
ld l, $0F ; Jump to top row
|
||||
jr .tilemapLoop
|
||||
.tilemapDone
|
||||
|
||||
; Turn on LCD
|
||||
ld a, $91
|
||||
ldh [$40], a
|
||||
ld a, LCDCF_ON | LCDCF_BLK01 | LCDCF_BGON
|
||||
ldh [rLCDC], a
|
||||
|
||||
ld a, $F1 ; Packet magic, increases by 2 for every packet
|
||||
ldh [hCommand], a
|
||||
ld hl, NintendoLogo ; Header start
|
||||
|
||||
ld a, $f1 ; Packet magic, increases by 2 for every packet
|
||||
ldh [$80], a
|
||||
ld hl, $104 ; Header start
|
||||
|
||||
xor a
|
||||
ld c, a ; JOYP
|
||||
|
||||
.sendCommand
|
||||
xor a
|
||||
ld [c], a
|
||||
ldh [c], a
|
||||
ld a, $30
|
||||
ld [c], a
|
||||
|
||||
ldh a, [$80]
|
||||
ldh [c], a
|
||||
|
||||
ldh a, [hCommand]
|
||||
call SendByte
|
||||
push hl
|
||||
ld b, $e
|
||||
|
||||
ld b, 14
|
||||
ld d, 0
|
||||
|
||||
.checksumLoop
|
||||
call ReadHeaderByte
|
||||
add d
|
||||
ld d, a
|
||||
dec b
|
||||
jr nz, .checksumLoop
|
||||
|
||||
|
||||
; Send checksum
|
||||
call SendByte
|
||||
pop hl
|
||||
|
||||
ld b, $e
|
||||
|
||||
ld b, 14
|
||||
.sendLoop
|
||||
call ReadHeaderByte
|
||||
call SendByte
|
||||
dec b
|
||||
jr nz, .sendLoop
|
||||
|
||||
|
||||
; Done bit
|
||||
ld a, $20
|
||||
ld [c], a
|
||||
ldh [c], a
|
||||
ld a, $30
|
||||
ld [c], a
|
||||
|
||||
ldh [c], a
|
||||
|
||||
; Wait 4 frames
|
||||
ld e, 4
|
||||
ld a, 1
|
||||
ldh [rIE], a
|
||||
xor a
|
||||
.waitLoop
|
||||
ldh [rIF], a
|
||||
halt
|
||||
nop
|
||||
dec e
|
||||
jr nz, .waitLoop
|
||||
ldh [rIE], a
|
||||
|
||||
; Update command
|
||||
ldh a, [$80]
|
||||
ldh a, [hCommand]
|
||||
add 2
|
||||
ldh [$80], a
|
||||
|
||||
ldh [hCommand], a
|
||||
|
||||
ld a, $58
|
||||
cp l
|
||||
jr nz, .sendCommand
|
||||
|
||||
|
||||
; Write to sound registers for DMG compatibility
|
||||
ld c, $13
|
||||
ld a, $c1
|
||||
ld [c], a
|
||||
ld c, LOW(rNR13)
|
||||
ld a, $C1
|
||||
ldh [c], a
|
||||
inc c
|
||||
ld a, 7
|
||||
ld [c], a
|
||||
|
||||
ld a, $7
|
||||
ldh [c], a
|
||||
|
||||
; Init BG palette
|
||||
ld a, $fc
|
||||
ldh [$47], a
|
||||
|
||||
ld a, %11_11_11_00
|
||||
ldh [rBGP], a
|
||||
|
||||
; Set registers to match the original SGB boot
|
||||
IF DEF(SGB2)
|
||||
ld a, $FF
|
||||
ld a, BOOTUP_A_MGB
|
||||
ELSE
|
||||
ld a, 1
|
||||
ld a, BOOTUP_A_DMG
|
||||
ENDC
|
||||
ld hl, $c060
|
||||
|
||||
ld hl, $C060
|
||||
|
||||
; Boot the game
|
||||
jp BootGame
|
||||
|
||||
|
@ -168,9 +185,9 @@ SendByte:
|
|||
jr c, .zeroBit
|
||||
add a ; 10 -> 20
|
||||
.zeroBit
|
||||
ld [c], a
|
||||
ldh [c], a
|
||||
ld a, $30
|
||||
ld [c], a
|
||||
ldh [c], a
|
||||
dec d
|
||||
ret z
|
||||
jr .loop
|
||||
|
@ -195,19 +212,24 @@ DoubleBitsAndWriteRow:
|
|||
inc hl
|
||||
ret
|
||||
|
||||
WaitFrame:
|
||||
push hl
|
||||
ld hl, $FF0F
|
||||
res 0, [hl]
|
||||
.wait
|
||||
bit 0, [hl]
|
||||
jr z, .wait
|
||||
pop hl
|
||||
ret
|
||||
|
||||
TrademarkSymbol:
|
||||
db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
|
||||
pusho
|
||||
opt b.X
|
||||
db %..XXXX..
|
||||
db %.X....X.
|
||||
db %X.XXX..X
|
||||
db %X.X..X.X
|
||||
db %X.XXX..X
|
||||
db %X.X..X.X
|
||||
db %.X....X.
|
||||
db %..XXXX..
|
||||
popo
|
||||
TrademarkSymbolEnd:
|
||||
|
||||
SECTION "BootGame", ROM0[$fe]
|
||||
SECTION "BootGame", ROM0[$00FE]
|
||||
BootGame:
|
||||
ldh [$50], a
|
||||
ldh [rBANK], a
|
||||
|
||||
SECTION "HRAM", HRAM[_HRAM]
|
||||
hCommand:
|
||||
ds 1
|
||||
|
|
|
@ -24,7 +24,7 @@ SameBoy's main target compiler is Clang, but GCC is also supported when targetin
|
|||
|
||||
### Third Party Libraries and Tools
|
||||
|
||||
Avoid adding new required dependencies; run-time and compile-time dependencies alike. Most importantly, avoid linking against GPL licensed libraries (LGPL libraries are fine), so SameBoy can retain its MIT license.
|
||||
Avoid adding new required dependencies; run-time and compile-time dependencies alike. Most importantly, avoid linking against GPL licensed libraries (LGPL libraries are fine), so SameBoy can retain its Expat license.
|
||||
|
||||
### Spacing, Indentation and Formatting
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?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">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="Document">
|
||||
<connections>
|
||||
<outlet property="audioFormatButton" destination="knX-AW-zt5" id="fKt-eI-H0y"/>
|
||||
<outlet property="audioRecordingAccessoryView" destination="c22-O7-iKe" id="XD8-Gi-qOC"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="354" height="36"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Atq-RE-328">
|
||||
<rect key="frame" x="18" y="10" width="56" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Format:" id="dso-NS-JlD">
|
||||
<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="knX-AW-zt5">
|
||||
<rect key="frame" x="81" y="4" width="256" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Apple AIFF" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="M3Z-UN-VKZ" id="tLM-Di-Dy3">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="gqn-SL-AA5">
|
||||
<items>
|
||||
<menuItem title="Apple AIFF" state="on" tag="1" id="M3Z-UN-VKZ"/>
|
||||
<menuItem title="RIFF WAVE" tag="2" id="zA0-Np-4XD"/>
|
||||
<menuItem title="Raw PCM (Stereo 96KHz, 16-bit LE)" id="r9J-4k-XH5"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="audioFormatChanged:" target="-2" id="I1k-d9-afp"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="75" y="19"/>
|
||||
</customView>
|
||||
</objects>
|
||||
</document>
|
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 336 B |
|
@ -1,6 +1,4 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#ifndef BigSurToolbar_h
|
||||
#define BigSurToolbar_h
|
||||
|
||||
/* Backport the toolbarStyle property to allow compilation with older SDKs*/
|
||||
#ifndef __MAC_10_16
|
||||
|
@ -26,5 +24,3 @@ typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) {
|
|||
@end
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 283 B |
After Width: | Height: | Size: 565 B |
After Width: | Height: | Size: 290 B |
After Width: | Height: | Size: 584 B |
|
@ -0,0 +1,253 @@
|
|||
<?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">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="GBCheatSearchController">
|
||||
<connections>
|
||||
<outlet property="addCheatButton" destination="o1I-5D-V4k" id="lRE-uQ-vul"/>
|
||||
<outlet property="conditionField" destination="XN7-BO-THS" id="vzh-y2-CcN"/>
|
||||
<outlet property="conditionTypeButton" destination="6RO-h8-lZ8" id="5wx-NY-lbK"/>
|
||||
<outlet property="dataTypeButton" destination="5Db-vP-S60" id="wdR-m7-fEI"/>
|
||||
<outlet property="operandField" destination="Q42-Vu-TJW" id="hRn-67-UmU"/>
|
||||
<outlet property="resultsLabel" destination="wbN-MX-lEy" id="gJB-fD-eKK"/>
|
||||
<outlet property="tableView" destination="fSU-85-fVM" id="1kO-kP-yBb"/>
|
||||
<outlet property="window" destination="QvC-M9-y7g" id="g09-DO-GsE"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Cheat Search" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="GBPanel">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="372"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<value key="minSize" type="size" width="480" height="372"/>
|
||||
<value key="maxSize" type="size" width="480" height="99999"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="372"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fvf-Co-5Ga">
|
||||
<rect key="frame" x="-1" y="155" width="482" height="218"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="1kJ-HR-keu">
|
||||
<rect key="frame" x="1" y="1" width="480" height="216"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" tableStyle="plain" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" headerView="oyv-sh-ulk" id="fSU-85-fVM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="188"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="17" height="0.0"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="AutomaticTableColumnIdentifier.0" editable="NO" width="143" minWidth="40" maxWidth="1000" id="8z0-o5-dNI">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Address">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" controlSize="large" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" title="Text Cell" id="bmG-fw-HDR" customClass="GBCenteredTextCell">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="AutomaticTableColumnIdentifier.1" editable="NO" width="143" minWidth="40" maxWidth="1000" id="mBc-yv-f00">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Previous Value">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" controlSize="large" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" title="Text Cell" id="BSe-S3-KNH" customClass="GBCenteredTextCell">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="143" minWidth="10" maxWidth="3.4028234663852886e+38" id="F7j-ck-3H0">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Current Value">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" controlSize="large" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="wjH-Ei-6hv" customClass="GBCenteredTextCell">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="-2" id="Yqt-V5-OkG"/>
|
||||
<outlet property="delegate" destination="-2" id="TRM-gh-TG4"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<nil key="backgroundColor"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="ZbQ-IG-kyP">
|
||||
<rect key="frame" x="1" y="201" width="480" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="wK3-8v-kAQ">
|
||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<tableHeaderView key="headerView" wantsLayer="YES" id="oyv-sh-ulk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableHeaderView>
|
||||
</scrollView>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KdM-lW-WbP">
|
||||
<rect key="frame" x="371" y="25" 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"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" 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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" 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"/>
|
||||
<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"/>
|
||||
<menu key="menu" id="e5r-qR-pg7">
|
||||
<items>
|
||||
<menuItem title="8-Bit" state="on" id="al4-Jb-OJB"/>
|
||||
<menuItem title="16-Bit" tag="1" id="G2m-fU-8Lt"/>
|
||||
<menuItem title="16-Bit (Big Endian)" tag="3" id="JRa-DB-dyG"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6RO-h8-lZ8">
|
||||
<rect key="frame" x="133" y="89" width="197" height="25"/>
|
||||
<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"/>
|
||||
<menu key="menu" id="Kg9-Rd-1GQ">
|
||||
<items>
|
||||
<menuItem title="Any" id="cEg-eI-4hb"/>
|
||||
<menuItem title="Is Equal To…" state="on" id="Vfb-Dg-Jkb"/>
|
||||
<menuItem title="Is Different From…" id="JDg-d9-5Ux"/>
|
||||
<menuItem title="Is Greater Than…" id="Rir-w0-737"/>
|
||||
<menuItem title="Is Equal or Greater Than…" id="Lvo-pV-Syt"/>
|
||||
<menuItem title="Is Less Than…" id="VUW-DY-2cI"/>
|
||||
<menuItem title="Is Equal or Less Than…" id="rDN-UF-tIo"/>
|
||||
<menuItem title="Did Change" id="JdI-CW-E8H"/>
|
||||
<menuItem title="Did Not Change" id="sWc-Yz-Cve"/>
|
||||
<menuItem title="Did Increase" id="LHF-Lf-7tK"/>
|
||||
<menuItem title="Did Decrease" id="8s2-8n-aQO"/>
|
||||
<menuItem title="Custom…" id="8xc-2v-YQj"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="conditionChanged:" target="-2" id="KF9-vz-yNC"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField 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">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<allowedInputSourceLocales>
|
||||
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
|
||||
</allowedInputSourceLocales>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<action selector="search:" target="-2" id="BDa-5j-qEz"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<allowedInputSourceLocales>
|
||||
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
|
||||
</allowedInputSourceLocales>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<action selector="search:" target="-2" id="sv5-eX-Kb9"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tBa-6c-9AY">
|
||||
<rect key="frame" x="13" y="25" 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"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="reset:" target="-2" id="KCy-Ob-tlg"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField 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">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o1I-5D-V4k">
|
||||
<rect key="frame" x="264" y="25" 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"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" 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"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
</view>
|
||||
<contentBorderThickness minY="22"/>
|
||||
<point key="canvasLocation" x="161" y="182"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
After Width: | Height: | Size: 197 B |
After Width: | Height: | Size: 314 B |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 462 B |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 849 B |
After Width: | Height: | Size: 231 B |
After Width: | Height: | Size: 435 B |
After Width: | Height: | Size: 234 B |
After Width: | Height: | Size: 433 B |
|
@ -1,61 +1,95 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#include "GBView.h"
|
||||
#include "GBImageView.h"
|
||||
#include "GBSplitView.h"
|
||||
#include "GBVisualizerView.h"
|
||||
#include "GBOSDView.h"
|
||||
#import "GBView.h"
|
||||
#import "GBImageView.h"
|
||||
#import "GBSplitView.h"
|
||||
#import "GBVisualizerView.h"
|
||||
#import "GBOSDView.h"
|
||||
#import "GBDebuggerButton.h"
|
||||
|
||||
enum model {
|
||||
MODEL_NONE,
|
||||
MODEL_DMG,
|
||||
MODEL_CGB,
|
||||
MODEL_AGB,
|
||||
MODEL_SGB,
|
||||
MODEL_MGB,
|
||||
MODEL_AUTO,
|
||||
|
||||
MODEL_QUICK_RESET = -1,
|
||||
};
|
||||
|
||||
@class GBCheatWindowController;
|
||||
@class GBPaletteView;
|
||||
@class GBObjectView;
|
||||
|
||||
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
|
||||
@property (nonatomic, readonly) GB_gameboy_t *gb;
|
||||
@property (nonatomic, strong) IBOutlet GBView *view;
|
||||
@property (nonatomic, strong) IBOutlet NSTextView *consoleOutput;
|
||||
@property (nonatomic, strong) IBOutlet NSPanel *consoleWindow;
|
||||
@property (nonatomic, strong) IBOutlet NSTextField *consoleInput;
|
||||
@property (nonatomic, strong) IBOutlet NSWindow *mainWindow;
|
||||
@property (nonatomic, strong) IBOutlet NSView *memoryView;
|
||||
@property (nonatomic, strong) IBOutlet NSPanel *memoryWindow;
|
||||
@property (nonatomic, readonly) GB_gameboy_t *gameboy;
|
||||
@property (nonatomic, strong) IBOutlet NSTextField *memoryBankInput;
|
||||
@property (nonatomic, strong) IBOutlet NSToolbarItem *memoryBankItem;
|
||||
@property (nonatomic, strong) IBOutlet GBImageView *tilesetImageView;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *tilesetPaletteButton;
|
||||
@property (nonatomic, strong) IBOutlet GBImageView *tilemapImageView;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *tilemapPaletteButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *tilemapMapButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *TilemapSetButton;
|
||||
@property (nonatomic, strong) IBOutlet NSButton *gridButton;
|
||||
@property (nonatomic, strong) IBOutlet NSTabView *vramTabView;
|
||||
@property (nonatomic, strong) IBOutlet NSPanel *vramWindow;
|
||||
@property (nonatomic, strong) IBOutlet NSTextField *vramStatusLabel;
|
||||
@property (nonatomic, strong) IBOutlet NSTableView *paletteTableView;
|
||||
@property (nonatomic, strong) IBOutlet NSTableView *spritesTableView;
|
||||
@property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow;
|
||||
@property (nonatomic, strong) IBOutlet NSImageView *feedImageView;
|
||||
@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput;
|
||||
@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideView;
|
||||
@property (nonatomic, strong) IBOutlet GBSplitView *debuggerSplitView;
|
||||
@property (nonatomic, strong) IBOutlet NSBox *debuggerVerticalLine;
|
||||
@property (nonatomic, strong) IBOutlet NSPanel *cheatsWindow;
|
||||
@property (nonatomic, strong) IBOutlet GBCheatWindowController *cheatWindowController;
|
||||
@property (nonatomic, readonly) Document *partner;
|
||||
@property (nonatomic, readonly) bool isSlave;
|
||||
@property (strong) IBOutlet NSView *gbsPlayerView;
|
||||
@property (strong) IBOutlet NSTextField *gbsTitle;
|
||||
@property (strong) IBOutlet NSTextField *gbsAuthor;
|
||||
@property (strong) IBOutlet NSTextField *gbsCopyright;
|
||||
@property (strong) IBOutlet NSPopUpButton *gbsTracks;
|
||||
@property (strong) IBOutlet NSButton *gbsPlayPauseButton;
|
||||
@property (strong) IBOutlet NSButton *gbsRewindButton;
|
||||
@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton;
|
||||
@property (strong) IBOutlet GBVisualizerView *gbsVisualizer;
|
||||
@property (strong) IBOutlet GBOSDView *osdView;
|
||||
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSSplitViewDelegate>
|
||||
@property (readonly) GB_gameboy_t *gb;
|
||||
@property IBOutlet GBView *view;
|
||||
@property IBOutlet NSTextView *consoleOutput;
|
||||
@property IBOutlet NSPanel *consoleWindow;
|
||||
@property IBOutlet NSTextField *consoleInput;
|
||||
@property IBOutlet NSWindow *mainWindow;
|
||||
@property IBOutlet NSView *memoryView;
|
||||
@property IBOutlet NSPanel *memoryWindow;
|
||||
@property (readonly) GB_gameboy_t *gameboy;
|
||||
@property IBOutlet NSTextField *memoryBankInput;
|
||||
@property IBOutlet NSToolbarItem *memoryBankItem;
|
||||
@property IBOutlet NSPopUpButton *memorySpaceButton;
|
||||
@property IBOutlet GBImageView *tilesetImageView;
|
||||
@property IBOutlet NSPopUpButton *tilesetPaletteButton;
|
||||
@property IBOutlet GBImageView *tilemapImageView;
|
||||
@property IBOutlet NSPopUpButton *tilemapPaletteButton;
|
||||
@property IBOutlet NSPopUpButton *tilemapMapButton;
|
||||
@property IBOutlet NSPopUpButton *TilemapSetButton;
|
||||
@property IBOutlet NSButton *gridButton;
|
||||
@property IBOutlet NSTabView *vramTabView;
|
||||
@property IBOutlet NSPanel *vramWindow;
|
||||
@property IBOutlet NSTextField *vramStatusLabel;
|
||||
@property IBOutlet GBPaletteView *paletteView;
|
||||
@property IBOutlet GBObjectView *objectView;
|
||||
@property IBOutlet NSPanel *printerFeedWindow;
|
||||
@property IBOutlet NSProgressIndicator *printerSpinner;
|
||||
@property IBOutlet NSImageView *feedImageView;
|
||||
@property IBOutlet NSTextView *debuggerSideViewInput;
|
||||
@property IBOutlet NSTextView *debuggerSideView;
|
||||
@property IBOutlet GBSplitView *debuggerSplitView;
|
||||
@property IBOutlet NSBox *debuggerVerticalLine;
|
||||
@property IBOutlet NSPanel *cheatsWindow;
|
||||
@property IBOutlet GBCheatWindowController *cheatWindowController;
|
||||
@property (readonly) Document *partner;
|
||||
@property (readonly) bool isSlave;
|
||||
@property IBOutlet NSView *gbsPlayerView;
|
||||
@property IBOutlet NSTextField *gbsTitle;
|
||||
@property IBOutlet NSTextField *gbsAuthor;
|
||||
@property IBOutlet NSTextField *gbsCopyright;
|
||||
@property IBOutlet NSPopUpButton *gbsTracks;
|
||||
@property IBOutlet NSButton *gbsPlayPauseButton;
|
||||
@property IBOutlet NSButton *gbsRewindButton;
|
||||
@property IBOutlet NSSegmentedControl *gbsNextPrevButton;
|
||||
@property IBOutlet GBVisualizerView *gbsVisualizer;
|
||||
@property IBOutlet GBOSDView *osdView;
|
||||
@property (readonly) GB_oam_info_t *oamInfo;
|
||||
@property uint8_t oamCount;
|
||||
@property uint8_t oamHeight;
|
||||
@property IBOutlet NSView *audioRecordingAccessoryView;
|
||||
@property IBOutlet NSPopUpButton *audioFormatButton;
|
||||
@property IBOutlet NSVisualEffectView *debuggerSidebarEffectView API_AVAILABLE(macos(10.10));
|
||||
|
||||
-(uint8_t) readMemory:(uint16_t) addr;
|
||||
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
|
||||
-(void) performAtomicBlock: (void (^)())block;
|
||||
-(void) connectLinkCable:(NSMenuItem *)sender;
|
||||
-(int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound;
|
||||
@property IBOutlet GBDebuggerButton *debuggerContinueButton;
|
||||
@property IBOutlet GBDebuggerButton *debuggerNextButton;
|
||||
@property IBOutlet GBDebuggerButton *debuggerStepButton;
|
||||
@property IBOutlet GBDebuggerButton *debuggerFinishButton;
|
||||
@property IBOutlet GBDebuggerButton *debuggerBackstepButton;
|
||||
|
||||
@property IBOutlet NSScrollView *debuggerScrollView;
|
||||
@property IBOutlet NSView *debugBar;
|
||||
|
||||
|
||||
+ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale;
|
||||
- (void) performAtomicBlock: (void (^)())block;
|
||||
- (void) connectLinkCable:(NSMenuItem *)sender;
|
||||
- (int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound;
|
||||
- (NSString *)captureOutputForBlock: (void (^)())block;
|
||||
- (NSFont *)debuggerFontOfSize:(unsigned)size;
|
||||
@end
|
||||
|
||||
|
|
After Width: | Height: | Size: 166 B |
After Width: | Height: | Size: 298 B |
|
@ -0,0 +1,27 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
@interface GBApp : NSApplication <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate, WebUIDelegate, WebPolicyDelegate, WebFrameLoadDelegate, JOYListener>
|
||||
|
||||
@property (nonatomic, strong) IBOutlet NSWindow *preferencesWindow;
|
||||
@property (nonatomic, strong) IBOutlet NSView *graphicsTab;
|
||||
@property (nonatomic, strong) IBOutlet NSView *emulationTab;
|
||||
@property (nonatomic, strong) IBOutlet NSView *audioTab;
|
||||
@property (nonatomic, strong) IBOutlet NSView *controlsTab;
|
||||
@property (nonatomic, strong) IBOutlet NSView *updatesTab;
|
||||
- (IBAction)showPreferences: (id) sender;
|
||||
- (IBAction)toggleDeveloperMode:(id)sender;
|
||||
- (IBAction)switchPreferencesTab:(id)sender;
|
||||
@property (nonatomic, weak) IBOutlet NSMenuItem *linkCableMenuItem;
|
||||
@property (nonatomic, strong) IBOutlet NSWindow *updateWindow;
|
||||
@property (nonatomic, strong) IBOutlet WebView *updateChanges;
|
||||
@property (nonatomic, strong) IBOutlet NSProgressIndicator *updatesSpinner;
|
||||
@property (strong) IBOutlet NSButton *updatesButton;
|
||||
@property (strong) IBOutlet NSTextField *updateProgressLabel;
|
||||
@property (strong) IBOutlet NSButton *updateProgressButton;
|
||||
@property (strong) IBOutlet NSWindow *updateProgressWindow;
|
||||
@property (strong) IBOutlet NSProgressIndicator *updateProgressSpinner;
|
||||
- (void)updateThemesDefault:(bool)overwrite;
|
||||
@end
|
||||
|
|
@ -0,0 +1,766 @@
|
|||
#import "GBApp.h"
|
||||
#import "GBButtons.h"
|
||||
#import "GBView.h"
|
||||
#import "Document.h"
|
||||
#import "GBJoyConManager.h"
|
||||
#import <Core/gb.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <mach-o/dyld.h>
|
||||
|
||||
#define UPDATE_SERVER "https://sameboy.github.io"
|
||||
|
||||
static uint32_t color_to_int(NSColor *color)
|
||||
{
|
||||
color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
|
||||
return (((unsigned)(color.redComponent * 0xFF)) << 16) |
|
||||
(((unsigned)(color.greenComponent * 0xFF)) << 8) |
|
||||
((unsigned)(color.blueComponent * 0xFF));
|
||||
}
|
||||
|
||||
@implementation GBApp
|
||||
{
|
||||
NSArray<NSView *> *_preferencesTabs;
|
||||
NSString *_lastVersion;
|
||||
NSString *_updateURL;
|
||||
NSURLSessionDownloadTask *_updateTask;
|
||||
enum {
|
||||
UPDATE_DOWNLOADING,
|
||||
UPDATE_EXTRACTING,
|
||||
UPDATE_WAIT_INSTALL,
|
||||
UPDATE_INSTALLING,
|
||||
UPDATE_FAILED,
|
||||
} _updateState;
|
||||
NSString *_downloadDirectory;
|
||||
AuthorizationRef _auth;
|
||||
bool _simulatingMenuEvent;
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
// Refresh icon if launched via a software update
|
||||
[NSApplication sharedApplication].applicationIconImage = [NSImage imageNamed:@"AppIcon"];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
for (unsigned i = 0; i < GBKeyboardButtonCount; i++) {
|
||||
if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) {
|
||||
[defaults removeObjectForKey:button_to_preference_name(i, 0)];
|
||||
}
|
||||
}
|
||||
|
||||
bool hasSFMono = false;
|
||||
if (@available(macOS 10.15, *)) {
|
||||
hasSFMono = [[NSFont monospacedSystemFontOfSize:12 weight:NSFontWeightRegular].displayName containsString:@"SF"];
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
|
||||
@"GBRight": @(kVK_RightArrow),
|
||||
@"GBLeft": @(kVK_LeftArrow),
|
||||
@"GBUp": @(kVK_UpArrow),
|
||||
@"GBDown": @(kVK_DownArrow),
|
||||
|
||||
@"GBA": @(kVK_ANSI_X),
|
||||
@"GBB": @(kVK_ANSI_Z),
|
||||
@"GBSelect": @(kVK_Delete),
|
||||
@"GBStart": @(kVK_Return),
|
||||
|
||||
@"GBTurbo": @(kVK_Space),
|
||||
@"GBRewind": @(kVK_Tab),
|
||||
@"GBSlow-Motion": @(kVK_Shift),
|
||||
|
||||
@"GBFilter": @"NearestNeighbor",
|
||||
@"GBColorCorrection": @(GB_COLOR_CORRECTION_MODERN_BALANCED),
|
||||
@"GBHighpassFilter": @(GB_HIGHPASS_ACCURATE),
|
||||
@"GBRewindLength": @(120),
|
||||
@"GBFrameBlendingMode": @([defaults boolForKey:@"DisableFrameBlending"]? GB_FRAME_BLENDING_MODE_DISABLED : GB_FRAME_BLENDING_MODE_ACCURATE),
|
||||
|
||||
@"GBDMGModel": @(GB_MODEL_DMG_B),
|
||||
@"GBCGBModel": @(GB_MODEL_CGB_E),
|
||||
@"GBAGBModel": @(GB_MODEL_AGB_A),
|
||||
@"GBSGBModel": @(GB_MODEL_SGB2),
|
||||
@"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY),
|
||||
|
||||
@"GBVolume": @(1.0),
|
||||
|
||||
@"GBMBC7JoystickOverride": @NO,
|
||||
@"GBMBC7AllowMouse": @YES,
|
||||
|
||||
@"GBJoyConAutoPair": @YES,
|
||||
@"GBJoyConsDefaultsToHorizontal": @YES,
|
||||
|
||||
@"GBEmulatedModel": @(MODEL_AUTO),
|
||||
|
||||
@"GBDebuggerFont": hasSFMono? @"SF Mono" : @"Menlo",
|
||||
@"GBDebuggerFontSize": @12,
|
||||
|
||||
// Default themes
|
||||
@"GBThemes": @{
|
||||
@"Canyon": @{
|
||||
@"BrightnessBias": @0.1227009965823247,
|
||||
@"Colors": @[@0xff0c1e20, @0xff122b91, @0xff466aa2, @0xfff1efae, @0xfff1efae],
|
||||
@"DisabledLCDColor": @NO,
|
||||
@"HueBias": @0.01782661816105247,
|
||||
@"HueBiasStrength": @1,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Desert": @{
|
||||
@"BrightnessBias": @0.0,
|
||||
@"Colors": @[@0xff302f3e, @0xff576674, @0xff839ba4, @0xffb1d0d2, @0xffb7d7d8],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.10087773904382469,
|
||||
@"HueBiasStrength": @0.062142056772908363,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Evening": @{
|
||||
@"BrightnessBias": @-0.10168700106441975,
|
||||
@"Colors": @[@0xff362601, @0xff695518, @0xff899853, @0xffa6e4ae, @0xffa9eebb],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.60027079191058874,
|
||||
@"HueBiasStrength": @0.33816297305747867,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Fog": @{
|
||||
@"BrightnessBias": @0.0,
|
||||
@"Colors": @[@0xff373c34, @0xff737256, @0xff9da386, @0xffc3d2bf, @0xffc7d8c6],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.55750435756972117,
|
||||
@"HueBiasStrength": @0.18424738545816732,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Green Slate": @{
|
||||
@"BrightnessBias": @0.2210012227296829,
|
||||
@"Colors": @[@0xff343117, @0xff6a876f, @0xff98b4a1, @0xffc3daca, @0xffc8decf],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.1887667975388467,
|
||||
@"HueBiasStrength": @0.1272283345460892,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Green Tea": @{
|
||||
@"BrightnessBias": @-0.4946326622596153,
|
||||
@"Colors": @[@0xff1a1d08, @0xff1d5231, @0xff3b9774, @0xff97e4c6, @0xffa9eed1],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.1912955007245464,
|
||||
@"HueBiasStrength": @0.3621708039314516,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Lavender": @{
|
||||
@"BrightnessBias": @0.10072476038566,
|
||||
@"Colors": @[@0xff2b2a3a, @0xff8c507c, @0xffbf82a8, @0xffe9bcce, @0xffeec3d3],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.7914529587142169,
|
||||
@"HueBiasStrength": @0.2498168498277664,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Magic Eggplant": @{
|
||||
@"BrightnessBias": @0.0,
|
||||
@"Colors": @[@0xff3c2136, @0xff942e84, @0xffc7699d, @0xfff1e4b0, @0xfff6f9b2],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.87717878486055778,
|
||||
@"HueBiasStrength": @0.65018052788844627,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Mystic Blue": @{
|
||||
@"BrightnessBias": @-0.3291049897670746,
|
||||
@"Colors": @[@0xff3b2306, @0xffa27807, @0xffd1b523, @0xfff6ebbe, @0xfffaf1e4],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.5282051088288426,
|
||||
@"HueBiasStrength": @0.7699633836746216,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Pink Pop": @{
|
||||
@"BrightnessBias": @0.624908447265625,
|
||||
@"Colors": @[@0xff28140a, @0xff7c42cb, @0xffaa83de, @0xffd1ceeb, @0xffd5d8ec],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.9477411056868732,
|
||||
@"HueBiasStrength": @0.80024421215057373,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Radioactive Pea": @{
|
||||
@"BrightnessBias": @-0.48079556772908372,
|
||||
@"Colors": @[@0xff215200, @0xff1f7306, @0xff169e34, @0xff03ceb8, @0xff00d4d1],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.3795131972111554,
|
||||
@"HueBiasStrength": @0.34337649402390436,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Rose": @{
|
||||
@"BrightnessBias": @0.2727272808551788,
|
||||
@"Colors": @[@0xff001500, @0xff4e1fae, @0xff865ac4, @0xffb7e6d3, @0xffbdffd4],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.9238900924101472,
|
||||
@"HueBiasStrength": @0.9957716464996338,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Seaweed": @{
|
||||
@"BrightnessBias": @-0.28532744023904377,
|
||||
@"Colors": @[@0xff3f0015, @0xff426532, @0xff58a778, @0xff95e0df, @0xffa0e7ee],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.2694067480079681,
|
||||
@"HueBiasStrength": @0.51565612549800799,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Twilight": @{
|
||||
@"BrightnessBias": @-0.091789093625498031,
|
||||
@"Colors": @[@0xff3f0015, @0xff461286, @0xff6254bd, @0xff97d3e9, @0xffa0e7ee],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.0,
|
||||
@"HueBiasStrength": @0.49710532868525897,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
},
|
||||
|
||||
@"NSToolbarItemForcesStandardSize": @YES, // Forces Monterey to resepect toolbar item sizes
|
||||
@"NSToolbarItemWarnOnMinMaxSize": @NO, // Not going to use Constraints, Apple
|
||||
}];
|
||||
|
||||
[JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{
|
||||
JOYAxes2DEmulateButtonsKey: @YES,
|
||||
JOYHatsEmulateButtonsKey: @YES,
|
||||
}];
|
||||
|
||||
[GBJoyConManager sharedInstance]; // Starts handling Joy-Cons
|
||||
|
||||
[JOYController registerListener:self];
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
|
||||
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
|
||||
}
|
||||
|
||||
[self askAutoUpdates];
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]) {
|
||||
[self checkForUpdates];
|
||||
}
|
||||
|
||||
if ([[NSProcessInfo processInfo].arguments containsObject:@"--update-launch"]) {
|
||||
[NSApp activateIgnoringOtherApps:true];
|
||||
}
|
||||
|
||||
if (![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBThemesVersion"] isEqualToString:@(GB_VERSION)]) {
|
||||
[self updateThemesDefault:false];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(GB_VERSION) forKey:@"GBThemesVersion"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateThemesDefault:(bool)overwrite
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableDictionary *currentThemes = [defaults dictionaryForKey:@"GBThemes"].mutableCopy;
|
||||
[defaults removeObjectForKey:@"GBThemes"];
|
||||
NSMutableDictionary *defaultThemes = [defaults dictionaryForKey:@"GBThemes"].mutableCopy;
|
||||
if (![[NSUserDefaults standardUserDefaults] stringForKey:@"GBThemesVersion"]) {
|
||||
// Force update the Pink Pop theme, it was glitchy in 1.0
|
||||
[currentThemes removeObjectForKey:@"Pink Pop"];
|
||||
}
|
||||
if (overwrite) {
|
||||
[currentThemes addEntriesFromDictionary:defaultThemes];
|
||||
[defaults setObject:currentThemes forKey:@"GBThemes"];
|
||||
}
|
||||
else {
|
||||
[defaultThemes addEntriesFromDictionary:currentThemes];
|
||||
[defaults setObject:defaultThemes forKey:@"GBThemes"];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)toggleDeveloperMode:(id)sender
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
[defaults setBool:![defaults boolForKey:@"DeveloperMode"] forKey:@"DeveloperMode"];
|
||||
}
|
||||
|
||||
- (IBAction)switchPreferencesTab:(id)sender
|
||||
{
|
||||
for (NSView *view in _preferencesTabs) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
NSView *tab = _preferencesTabs[[sender tag]];
|
||||
NSRect old = [_preferencesWindow frame];
|
||||
NSRect new = [_preferencesWindow frameRectForContentRect:tab.frame];
|
||||
new.origin.x = old.origin.x;
|
||||
new.origin.y = old.origin.y + (old.size.height - new.size.height);
|
||||
[_preferencesWindow setFrame:new display:true animate:_preferencesWindow.visible];
|
||||
[_preferencesWindow.contentView addSubview:tab];
|
||||
}
|
||||
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)anItem
|
||||
{
|
||||
if ([anItem action] == @selector(toggleDeveloperMode:)) {
|
||||
[(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]];
|
||||
}
|
||||
|
||||
if (anItem == self.linkCableMenuItem) {
|
||||
return [[NSDocumentController sharedDocumentController] documents].count > 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)menuNeedsUpdate:(NSMenu *)menu
|
||||
{
|
||||
NSMutableArray *items = [NSMutableArray array];
|
||||
NSDocument *currentDocument = [[NSDocumentController sharedDocumentController] currentDocument];
|
||||
|
||||
for (NSDocument *document in [[NSDocumentController sharedDocumentController] documents]) {
|
||||
if (document == currentDocument) continue;
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:document.displayName action:@selector(connectLinkCable:) keyEquivalent:@""];
|
||||
item.representedObject = document;
|
||||
item.image = [[NSWorkspace sharedWorkspace] iconForFile:document.fileURL.path];
|
||||
[item.image setSize:NSMakeSize(16, 16)];
|
||||
[items addObject:item];
|
||||
}
|
||||
[menu removeAllItems];
|
||||
for (NSMenuItem *item in items) {
|
||||
[menu addItem:item];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction) showPreferences: (id) sender
|
||||
{
|
||||
NSArray *objects;
|
||||
if (!_preferencesWindow) {
|
||||
[[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:&objects];
|
||||
NSToolbarItem *first_toolbar_item = [_preferencesWindow.toolbar.items firstObject];
|
||||
_preferencesWindow.toolbar.selectedItemIdentifier = [first_toolbar_item itemIdentifier];
|
||||
_preferencesTabs = @[self.emulationTab, self.graphicsTab, self.audioTab, self.controlsTab, self.updatesTab];
|
||||
[self switchPreferencesTab:first_toolbar_item];
|
||||
[_preferencesWindow center];
|
||||
#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];
|
||||
}
|
||||
}
|
||||
[_preferencesWindow makeKeyAndOrderFront:self];
|
||||
}
|
||||
|
||||
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
|
||||
{
|
||||
[self askAutoUpdates];
|
||||
/* Bring an existing panel to the foreground */
|
||||
for (NSWindow *window in [[NSApplication sharedApplication] windows]) {
|
||||
if ([window isKindOfClass:[NSOpenPanel class]]) {
|
||||
[(NSOpenPanel *)window makeKeyAndOrderFront:nil];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
[[NSDocumentController sharedDocumentController] openDocument:self];
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
|
||||
{
|
||||
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:true];
|
||||
}
|
||||
|
||||
- (void)updateFound
|
||||
{
|
||||
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/raw_changes"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
|
||||
NSColor *linkColor = [NSColor colorWithRed:0.125 green:0.325 blue:1.0 alpha:1.0];
|
||||
if (@available(macOS 10.10, *)) {
|
||||
linkColor = [NSColor linkColor];
|
||||
}
|
||||
|
||||
NSString *changes = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
NSRange cutoffRange = [changes rangeOfString:@"<!--(" GB_VERSION ")-->"];
|
||||
if (cutoffRange.location != NSNotFound) {
|
||||
changes = [changes substringToIndex:cutoffRange.location];
|
||||
}
|
||||
|
||||
NSString *html = [NSString stringWithFormat:@"<!DOCTYPE html><html><head><title></title>"
|
||||
"<style>html {background-color:transparent; color: #%06x; line-height:1.5} a:link, a:visited{color:#%06x; text-decoration:none}</style>"
|
||||
"</head><body>%@</body></html>",
|
||||
color_to_int([NSColor textColor]),
|
||||
color_to_int(linkColor),
|
||||
changes];
|
||||
|
||||
if ([(NSHTTPURLResponse *)response statusCode] == 200) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSArray *objects;
|
||||
[[NSBundle mainBundle] loadNibNamed:@"UpdateWindow" owner:self topLevelObjects:&objects];
|
||||
if (@available(macOS 10.11, *)) {
|
||||
self.updateChanges.preferences.standardFontFamily = @"-apple-system";
|
||||
}
|
||||
else if (@available(macOS 10.10, *)) {
|
||||
self.updateChanges.preferences.standardFontFamily = @"Helvetica Neue";
|
||||
}
|
||||
else {
|
||||
self.updateChanges.preferences.standardFontFamily = @"Lucida Grande";
|
||||
}
|
||||
self.updateChanges.preferences.fixedFontFamily = @"Menlo";
|
||||
self.updateChanges.drawsBackground = false;
|
||||
[self.updateChanges.mainFrame loadHTMLString:html baseURL:nil];
|
||||
});
|
||||
}
|
||||
}] resume];
|
||||
}
|
||||
|
||||
- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
|
||||
{
|
||||
// Disable reload context menu
|
||||
if ([defaultMenuItems count] <= 2) {
|
||||
return nil;
|
||||
}
|
||||
return defaultMenuItems;
|
||||
}
|
||||
|
||||
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sender.mainFrame.frameView.documentView.enclosingScrollView.drawsBackground = true;
|
||||
sender.mainFrame.frameView.documentView.enclosingScrollView.backgroundColor = [NSColor textBackgroundColor];
|
||||
sender.policyDelegate = self;
|
||||
[self.updateWindow center];
|
||||
[self.updateWindow makeKeyAndOrderFront:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
|
||||
{
|
||||
[listener ignore];
|
||||
[[NSWorkspace sharedWorkspace] openURL:[request URL]];
|
||||
}
|
||||
|
||||
- (void)checkForUpdates
|
||||
{
|
||||
#ifdef UPDATE_SUPPORT
|
||||
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@UPDATE_SERVER "/latest_version"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.updatesSpinner stopAnimation:nil];
|
||||
[self.updatesButton setEnabled:true];
|
||||
});
|
||||
if ([(NSHTTPURLResponse *)response statusCode] == 200) {
|
||||
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
NSArray <NSString *> *components = [string componentsSeparatedByString:@"|"];
|
||||
if (components.count != 2) return;
|
||||
_lastVersion = components[0];
|
||||
_updateURL = components[1];
|
||||
if (![@GB_VERSION isEqualToString:_lastVersion] &&
|
||||
![[[NSUserDefaults standardUserDefaults] stringForKey:@"GBSkippedVersion"] isEqualToString:_lastVersion]) {
|
||||
[self updateFound];
|
||||
}
|
||||
}
|
||||
}] resume];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (IBAction)userCheckForUpdates:(id)sender
|
||||
{
|
||||
if (self.updateWindow) {
|
||||
[self.updateWindow makeKeyAndOrderFront:sender];
|
||||
}
|
||||
else {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"GBSkippedVersion"];
|
||||
[self checkForUpdates];
|
||||
[sender setEnabled:false];
|
||||
[self.updatesSpinner startAnimation:sender];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)askAutoUpdates
|
||||
{
|
||||
#ifdef UPDATE_SUPPORT
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAskedAutoUpdates"]) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"Should SameBoy check for updates when launched?";
|
||||
alert.informativeText = @"SameBoy is frequently updated with new features, accuracy improvements, and bug fixes. This setting can always be changed in the preferences window.";
|
||||
[alert addButtonWithTitle:@"Check on Launch"];
|
||||
[alert addButtonWithTitle:@"Don't Check on Launch"];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setBool:[alert runModal] == NSAlertFirstButtonReturn forKey:@"GBAutoUpdatesEnabled"];
|
||||
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBAskedAutoUpdates"];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (IBAction)skipVersion:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:_lastVersion forKey:@"GBSkippedVersion"];
|
||||
[self.updateWindow performClose:sender];
|
||||
}
|
||||
|
||||
- (bool)executePath:(NSString *)path withArguments:(NSArray <NSString *> *)arguments
|
||||
{
|
||||
if (!_auth) {
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
task.launchPath = path;
|
||||
task.arguments = arguments;
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
return task.terminationStatus == 0 && task.terminationReason == NSTaskTerminationReasonExit;
|
||||
}
|
||||
|
||||
char *argv[arguments.count + 1];
|
||||
argv[arguments.count] = NULL;
|
||||
for (unsigned i = 0; i < arguments.count; i++) {
|
||||
argv[i] = (char *)arguments[i].UTF8String;
|
||||
}
|
||||
|
||||
return AuthorizationExecuteWithPrivileges(_auth, path.UTF8String, kAuthorizationFlagDefaults, argv, NULL) == errAuthorizationSuccess;
|
||||
}
|
||||
|
||||
- (void)deauthorize
|
||||
{
|
||||
if (_auth) {
|
||||
AuthorizationFree(_auth, kAuthorizationFlagDefaults);
|
||||
_auth = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)installUpdate:(id)sender
|
||||
{
|
||||
bool needsAuthorization = false;
|
||||
if ([self executePath:@"/usr/sbin/spctl" withArguments:@[@"--status"]]) { // Succeeds when GateKeeper is on
|
||||
// GateKeeper is on, we need to --add ourselves as root, else we might get a GateKeeper crash
|
||||
needsAuthorization = true;
|
||||
}
|
||||
else if (access(_dyld_get_image_name(0), W_OK)) {
|
||||
// We don't have write access, so we need authorization to update as root
|
||||
needsAuthorization = true;
|
||||
}
|
||||
|
||||
if (needsAuthorization && !_auth) {
|
||||
AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed, &_auth);
|
||||
// Make sure we can modify the bundle
|
||||
if (![self executePath:@"/usr/sbin/chown" withArguments:@[@"-R", [NSString stringWithFormat:@"%d:%d", getuid(), getgid()], [NSBundle mainBundle].bundlePath]]) {
|
||||
[self deauthorize];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self.updateProgressSpinner startAnimation:nil];
|
||||
self.updateProgressButton.title = @"Cancel";
|
||||
self.updateProgressButton.enabled = true;
|
||||
self.updateProgressLabel.stringValue = @"Downloading update…";
|
||||
_updateState = UPDATE_DOWNLOADING;
|
||||
_updateTask = [[NSURLSession sharedSession] downloadTaskWithURL: [NSURL URLWithString:_updateURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
|
||||
_updateTask = nil;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.updateProgressButton.enabled = false;
|
||||
self.updateProgressLabel.stringValue = @"Extracting update…";
|
||||
_updateState = UPDATE_EXTRACTING;
|
||||
});
|
||||
|
||||
_downloadDirectory = [[[NSFileManager defaultManager] URLForDirectory:NSItemReplacementDirectory
|
||||
inDomain:NSUserDomainMask
|
||||
appropriateForURL:[[NSBundle mainBundle] bundleURL]
|
||||
create:true
|
||||
error:nil] path];
|
||||
if (!_downloadDirectory) {
|
||||
[self deauthorize];
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.updateProgressButton.enabled = false;
|
||||
self.updateProgressLabel.stringValue = @"Failed to extract update.";
|
||||
_updateState = UPDATE_FAILED;
|
||||
self.updateProgressButton.title = @"Close";
|
||||
self.updateProgressButton.enabled = true;
|
||||
[self.updateProgressSpinner stopAnimation:nil];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
NSTask *unzipTask;
|
||||
unzipTask = [[NSTask alloc] init];
|
||||
unzipTask.launchPath = @"/usr/bin/unzip";
|
||||
unzipTask.arguments = @[location.path, @"-d", _downloadDirectory];
|
||||
[unzipTask launch];
|
||||
[unzipTask waitUntilExit];
|
||||
if (unzipTask.terminationStatus != 0 || unzipTask.terminationReason != NSTaskTerminationReasonExit) {
|
||||
[self deauthorize];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.updateProgressButton.enabled = false;
|
||||
self.updateProgressLabel.stringValue = @"Failed to extract update.";
|
||||
_updateState = UPDATE_FAILED;
|
||||
self.updateProgressButton.title = @"Close";
|
||||
self.updateProgressButton.enabled = true;
|
||||
[self.updateProgressSpinner stopAnimation:nil];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.updateProgressButton.enabled = false;
|
||||
self.updateProgressLabel.stringValue = @"Update ready, save your game progress and click Install.";
|
||||
_updateState = UPDATE_WAIT_INSTALL;
|
||||
self.updateProgressButton.title = @"Install";
|
||||
self.updateProgressButton.enabled = true;
|
||||
[self.updateProgressSpinner stopAnimation:nil];
|
||||
});
|
||||
}];
|
||||
[_updateTask resume];
|
||||
|
||||
self.updateProgressWindow.preventsApplicationTerminationWhenModal = false;
|
||||
[self.updateWindow beginSheet:self.updateProgressWindow completionHandler:^(NSModalResponse returnCode) {
|
||||
[self.updateWindow close];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)performUpgrade
|
||||
{
|
||||
self.updateProgressButton.enabled = false;
|
||||
self.updateProgressLabel.stringValue = @"Instaling update…";
|
||||
_updateState = UPDATE_INSTALLING;
|
||||
self.updateProgressButton.enabled = false;
|
||||
[self.updateProgressSpinner startAnimation:nil];
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSString *executablePath = [[NSBundle mainBundle] executablePath];
|
||||
NSString *contentsPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents"];
|
||||
NSString *contentsTempPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"TempContents"];
|
||||
NSString *updateContentsPath = [_downloadDirectory stringByAppendingPathComponent:@"SameBoy.app/Contents"];
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] moveItemAtPath:contentsPath toPath:contentsTempPath error:&error];
|
||||
if (error) {
|
||||
[self deauthorize];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
|
||||
_downloadDirectory = nil;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.updateProgressButton.enabled = false;
|
||||
self.updateProgressLabel.stringValue = @"Failed to install update.";
|
||||
_updateState = UPDATE_FAILED;
|
||||
self.updateProgressButton.title = @"Close";
|
||||
self.updateProgressButton.enabled = true;
|
||||
[self.updateProgressSpinner stopAnimation:nil];
|
||||
});
|
||||
return;
|
||||
}
|
||||
[[NSFileManager defaultManager] moveItemAtPath:updateContentsPath toPath:contentsPath error:&error];
|
||||
if (error) {
|
||||
[self deauthorize];
|
||||
[[NSFileManager defaultManager] moveItemAtPath:contentsTempPath toPath:contentsPath error:nil];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
|
||||
_downloadDirectory = nil;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.updateProgressButton.enabled = false;
|
||||
self.updateProgressLabel.stringValue = @"Failed to install update.";
|
||||
_updateState = UPDATE_FAILED;
|
||||
self.updateProgressButton.title = @"Close";
|
||||
self.updateProgressButton.enabled = true;
|
||||
[self.updateProgressSpinner stopAnimation:nil];
|
||||
});
|
||||
return;
|
||||
}
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:contentsTempPath error:nil];
|
||||
_downloadDirectory = nil;
|
||||
atexit_b(^{
|
||||
execl(executablePath.UTF8String, executablePath.UTF8String, "--update-launch", NULL);
|
||||
});
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[NSApp terminate:nil];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (IBAction)updateAction:(id)sender
|
||||
{
|
||||
switch (_updateState) {
|
||||
case UPDATE_DOWNLOADING:
|
||||
[_updateTask cancelByProducingResumeData:nil];
|
||||
_updateTask = nil;
|
||||
[self.updateProgressWindow close];
|
||||
break;
|
||||
case UPDATE_WAIT_INSTALL:
|
||||
[self performUpgrade];
|
||||
break;
|
||||
case UPDATE_EXTRACTING:
|
||||
case UPDATE_INSTALLING:
|
||||
break;
|
||||
case UPDATE_FAILED:
|
||||
[self.updateProgressWindow close];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)orderFrontAboutPanel:(id)sender
|
||||
{
|
||||
// NSAboutPanelOptionApplicationIcon is not available prior to 10.13, but the key is still there and working.
|
||||
[[NSApplication sharedApplication] orderFrontStandardAboutPanelWithOptions:@{
|
||||
@"ApplicationIcon": [NSImage imageNamed:@"Icon"]
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
|
||||
{
|
||||
if (!button.isPressed) return;
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
|
||||
if (!mapping) {
|
||||
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
|
||||
}
|
||||
|
||||
JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: -1;
|
||||
if (!mapping && usage >= JOYButtonUsageGeneric0) {
|
||||
usage = GB_inline_const(JOYButtonUsage[], {JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX})[(usage - JOYButtonUsageGeneric0) & 3];
|
||||
}
|
||||
|
||||
if (usage == GBJoyKitHotkey1 || usage == GBJoyKitHotkey2) {
|
||||
if (_preferencesWindow && self.keyWindow == _preferencesWindow) {
|
||||
return;
|
||||
}
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"] && !self.keyWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:usage == GBJoyKitHotkey1? @"GBJoypadHotkey1" : @"GBJoypadHotkey2"];
|
||||
NSEventModifierFlags flags = NSEventModifierFlagCommand;
|
||||
if ([keyEquivalent hasPrefix:@"^"]) {
|
||||
flags |= NSEventModifierFlagShift;
|
||||
[keyEquivalent substringFromIndex:1];
|
||||
}
|
||||
_simulatingMenuEvent = true;
|
||||
[[NSApplication sharedApplication] sendEvent:[NSEvent keyEventWithType:NSEventTypeKeyDown
|
||||
location:(NSPoint){0,}
|
||||
modifierFlags:flags
|
||||
timestamp:0
|
||||
windowNumber:0
|
||||
context:NULL
|
||||
characters:keyEquivalent
|
||||
charactersIgnoringModifiers:keyEquivalent
|
||||
isARepeat:false
|
||||
keyCode:0]];
|
||||
_simulatingMenuEvent = false;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSWindow *)keyWindow
|
||||
{
|
||||
NSWindow *ret = [super keyWindow];
|
||||
if (!ret && _simulatingMenuEvent) {
|
||||
ret = [(Document *)self.orderedDocuments.firstObject mainWindow];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (NSWindow *)mainWindow
|
||||
{
|
||||
NSWindow *ret = [super mainWindow];
|
||||
if (!ret && _simulatingMenuEvent) {
|
||||
ret = [(Document *)self.orderedDocuments.firstObject mainWindow];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (IBAction)openDebuggerHelp:(id)sender
|
||||
{
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sameboy.github.io/debugger/"]];
|
||||
}
|
||||
|
||||
- (IBAction)openSponsor:(id)sender
|
||||
{
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/sponsors/LIJI32"]];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_downloadDirectory) {
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)nop:(id)sender
|
||||
{
|
||||
}
|
||||
@end
|
|
@ -1,7 +1,4 @@
|
|||
#ifndef GBButtons_h
|
||||
#define GBButtons_h
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
typedef enum {
|
||||
GBRight,
|
||||
GBLeft,
|
||||
GBUp,
|
||||
|
@ -10,14 +7,24 @@ typedef enum : NSUInteger {
|
|||
GBB,
|
||||
GBSelect,
|
||||
GBStart,
|
||||
GBRapidA,
|
||||
GBRapidB,
|
||||
GBTurbo,
|
||||
GBRewind,
|
||||
GBUnderclock,
|
||||
GBButtonCount,
|
||||
GBGameBoyButtonCount = GBStart + 1,
|
||||
GBHotkey1,
|
||||
GBHotkey2,
|
||||
GBTotalButtonCount,
|
||||
GBKeyboardButtonCount = GBUnderclock + 1,
|
||||
GBPerPlayerButtonCount = GBRapidB + 1,
|
||||
} GBButton;
|
||||
|
||||
extern NSString const *GBButtonNames[GBButtonCount];
|
||||
#define GBJoyKitHotkey1 JOYButtonUsageGeneric0 + 0x100
|
||||
#define GBJoyKitHotkey2 JOYButtonUsageGeneric0 + 0x101
|
||||
#define GBJoyKitRapidA JOYButtonUsageGeneric0 + 0x102
|
||||
#define GBJoyKitRapidB JOYButtonUsageGeneric0 + 0x103
|
||||
|
||||
extern NSString const *GBButtonNames[GBTotalButtonCount];
|
||||
|
||||
static inline NSString *n2s(uint64_t number)
|
||||
{
|
||||
|
@ -31,5 +38,3 @@ static inline NSString *button_to_preference_name(GBButton button, unsigned play
|
|||
}
|
||||
return [NSString stringWithFormat:@"GB%@", GBButtonNames[button]];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "GBButtons.h"
|
||||
|
||||
NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion"};
|
||||
NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Rapid A", @"Rapid B", @"Turbo", @"Rewind", @"Slow-Motion", @"Hotkey 1", @"Hotkey 2"};
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBCenteredTextCell : NSTextFieldCell
|
||||
|
||||
@end
|
|
@ -0,0 +1,29 @@
|
|||
#import "GBCenteredTextCell.h"
|
||||
|
||||
@implementation GBCenteredTextCell
|
||||
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
|
||||
{
|
||||
double height = round([self.attributedStringValue size].height);
|
||||
cellFrame.origin.y += (cellFrame.size.height - height) / 2;
|
||||
cellFrame.size.height = height;
|
||||
[super drawInteriorWithFrame:cellFrame inView:controlView];
|
||||
}
|
||||
|
||||
|
||||
- (void)selectWithFrame:(NSRect)rect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)delegate start:(NSInteger)selStart length:(NSInteger)selLength
|
||||
{
|
||||
double height = round([self.attributedStringValue size].height);
|
||||
rect.origin.y += (rect.size.height - height) / 2;
|
||||
rect.size.height = height;
|
||||
[super selectWithFrame:rect inView:controlView editor:textObj delegate:delegate start:selStart length:selLength];
|
||||
}
|
||||
|
||||
- (void)editWithFrame:(NSRect)rect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)delegate event:(NSEvent *)event
|
||||
{
|
||||
double height = round([self.attributedStringValue size].height);
|
||||
rect.origin.y += (rect.size.height - height) / 2;
|
||||
rect.size.height = height;
|
||||
[super editWithFrame:rect inView:controlView editor:textObj delegate:delegate event:event];
|
||||
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,8 @@
|
|||
#import <AppKit/AppKit.h>
|
||||
#import "Document.h"
|
||||
|
||||
@interface GBCheatSearchController<NSTableViewDelegate, NSTableViewDataSource, NSControlTextEditingDelegate> : NSObject
|
||||
@property IBOutlet NSWindow *window;
|
||||
@property IBOutlet NSTableView *tableView;
|
||||
+ (instancetype)controllerWithDocument:(Document *)document;
|
||||
@end
|
|
@ -0,0 +1,234 @@
|
|||
#import "GBCheatSearchController.h"
|
||||
#import "GBWarningPopover.h"
|
||||
#import "GBCheatWindowController.h"
|
||||
#import "GBPanel.h"
|
||||
|
||||
@interface GBCheatSearchController() <NSTableViewDelegate, NSTableViewDataSource>
|
||||
@property IBOutlet NSPopUpButton *dataTypeButton;
|
||||
@property IBOutlet NSPopUpButton *conditionTypeButton;
|
||||
@property IBOutlet NSTextField *operandField;
|
||||
@property IBOutlet NSTextField *conditionField;
|
||||
@property IBOutlet NSTextField *resultsLabel;
|
||||
@property (strong) IBOutlet NSButton *addCheatButton;
|
||||
@end
|
||||
|
||||
@implementation GBCheatSearchController
|
||||
{
|
||||
__weak Document *_document;
|
||||
size_t _resultCount;
|
||||
GB_cheat_search_result_t *_results;
|
||||
GBPanel *_window;
|
||||
}
|
||||
|
||||
+ (instancetype)controllerWithDocument:(Document *)document
|
||||
{
|
||||
GBCheatSearchController *ret = [[self alloc] init];
|
||||
ret->_document = document;
|
||||
NSArray *objects;
|
||||
[[NSBundle mainBundle] loadNibNamed:@"CheatSearch" owner:ret topLevelObjects:&objects];
|
||||
ret->_resultsLabel.stringValue = @"";
|
||||
ret->_resultsLabel.cell.backgroundStyle = NSBackgroundStyleRaised;
|
||||
ret->_window.ownerWindow = document.mainWindow;
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (IBAction)reset:(id)sender
|
||||
{
|
||||
_dataTypeButton.enabled = true;
|
||||
[_document performAtomicBlock:^{
|
||||
GB_cheat_search_reset(_document.gb);
|
||||
}];
|
||||
_resultCount = 0;
|
||||
if (_results) {
|
||||
free(_results);
|
||||
_results = NULL;
|
||||
}
|
||||
[_tableView reloadData];
|
||||
_resultsLabel.stringValue = @"";
|
||||
}
|
||||
|
||||
- (IBAction)search:(id)sender
|
||||
{
|
||||
// Dispatch to work around firstResponder oddities
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([sender isKindOfClass:[NSTextField class]]) {
|
||||
// Action sent by losing focus rather than pressing enter
|
||||
if (![sender currentEditor]) return;
|
||||
}
|
||||
_dataTypeButton.enabled = false;
|
||||
[_document performAtomicBlock:^{
|
||||
__block bool success = false;
|
||||
NSString *error = [_document captureOutputForBlock:^{
|
||||
success = GB_cheat_search_filter(_document.gb, _conditionField.stringValue.UTF8String, _dataTypeButton.selectedTag);
|
||||
}];
|
||||
if (!success) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[GBWarningPopover popoverWithContents:error onView:_conditionField];
|
||||
NSBeep();
|
||||
});
|
||||
return;
|
||||
}
|
||||
_resultCount = GB_cheat_search_result_count(_document.gb);
|
||||
_results = malloc(sizeof(*_results) * _resultCount);
|
||||
GB_cheat_search_get_results(_document.gb, _results);
|
||||
}];
|
||||
if (_resultCount == 0) {
|
||||
_dataTypeButton.enabled = true;
|
||||
_resultsLabel.stringValue = @"No results.";
|
||||
}
|
||||
else {
|
||||
_resultsLabel.stringValue = [NSString stringWithFormat:@"%@ result%s",
|
||||
[NSNumberFormatter localizedStringFromNumber:@(_resultCount)
|
||||
numberStyle:NSNumberFormatterDecimalStyle],
|
||||
_resultCount > 1? "s" : ""];
|
||||
}
|
||||
[_tableView reloadData];
|
||||
});
|
||||
}
|
||||
|
||||
- (IBAction)conditionChanged:(id)sender
|
||||
{
|
||||
unsigned index = [_conditionTypeButton indexOfSelectedItem];
|
||||
_conditionField.enabled = index == 11;
|
||||
_operandField.enabled = index >= 1 && index <= 6;
|
||||
switch ([_conditionTypeButton indexOfSelectedItem]) {
|
||||
case 0: _conditionField.stringValue = @"1"; break;
|
||||
case 1: _conditionField.stringValue = [NSString stringWithFormat:@"new == (%@)", _operandField.stringValue]; break;
|
||||
case 2: _conditionField.stringValue = [NSString stringWithFormat:@"new != (%@)", _operandField.stringValue]; break;
|
||||
case 3: _conditionField.stringValue = [NSString stringWithFormat:@"new > (%@)", _operandField.stringValue]; break;
|
||||
case 4: _conditionField.stringValue = [NSString stringWithFormat:@"new >= (%@)", _operandField.stringValue]; break;
|
||||
case 5: _conditionField.stringValue = [NSString stringWithFormat:@"new < (%@)", _operandField.stringValue]; break;
|
||||
case 6: _conditionField.stringValue = [NSString stringWithFormat:@"new <= (%@)", _operandField.stringValue]; break;
|
||||
case 7: _conditionField.stringValue = @"new != old"; break;
|
||||
case 8: _conditionField.stringValue = @"new == old"; break;
|
||||
case 9: _conditionField.stringValue = @"new > old"; break;
|
||||
case 10: _conditionField.stringValue = @"new < old"; break;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||
{
|
||||
return _resultCount;
|
||||
}
|
||||
|
||||
- (uint8_t *)addressForRow:(unsigned)row
|
||||
{
|
||||
uint8_t *base;
|
||||
uint32_t offset;
|
||||
if (_results[row].addr < 0xc000) {
|
||||
base = GB_get_direct_access(_document.gb, GB_DIRECT_ACCESS_CART_RAM, NULL, NULL);
|
||||
offset = (_results[row].addr & 0x1FFF) + _results[row].bank * 0x2000;
|
||||
}
|
||||
else if (_results[row].addr < 0xe000) {
|
||||
base = GB_get_direct_access(_document.gb, GB_DIRECT_ACCESS_RAM, NULL, NULL);
|
||||
offset = (_results[row].addr & 0xFFF) + _results[row].bank * 0x1000;
|
||||
}
|
||||
else {
|
||||
base = GB_get_direct_access(_document.gb, GB_DIRECT_ACCESS_HRAM, NULL, NULL);
|
||||
offset = (_results[row].addr & 0x7F);
|
||||
}
|
||||
return base + offset;
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
switch ([[tableView tableColumns] indexOfObject:tableColumn]) {
|
||||
case 0:
|
||||
return [NSString stringWithFormat:@"$%02x:$%04x", _results[row].bank, _results[row].addr];
|
||||
case 1:
|
||||
if (_dataTypeButton.selectedTag & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) {
|
||||
return [NSString stringWithFormat:@"$%04x", _results[row].value];
|
||||
}
|
||||
return [NSString stringWithFormat:@"$%02x", _results[row].value];
|
||||
default: {
|
||||
const uint8_t *data = [self addressForRow:row];
|
||||
GB_cheat_search_data_type_t dataType = _dataTypeButton.selectedTag;
|
||||
uint16_t value = data[0];
|
||||
if (!(dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT)) {
|
||||
return [NSString stringWithFormat:@"$%02x", value];
|
||||
}
|
||||
value |= data[1] << 8;
|
||||
if ((dataType & GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT)) {
|
||||
value = __builtin_bswap16(value);
|
||||
}
|
||||
return [NSString stringWithFormat:@"$%04x", value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(NSTableView *)tableView setObjectValue:(NSString *)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
[_document performAtomicBlock:^{
|
||||
__block bool success = false;
|
||||
__block uint16_t value;
|
||||
NSString *error = [_document captureOutputForBlock:^{
|
||||
success = !GB_debugger_evaluate(_document.gb, object.UTF8String, &value, NULL);
|
||||
}];
|
||||
if (!success) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[GBWarningPopover popoverWithContents:error onView:tableView];
|
||||
NSBeep();
|
||||
});
|
||||
return;
|
||||
}
|
||||
uint8_t *dest = [self addressForRow:row];
|
||||
GB_cheat_search_data_type_t dataType = _dataTypeButton.selectedTag;
|
||||
if (dataType & GB_CHEAT_SEARCH_DATA_TYPE_BE_BIT) {
|
||||
value = __builtin_bswap16(value);
|
||||
}
|
||||
dest[0] = value;
|
||||
if (dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) {
|
||||
dest[1] = value >> 8;
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[tableView reloadData];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)obj
|
||||
{
|
||||
[self conditionChanged:nil];
|
||||
}
|
||||
|
||||
- (IBAction)addCheat:(id)sender
|
||||
{
|
||||
GB_cheat_search_result_t *result = _results + _tableView.selectedRow;
|
||||
uint8_t *data = [self addressForRow:_tableView.selectedRow];
|
||||
GB_cheat_search_data_type_t dataType = _dataTypeButton.selectedTag;
|
||||
size_t rowToSelect = 0;
|
||||
GB_get_cheats(_document.gb, &rowToSelect);
|
||||
[_document performAtomicBlock:^{
|
||||
GB_add_cheat(_document.gb,
|
||||
(dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT)? "New Cheat (Part 1)" : "New Cheat",
|
||||
result->addr, result->bank,
|
||||
*data,
|
||||
0, false,
|
||||
true);
|
||||
if (dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT) {
|
||||
GB_add_cheat(_document.gb,
|
||||
(dataType & GB_CHEAT_SEARCH_DATA_TYPE_16BIT)? "New Cheat (Part 2)" : "New Cheat",
|
||||
result->addr + 1, result->bank,
|
||||
data[1],
|
||||
0, false,
|
||||
true);
|
||||
}
|
||||
GB_set_cheats_enabled(_document.gb, true);
|
||||
}];
|
||||
[_document.cheatsWindow makeKeyAndOrderFront:nil];
|
||||
[_document.cheatWindowController.cheatsTable reloadData];
|
||||
[_document.cheatWindowController.cheatsTable selectRow:rowToSelect byExtendingSelection:false];
|
||||
[_document.cheatWindowController.cheatsTable.delegate tableViewSelectionDidChange:nil];
|
||||
}
|
||||
|
||||
- (void)tableViewSelectionDidChange:(NSNotification *)notification
|
||||
{
|
||||
_addCheatButton.enabled = _tableView.numberOfSelectedRows != 0;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_results) free(_results);
|
||||
}
|
||||
|
||||
@end
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
@implementation GBCheatTextView
|
||||
|
||||
- (bool)_insertText:(NSString *)string replacementRange:(NSRange)range
|
||||
- (bool)_internalInsertText:(NSString *)string replacementRange:(NSRange)range
|
||||
{
|
||||
if (range.location == NSNotFound) {
|
||||
range = self.selectedRange;
|
||||
|
@ -60,19 +60,19 @@
|
|||
return true;
|
||||
}
|
||||
if (([string isEqualToString:@"$"] || [string isEqualToString:@":"]) && range.length == 0 && range.location == 0) {
|
||||
if ([self _insertText:@"$00:" replacementRange:range]) {
|
||||
if ([self _internalInsertText:@"$00:" replacementRange:range]) {
|
||||
self.selectedRange = NSMakeRange(1, 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ([string isEqualToString:@":"] && range.length + range.location == self.string.length) {
|
||||
if ([self _insertText:@":$0" replacementRange:range]) {
|
||||
if ([self _internalInsertText:@":$0" replacementRange:range]) {
|
||||
self.selectedRange = NSMakeRange(self.string.length - 2, 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ([string isEqualToString:@"$"]) {
|
||||
if ([self _insertText:@"$0" replacementRange:range]) {
|
||||
if ([self _internalInsertText:@"$0" replacementRange:range]) {
|
||||
self.selectedRange = NSMakeRange(range.location + 1, 1);
|
||||
return true;
|
||||
}
|
||||
|
@ -88,8 +88,10 @@
|
|||
|
||||
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
|
||||
{
|
||||
if (![self _insertText:string replacementRange:replacementRange]) {
|
||||
NSBeep();
|
||||
if (![self _internalInsertText:string replacementRange:replacementRange]) {
|
||||
if (![self _internalInsertText:[@"$" stringByAppendingString:string] replacementRange:replacementRange]) {
|
||||
NSBeep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
return nil;
|
||||
}
|
||||
|
||||
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
size_t cheatCount;
|
||||
GB_gameboy_t *gb = self.document.gameboy;
|
||||
|
@ -58,7 +58,7 @@
|
|||
return @NO;
|
||||
|
||||
case 2:
|
||||
return @"Add Cheat...";
|
||||
return @"Add Cheat…";
|
||||
|
||||
case 3:
|
||||
return @"";
|
||||
|
@ -92,14 +92,18 @@
|
|||
self.importCodeField.stringValue.UTF8String,
|
||||
self.importDescriptionField.stringValue.UTF8String,
|
||||
true)) {
|
||||
self.importCodeField.stringValue = @"";
|
||||
self.importDescriptionField.stringValue = @"";
|
||||
[self.cheatsTable reloadData];
|
||||
[self tableViewSelectionDidChange:nil];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.importCodeField.stringValue = @"";
|
||||
self.importDescriptionField.stringValue = @"";
|
||||
[self.cheatsTable reloadData];
|
||||
[self tableViewSelectionDidChange:nil];
|
||||
});
|
||||
}
|
||||
else {
|
||||
NSBeep();
|
||||
[GBWarningPopover popoverWithContents:@"This code is not a valid GameShark or GameGenie code" onView:self.importCodeField];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSBeep();
|
||||
[GBWarningPopover popoverWithContents:@"This code is not a valid GameShark or Game Genie code" onView:self.importCodeField];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class GBDocument;
|
||||
@interface GBDebuggerButton : NSButton
|
||||
@property (weak) IBOutlet NSTextField *textField;
|
||||
@end
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#import "GBDebuggerButton.h"
|
||||
|
||||
@implementation GBDebuggerButton
|
||||
{
|
||||
NSTrackingArea *_trackingArea;
|
||||
}
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
self.toolTip = self.title;
|
||||
self.imagePosition = NSImageOnly; // Newer versions of AppKit refuse to respect the value from the nib file
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)event
|
||||
{
|
||||
if (@available(macOS 10.10, *)) {
|
||||
NSDictionary *attributes = @{
|
||||
NSForegroundColorAttributeName: [NSColor colorWithWhite:1.0 alpha:0.5],
|
||||
NSFontAttributeName: self.textField.font
|
||||
};
|
||||
self.textField.placeholderAttributedString =
|
||||
[[NSAttributedString alloc] initWithString:self.alternateTitle attributes:attributes];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)event
|
||||
{
|
||||
if (@available(macOS 10.10, *)) {
|
||||
if ([self.textField.placeholderAttributedString.string isEqualToString:self.alternateTitle]) {
|
||||
self.textField.placeholderAttributedString = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[super updateTrackingAreas];
|
||||
if (_trackingArea) {
|
||||
[self removeTrackingArea:_trackingArea];
|
||||
}
|
||||
|
||||
_trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:_trackingArea];
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBDeleteButtonCell : NSButtonCell
|
||||
|
||||
@end
|
|
@ -0,0 +1,30 @@
|
|||
#import "GBDeleteButtonCell.h"
|
||||
|
||||
@implementation GBDeleteButtonCell
|
||||
|
||||
// Image scaling is broken on some older macOS versions
|
||||
- (void)drawImage:(NSImage *)image withFrame:(NSRect)frame inView:(NSView *)controlView
|
||||
{
|
||||
double size = 13;
|
||||
unsigned offset = 1;
|
||||
if (@available(macOS 10.10, *)) {
|
||||
size = 15;
|
||||
offset = 0;
|
||||
}
|
||||
frame.origin.x += round((frame.size.width - size) / 2) + offset;
|
||||
frame.origin.y += round((frame.size.height - size) / 2) - offset;
|
||||
frame.size.width = frame.size.height = size;
|
||||
[super drawImage:image withFrame:frame inView:controlView];
|
||||
}
|
||||
|
||||
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
|
||||
{
|
||||
[self drawImage:self.image withFrame:cellFrame inView:controlView];
|
||||
}
|
||||
|
||||
-(void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@end
|
|
@ -163,7 +163,7 @@ void main(void) {\n\
|
|||
|
||||
/* OpenGL is black magic. Closing one view causes others to be completely black unless we reload their shaders */
|
||||
/* We're probably not freeing thing in the right place. */
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged$DefaultsObserver" object:nil];
|
||||
}
|
||||
|
||||
+ (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#import <Core/gb.h>
|
||||
#import <HexFiend/HFRepresenter.h>
|
||||
|
||||
|
||||
@interface GBHexStatusBarRepresenter : HFRepresenter
|
||||
@property GB_gameboy_t *gb;
|
||||
@property (nonatomic) bool useDecimalLength;
|
||||
@property (nonatomic) uint16_t bankForDescription;
|
||||
@property (nonatomic) uint16_t baseAddress;
|
||||
@end
|
|
@ -0,0 +1,220 @@
|
|||
#import "GBHexStatusBarRepresenter.h"
|
||||
#import <HexFiend/HFFunctions.h>
|
||||
|
||||
@interface GBHexStatusBarView : NSView
|
||||
{
|
||||
NSCell *_cell;
|
||||
NSSize _cellSize;
|
||||
GBHexStatusBarRepresenter *_representer;
|
||||
NSDictionary *_cellAttributes;
|
||||
bool _registeredForAppNotifications;
|
||||
}
|
||||
|
||||
- (void)setRepresenter:(GBHexStatusBarRepresenter *)rep;
|
||||
- (void)setString:(NSString *)string;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation GBHexStatusBarView
|
||||
|
||||
- (void)_sharedInitStatusBarView
|
||||
{
|
||||
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
[style setAlignment:NSCenterTextAlignment];
|
||||
style.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
_cellAttributes = @{
|
||||
NSForegroundColorAttributeName: [NSColor windowFrameTextColor],
|
||||
NSFontAttributeName: [NSFont labelFontOfSize:[NSFont smallSystemFontSize]],
|
||||
NSParagraphStyleAttributeName: style,
|
||||
};
|
||||
_cell = [[NSCell alloc] initTextCell:@""];
|
||||
[_cell setAlignment:NSCenterTextAlignment];
|
||||
[_cell setBackgroundStyle:NSBackgroundStyleRaised];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
[self _sharedInitStatusBarView];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
[self _sharedInitStatusBarView];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isFlipped
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)setRepresenter:(GBHexStatusBarRepresenter *)rep
|
||||
{
|
||||
_representer = rep;
|
||||
}
|
||||
|
||||
- (void)setString:(NSString *)string
|
||||
{
|
||||
[_cell setAttributedStringValue:[[NSAttributedString alloc] initWithString:string attributes:_cellAttributes]];
|
||||
_cellSize = [_cell cellSize];
|
||||
[self setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)clip
|
||||
{
|
||||
NSRect bounds = [self bounds];
|
||||
NSRect cellRect = NSMakeRect(NSMinX(bounds), HFCeil(NSMidY(bounds) - _cellSize.height / 2), NSWidth(bounds), _cellSize.height);
|
||||
[_cell drawWithFrame:cellRect inView:self];
|
||||
}
|
||||
|
||||
- (void)setFrame:(NSRect)frame
|
||||
{
|
||||
[super setFrame:frame];
|
||||
[self.window setContentBorderThickness:frame.origin.y + frame.size.height forEdge:NSMinYEdge];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
_representer.useDecimalLength ^= true;
|
||||
}
|
||||
|
||||
- (void)windowDidChangeKeyStatus:(NSNotification *)note
|
||||
{
|
||||
[self setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !_registeredForAppNotifications);
|
||||
_registeredForAppNotifications = true;
|
||||
[self.window setContentBorderThickness:self.frame.origin.y + self.frame.size.height forEdge:NSMinYEdge];
|
||||
[super viewDidMoveToWindow];
|
||||
}
|
||||
|
||||
- (void)viewWillMoveToWindow:(NSWindow *)newWindow
|
||||
{
|
||||
HFUnregisterViewForWindowAppearanceChanges(self, NO);
|
||||
[super viewWillMoveToWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
HFUnregisterViewForWindowAppearanceChanges(self, _registeredForAppNotifications);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GBHexStatusBarRepresenter
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSView *)createView {
|
||||
GBHexStatusBarView *view = [[GBHexStatusBarView alloc] initWithFrame:NSMakeRect(0, 0, 100, 18)];
|
||||
[view setRepresenter:self];
|
||||
[view setAutoresizingMask:NSViewWidthSizable];
|
||||
return view;
|
||||
}
|
||||
|
||||
- (NSString *)describeLength:(unsigned long long)length
|
||||
{
|
||||
if (self.useDecimalLength) {
|
||||
return [NSString stringWithFormat:@"%llu byte%s", length, length == 1 ? "" : "s"];
|
||||
}
|
||||
return [NSString stringWithFormat:@"$%llX byte%s", length, length == 1 ? "" : "s"];
|
||||
}
|
||||
|
||||
- (NSString *)describeOffset:(unsigned long long)offset isRangeEnd:(bool)isRangeEnd
|
||||
{
|
||||
if (!_gb) {
|
||||
return [NSString stringWithFormat:@"$%llX", offset];
|
||||
}
|
||||
return @(GB_debugger_describe_address(_gb, offset + _baseAddress, _bankForDescription, false, isRangeEnd));
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)stringForEmptySelectionAtOffset:(unsigned long long)offset length:(unsigned long long)length
|
||||
{
|
||||
return [self describeOffset:offset isRangeEnd:false];
|
||||
}
|
||||
|
||||
- (NSString *)stringForSingleByteSelectionAtOffset:(unsigned long long)offset length:(unsigned long long)length
|
||||
{
|
||||
return [NSString stringWithFormat:@"Byte %@ selected", [self describeOffset:offset isRangeEnd:false]];
|
||||
}
|
||||
|
||||
- (NSString *)stringForSingleRangeSelection:(HFRange)range length:(unsigned long long)length
|
||||
{
|
||||
return [NSString stringWithFormat:@"Range %@ to %@ selected (%@)",
|
||||
[self describeOffset:range.location isRangeEnd:false],
|
||||
[self describeOffset:range.location + range.length isRangeEnd:true],
|
||||
[self describeLength:range.length]];
|
||||
}
|
||||
|
||||
|
||||
- (void)updateString
|
||||
{
|
||||
NSString *string = nil;
|
||||
HFController *controller = [self controller];
|
||||
if (controller) {
|
||||
unsigned long long length = [controller contentsLength];
|
||||
NSArray *ranges = [controller selectedContentsRanges];
|
||||
NSUInteger rangeCount = [ranges count];
|
||||
if (rangeCount == 1) {
|
||||
HFRange range = [ranges[0] HFRange];
|
||||
if (range.length == 0) {
|
||||
string = [self stringForEmptySelectionAtOffset:range.location length:length];
|
||||
}
|
||||
else if (range.length == 1) {
|
||||
string = [self stringForSingleByteSelectionAtOffset:range.location length:length];
|
||||
}
|
||||
else {
|
||||
string = [self stringForSingleRangeSelection:range length:length];
|
||||
}
|
||||
}
|
||||
else {
|
||||
string = @"Multiple ranges selected";
|
||||
}
|
||||
}
|
||||
if (! string) string = @"";
|
||||
[[self view] setString:string];
|
||||
}
|
||||
|
||||
- (void)setUseDecimalLength:(bool)useDecimalLength
|
||||
{
|
||||
_useDecimalLength = useDecimalLength;
|
||||
[self updateString];
|
||||
}
|
||||
|
||||
- (void)setBaseAddress:(uint16_t)baseAddress
|
||||
{
|
||||
_baseAddress = baseAddress;
|
||||
[self updateString];
|
||||
}
|
||||
|
||||
- (void) setBankForDescription:(uint16_t)bankForDescription
|
||||
{
|
||||
_bankForDescription = bankForDescription;
|
||||
[self updateString];
|
||||
}
|
||||
|
||||
- (void)controllerDidChange:(HFControllerPropertyBits)bits
|
||||
{
|
||||
if (bits & (HFControllerContentLength | HFControllerSelectedRanges)) {
|
||||
[self updateString];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSPoint)defaultLayoutPosition
|
||||
{
|
||||
return NSMakePoint(0, -1);
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,9 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface NSSlider (GBHueSlider)
|
||||
-(NSColor *)colorValue;
|
||||
@end
|
||||
|
||||
@interface GBHueSliderCell : NSSliderCell
|
||||
-(NSColor *)colorValue;
|
||||
@end
|
|
@ -0,0 +1,113 @@
|
|||
#import "GBHueSliderCell.h"
|
||||
|
||||
@interface NSSliderCell(privateAPI)
|
||||
- (double)_normalizedDoubleValue;
|
||||
@end
|
||||
|
||||
@implementation GBHueSliderCell
|
||||
{
|
||||
bool _drawingTrack;
|
||||
}
|
||||
|
||||
-(NSColor *)colorValue
|
||||
{
|
||||
double hue = self.doubleValue / 360.0;
|
||||
double r = 0, g = 0, b =0 ;
|
||||
double t = fmod(hue * 6, 1);
|
||||
switch ((int)(hue * 6) % 6) {
|
||||
case 0:
|
||||
r = 1;
|
||||
g = t;
|
||||
break;
|
||||
case 1:
|
||||
r = 1 - t;
|
||||
g = 1;
|
||||
break;
|
||||
case 2:
|
||||
g = 1;
|
||||
b = t;
|
||||
break;
|
||||
case 3:
|
||||
g = 1 - t;
|
||||
b = 1;
|
||||
break;
|
||||
case 4:
|
||||
b = 1;
|
||||
r = t;
|
||||
break;
|
||||
case 5:
|
||||
b = 1 - t;
|
||||
r = 1;
|
||||
break;
|
||||
}
|
||||
return [NSColor colorWithRed:r green:g blue:b alpha:1.0];
|
||||
}
|
||||
|
||||
-(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;
|
||||
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];
|
||||
[path stroke];
|
||||
}
|
||||
|
||||
-(double)_normalizedDoubleValue
|
||||
{
|
||||
if (_drawingTrack) return 0;
|
||||
return [super _normalizedDoubleValue];
|
||||
}
|
||||
|
||||
-(void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped
|
||||
{
|
||||
if (!self.enabled) {
|
||||
[super drawBarInside:rect flipped:flipped];
|
||||
return;
|
||||
}
|
||||
|
||||
_drawingTrack = true;
|
||||
[super drawBarInside:rect flipped:flipped];
|
||||
_drawingTrack = false;
|
||||
|
||||
NSGradient *gradient = [[NSGradient alloc] initWithColors:@[
|
||||
[NSColor redColor],
|
||||
[NSColor yellowColor],
|
||||
[NSColor greenColor],
|
||||
[NSColor cyanColor],
|
||||
[NSColor blueColor],
|
||||
[NSColor magentaColor],
|
||||
[NSColor redColor],
|
||||
]];
|
||||
|
||||
rect.origin.y += rect.size.height / 2 - 0.5;
|
||||
rect.size.height = 1;
|
||||
rect.size.width -= 2;
|
||||
rect.origin.x += 1;
|
||||
[[NSColor redColor] set];
|
||||
NSRectFill(rect);
|
||||
|
||||
rect.size.width -= self.knobThickness + 2;
|
||||
rect.origin.x += self.knobThickness / 2 - 1;
|
||||
|
||||
[gradient drawInRect:rect angle:0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSSlider (GBHueSlider)
|
||||
- (NSColor *)colorValue
|
||||
{
|
||||
return ((GBHueSliderCell *)self.cell).colorValue;
|
||||
}
|
||||
@end
|
|
@ -10,18 +10,18 @@
|
|||
}
|
||||
@end
|
||||
|
||||
@implementation GBImageView
|
||||
{
|
||||
NSTrackingArea *trackingArea;
|
||||
}
|
||||
@interface GBGridView : NSView
|
||||
@end
|
||||
|
||||
@implementation GBGridView
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
|
||||
[super drawRect:dirtyRect];
|
||||
CGFloat y_ratio = self.frame.size.height / self.image.size.height;
|
||||
CGFloat x_ratio = self.frame.size.width / self.image.size.width;
|
||||
for (GBImageViewGridConfiguration *conf in self.verticalGrids) {
|
||||
GBImageView *parent = (GBImageView *)self.superview;
|
||||
|
||||
CGFloat y_ratio = parent.frame.size.height / parent.image.size.height;
|
||||
CGFloat x_ratio = parent.frame.size.width / parent.image.size.width;
|
||||
for (GBImageViewGridConfiguration *conf in parent.verticalGrids) {
|
||||
[conf.color set];
|
||||
for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) {
|
||||
NSBezierPath *line = [NSBezierPath bezierPath];
|
||||
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
for (GBImageViewGridConfiguration *conf in self.horizontalGrids) {
|
||||
for (GBImageViewGridConfiguration *conf in parent.horizontalGrids) {
|
||||
[conf.color set];
|
||||
for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) {
|
||||
NSBezierPath *line = [NSBezierPath bezierPath];
|
||||
|
@ -43,11 +43,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (self.displayScrollRect) {
|
||||
if (parent.displayScrollRect) {
|
||||
NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite];
|
||||
for (unsigned x = 0; x < 2; x++) {
|
||||
for (unsigned y = 0; y < 2; y++) {
|
||||
NSRect rect = self.scrollRect;
|
||||
NSRect rect = parent.scrollRect;
|
||||
rect.origin.x *= x_ratio;
|
||||
rect.origin.y *= y_ratio;
|
||||
rect.size.width *= x_ratio;
|
||||
|
@ -56,7 +56,7 @@
|
|||
|
||||
rect.origin.x -= self.frame.size.width * x;
|
||||
rect.origin.y += self.frame.size.height * y;
|
||||
|
||||
|
||||
|
||||
NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect];
|
||||
[path appendBezierPath:subpath];
|
||||
|
@ -72,36 +72,62 @@
|
|||
[path stroke];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation GBImageView
|
||||
{
|
||||
NSTrackingArea *_trackingArea;
|
||||
GBGridView *_gridView;
|
||||
NSRect _scrollRect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
self.wantsLayer = true;
|
||||
_gridView = [[GBGridView alloc] initWithFrame:self.bounds];
|
||||
_gridView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
[self addSubview:_gridView];
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)viewWillDraw
|
||||
{
|
||||
[super viewWillDraw];
|
||||
for (CALayer *layer in self.layer.sublayers) {
|
||||
layer.magnificationFilter = kCAFilterNearest;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHorizontalGrids:(NSArray *)horizontalGrids
|
||||
{
|
||||
self->_horizontalGrids = horizontalGrids;
|
||||
[self setNeedsDisplay];
|
||||
[_gridView setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
- (void)setVerticalGrids:(NSArray *)verticalGrids
|
||||
{
|
||||
self->_verticalGrids = verticalGrids;
|
||||
[self setNeedsDisplay];
|
||||
[_gridView setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
- (void)setDisplayScrollRect:(bool)displayScrollRect
|
||||
{
|
||||
self->_displayScrollRect = displayScrollRect;
|
||||
[self setNeedsDisplay];
|
||||
[_gridView setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (trackingArea != nil) {
|
||||
[self removeTrackingArea:trackingArea];
|
||||
if (_trackingArea != nil) {
|
||||
[self removeTrackingArea:_trackingArea];
|
||||
}
|
||||
|
||||
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
_trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:trackingArea];
|
||||
[self addTrackingArea:_trackingArea];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)theEvent
|
||||
|
@ -124,4 +150,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)setScrollRect:(NSRect)scrollRect
|
||||
{
|
||||
if (memcmp(&scrollRect, &_scrollRect, sizeof(scrollRect)) != 0) {
|
||||
_scrollRect = scrollRect;
|
||||
[_gridView setNeedsDisplay:true];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRect)scrollRect
|
||||
{
|
||||
return _scrollRect;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
@interface GBJoyConManager : NSObject<JOYListener, NSTableViewDataSource, NSTableViewDelegate>
|
||||
+ (instancetype)sharedInstance;
|
||||
- (IBAction)autopair:(id)sender;
|
||||
|
||||
@property (nonatomic) bool arrangementMode;
|
||||
@property (weak) IBOutlet NSTableView *tableView;
|
||||
@end
|
||||
|
|
@ -0,0 +1,303 @@
|
|||
#import "GBJoyConManager.h"
|
||||
#import "GBTintedImageCell.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation GBJoyConManager
|
||||
{
|
||||
GBTintedImageCell *_tintedImageCell;
|
||||
NSImageCell *_imageCell;
|
||||
NSMutableDictionary<NSString *, NSString *> *_pairings;
|
||||
NSMutableDictionary<NSString *, NSNumber *> *_gripSettings;
|
||||
bool _unpairing;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static GBJoyConManager *manager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[self alloc] _init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (NSArray <JOYController *> *)joycons
|
||||
{
|
||||
NSMutableArray *ret = [[JOYController allControllers] mutableCopy];
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
if (controller.joyconType == JOYJoyConTypeNone) {
|
||||
[ret removeObject:controller];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self.class sharedInstance];
|
||||
}
|
||||
|
||||
- (instancetype) _init
|
||||
{
|
||||
self = [super init];
|
||||
_imageCell = [[NSImageCell alloc] init];
|
||||
_tintedImageCell = [[GBTintedImageCell alloc] init];
|
||||
if (@available(macOS 10.14, *)) {
|
||||
_tintedImageCell.tint = [NSColor controlAccentColor];
|
||||
}
|
||||
else {
|
||||
_tintedImageCell.tint = [NSColor selectedMenuItemColor];
|
||||
}
|
||||
_pairings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConPairings"] ?: @{} mutableCopy];
|
||||
_gripSettings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConGrips"] ?: @{} mutableCopy];
|
||||
|
||||
// Sanity check the pairings
|
||||
for (NSString *key in _pairings) {
|
||||
if (![_pairings[_pairings[key]] isEqualToString:key]) {
|
||||
[_pairings removeAllObjects];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[JOYController registerListener:self];
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
[self controllerConnected:controller];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||
{
|
||||
return self.joycons.count;
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
if (row >= [self numberOfRowsInTableView:tableView]) return nil;
|
||||
|
||||
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
|
||||
JOYController *controller = self.joycons[row];
|
||||
switch (columnIndex) {
|
||||
case 0: {
|
||||
switch (controller.joyconType) {
|
||||
case JOYJoyConTypeNone:
|
||||
return nil;
|
||||
case JOYJoyConTypeLeft:
|
||||
return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConLeftTemplate", controller.usesHorizontalJoyConGrip? "Horizontal" :""]];
|
||||
case JOYJoyConTypeRight:
|
||||
return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConRightTemplate", controller.usesHorizontalJoyConGrip? "Horizontal" :""]];
|
||||
case JOYJoyConTypeDual:
|
||||
return [NSImage imageNamed:@"JoyConDualTemplate"];
|
||||
}
|
||||
}
|
||||
case 1: {
|
||||
NSMutableAttributedString *ret = [[NSMutableAttributedString alloc] initWithString:controller.deviceName
|
||||
attributes:@{NSFontAttributeName:
|
||||
[NSFont systemFontOfSize:[NSFont systemFontSize]]}];
|
||||
|
||||
[ret appendAttributedString:[[NSAttributedString alloc] initWithString:[@"\n" stringByAppendingString:controller.uniqueID]
|
||||
attributes:@{NSFontAttributeName:
|
||||
[NSFont systemFontOfSize:[NSFont smallSystemFontSize]],
|
||||
NSForegroundColorAttributeName:[NSColor disabledControlTextColor]}]];
|
||||
return ret;
|
||||
}
|
||||
case 2:
|
||||
return @([(_gripSettings[controller.uniqueID] ?: @(-1)) unsignedIntValue] + 1);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)updateGripForController:(JOYController *)controller
|
||||
{
|
||||
NSNumber *grip = _gripSettings[controller.uniqueID];
|
||||
if (!grip) {
|
||||
controller.usesHorizontalJoyConGrip = [[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConsDefaultsToHorizontal"];
|
||||
return;
|
||||
}
|
||||
controller.usesHorizontalJoyConGrip = [grip unsignedIntValue] == 1;
|
||||
}
|
||||
|
||||
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
|
||||
if (columnIndex != 2) return;
|
||||
if (row >= [self numberOfRowsInTableView:tableView]) return;
|
||||
JOYController *controller = self.joycons[row];
|
||||
if (controller.joyconType == JOYJoyConTypeDual) {
|
||||
return;
|
||||
}
|
||||
switch ([object unsignedIntValue]) {
|
||||
case 0:
|
||||
[_gripSettings removeObjectForKey:controller.uniqueID];
|
||||
break;
|
||||
case 1:
|
||||
_gripSettings[controller.uniqueID] = @(0);
|
||||
break;
|
||||
case 2:
|
||||
_gripSettings[controller.uniqueID] = @(1);
|
||||
break;
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] setObject:_gripSettings forKey:@"GBJoyConGrips"];
|
||||
[self updateGripForController:controller];
|
||||
}
|
||||
|
||||
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
if (row >= [self numberOfRowsInTableView:tableView]) return [[NSCell alloc] init];
|
||||
|
||||
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
|
||||
if (columnIndex == 2) {
|
||||
JOYCombinedController *controller = (JOYCombinedController *)self.joycons[row];
|
||||
if (controller.joyconType == JOYJoyConTypeDual) {
|
||||
NSButtonCell *cell = [[NSButtonCell alloc] initTextCell:@"Separate Joy-Cons"];
|
||||
cell.bezelStyle = NSBezelStyleRounded;
|
||||
cell.action = @selector(invoke);
|
||||
id block = ^(void) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
for (JOYController *child in controller.children) {
|
||||
[_pairings removeObjectForKey:child.uniqueID];
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"];
|
||||
_unpairing = true;
|
||||
[controller breakApart];
|
||||
_unpairing = false;
|
||||
});
|
||||
};
|
||||
// To retain the block
|
||||
objc_setAssociatedObject(cell, @selector(breakApart), block, OBJC_ASSOCIATION_RETAIN);
|
||||
cell.target = block;
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
if (columnIndex == 0) {
|
||||
JOYController *controller = self.joycons[row];
|
||||
for (JOYButton *button in controller.buttons) {
|
||||
if (button.isPressed) {
|
||||
return _tintedImageCell;
|
||||
}
|
||||
}
|
||||
return _imageCell;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)controllerConnected:(JOYController *)controller
|
||||
{
|
||||
[self updateGripForController:controller];
|
||||
for (JOYController *partner in [JOYController allControllers]) {
|
||||
if ([partner.uniqueID isEqualToString:_pairings[controller.uniqueID]]) {
|
||||
[self pairJoyCon:controller withJoyCon:partner];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (controller.joyconType == JOYJoyConTypeLeft || controller.joyconType == JOYJoyConTypeRight) {
|
||||
[self autopair:nil];
|
||||
}
|
||||
if (_arrangementMode) {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)autopair:(id)sender
|
||||
{
|
||||
if (_unpairing) return;
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConAutoPair"]) return;
|
||||
NSArray<JOYController *> *controllers = [[JOYController allControllers] copy];
|
||||
for (JOYController *first in controllers) {
|
||||
if (_pairings[first.uniqueID]) continue; // Has an established partner
|
||||
if (first.joyconType != JOYJoyConTypeLeft) continue;
|
||||
for (JOYController *second in controllers) {
|
||||
if (_pairings[second.uniqueID]) continue; // Has an established partner
|
||||
if (second.joyconType != JOYJoyConTypeRight) continue;
|
||||
[self pairJoyCon:first withJoyCon:second];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_arrangementMode) {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controllerDisconnected:(JOYController *)controller
|
||||
{
|
||||
if (_arrangementMode) {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
|
||||
return columnIndex == 2;
|
||||
}
|
||||
|
||||
- (JOYCombinedController *)pairJoyCon:(JOYController *)first withJoyCon:(JOYController *)second
|
||||
{
|
||||
if (first.joyconType != JOYJoyConTypeLeft && first.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con
|
||||
if (second.joyconType != JOYJoyConTypeLeft && second.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con
|
||||
if (first.joyconType == second.joyconType) return nil; // Not a sensible pair
|
||||
|
||||
_pairings[first.uniqueID] = second.uniqueID;
|
||||
_pairings[second.uniqueID] = first.uniqueID;
|
||||
first.usesHorizontalJoyConGrip = false;
|
||||
second.usesHorizontalJoyConGrip = false;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"];
|
||||
return [[JOYCombinedController alloc] initWithChildren:@[first, second]];
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
|
||||
{
|
||||
if (!_arrangementMode) return;
|
||||
if (controller.joyconType == JOYJoyConTypeNone) return;
|
||||
[self.tableView setNeedsDisplay:true];
|
||||
if (controller.joyconType != JOYJoyConTypeLeft && controller.joyconType != JOYJoyConTypeRight) return;
|
||||
if (button.usage != JOYButtonUsageL1 && button.usage != JOYButtonUsageR1) return;
|
||||
|
||||
|
||||
// L or R were pressed on a single Joy-Con, try and pair available Joy-Cons
|
||||
JOYController *left = nil;
|
||||
JOYController *right = nil;
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
if (!left && controller.joyconType == JOYJoyConTypeLeft) {
|
||||
for (JOYButton *button in controller.buttons) {
|
||||
if (button.usage == JOYButtonUsageL1 && button.isPressed) {
|
||||
left = controller;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!right && controller.joyconType == JOYJoyConTypeRight) {
|
||||
for (JOYButton *button in controller.buttons) {
|
||||
if (button.usage == JOYButtonUsageR1 && button.isPressed) {
|
||||
right = controller;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (left && right) {
|
||||
[self pairJoyCon:left withJoyCon:right];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)toggleHorizontalDefault:(NSButton *)sender
|
||||
{
|
||||
for (JOYController *controller in self.joycons) {
|
||||
[self updateGripForController:controller];
|
||||
}
|
||||
if (_arrangementMode) {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setArrangementMode:(bool)arrangementMode
|
||||
{
|
||||
_arrangementMode = arrangementMode;
|
||||
if (arrangementMode) {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,4 +1,3 @@
|
|||
#define GB_INTERNAL // Todo: Some memory accesses are being done using the struct directly
|
||||
#import "GBMemoryByteArray.h"
|
||||
#import "GBCompleteByteSlice.h"
|
||||
|
||||
|
@ -32,69 +31,110 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (uint16_t)base
|
||||
{
|
||||
switch (_mode) {
|
||||
case GBMemoryEntireSpace: return 0;
|
||||
case GBMemoryROM: return 0;
|
||||
case GBMemoryVRAM: return 0x8000;
|
||||
case GBMemoryExternalRAM: return 0xA000;
|
||||
case GBMemoryRAM: return 0xC000;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range
|
||||
{
|
||||
__block uint16_t addr = (uint16_t) range.location;
|
||||
__block unsigned long long length = range.length;
|
||||
if (_mode == GBMemoryEntireSpace) {
|
||||
while (length) {
|
||||
*(dst++) = [_document readMemory:addr++];
|
||||
length--;
|
||||
}
|
||||
// Do everything in 0x1000 chunks, never cross a 0x1000 boundary
|
||||
if ((range.location & 0xF000) != ((range.location + range.length) & 0xF000)) {
|
||||
size_t partial = 0x1000 - (range.location & 0xFFF);
|
||||
[self copyBytes:dst + partial range:HFRangeMake(range.location + partial, range.length - partial)];
|
||||
range.length = partial;
|
||||
}
|
||||
else {
|
||||
[_document performAtomicBlock:^{
|
||||
unsigned char *_dst = dst;
|
||||
uint16_t bank_backup = 0;
|
||||
GB_gameboy_t *gb = _document.gameboy;
|
||||
switch (_mode) {
|
||||
case GBMemoryROM:
|
||||
bank_backup = gb->mbc_rom_bank;
|
||||
gb->mbc_rom_bank = self.selectedBank;
|
||||
break;
|
||||
case GBMemoryVRAM:
|
||||
bank_backup = gb->cgb_vram_bank;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->cgb_vram_bank = self.selectedBank;
|
||||
range.location += self.base;
|
||||
|
||||
GB_gameboy_t *gb = _document.gameboy;
|
||||
|
||||
switch (range.location >> 12) {
|
||||
case 0x0:
|
||||
case 0x1:
|
||||
case 0x2:
|
||||
case 0x3: {
|
||||
uint16_t bank;
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_ROM0, NULL, &bank);
|
||||
memcpy(dst, data + bank * 0x4000 + range.location, range.length);
|
||||
break;
|
||||
}
|
||||
case 0x4:
|
||||
case 0x5:
|
||||
case 0x6:
|
||||
case 0x7: {
|
||||
uint16_t bank;
|
||||
size_t size;
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_ROM, &size, &bank);
|
||||
if (_mode != GBMemoryEntireSpace) {
|
||||
bank = self.selectedBank & (size / 0x4000 - 1);
|
||||
}
|
||||
memcpy(dst, data + bank * 0x4000 + range.location - 0x4000, range.length);
|
||||
break;
|
||||
}
|
||||
case 0x8:
|
||||
case 0x9: {
|
||||
uint16_t bank;
|
||||
size_t size;
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_VRAM, &size, &bank);
|
||||
if (_mode != GBMemoryEntireSpace) {
|
||||
bank = self.selectedBank & (size / 0x2000 - 1);
|
||||
}
|
||||
memcpy(dst, data + bank * 0x2000 + range.location - 0x8000, range.length);
|
||||
break;
|
||||
}
|
||||
case 0xA:
|
||||
case 0xB: {
|
||||
// Some carts are special, use memory read directly in full mem mode
|
||||
if (_mode == GBMemoryEntireSpace) {
|
||||
case 0xF:
|
||||
slow_path:
|
||||
[_document performAtomicBlock:^{
|
||||
for (unsigned i = 0; i < range.length; i++) {
|
||||
dst[i] = GB_safe_read_memory(gb, range.location + i);
|
||||
}
|
||||
addr += 0x8000;
|
||||
break;
|
||||
case GBMemoryExternalRAM:
|
||||
bank_backup = gb->mbc_ram_bank;
|
||||
gb->mbc_ram_bank = self.selectedBank;
|
||||
addr += 0xA000;
|
||||
break;
|
||||
case GBMemoryRAM:
|
||||
bank_backup = gb->cgb_ram_bank;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->cgb_ram_bank = self.selectedBank;
|
||||
}
|
||||
addr += 0xC000;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}];
|
||||
break;
|
||||
}
|
||||
while (length) {
|
||||
*(_dst++) = [_document readMemory:addr++];
|
||||
length--;
|
||||
else {
|
||||
uint16_t bank;
|
||||
size_t size;
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank);
|
||||
bank = self.selectedBank & (size / 0x2000 - 1);
|
||||
if (size == 0) {
|
||||
memset(dst, 0xFF, range.length);
|
||||
}
|
||||
else if (range.location + range.length - 0xA000 > size) {
|
||||
goto slow_path;
|
||||
}
|
||||
else {
|
||||
memcpy(dst, data + bank * 0x2000 + range.location - 0xA000, range.length);
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (_mode) {
|
||||
case GBMemoryROM:
|
||||
gb->mbc_rom_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryVRAM:
|
||||
gb->cgb_vram_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryExternalRAM:
|
||||
gb->mbc_ram_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryRAM:
|
||||
gb->cgb_ram_bank = bank_backup;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
case 0xC:
|
||||
case 0xE: {
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, NULL, NULL);
|
||||
memcpy(dst, data + (range.location & 0xFFF), range.length);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xD: {
|
||||
uint16_t bank;
|
||||
size_t size;
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, &size, &bank);
|
||||
if (_mode != GBMemoryEntireSpace) {
|
||||
bank = self.selectedBank & (size / 0x1000 - 1);
|
||||
}
|
||||
}];
|
||||
memcpy(dst, data + bank * 0x1000 + range.location - 0xD000, range.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,65 +153,104 @@
|
|||
return ret;
|
||||
}
|
||||
|
||||
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange
|
||||
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)range
|
||||
{
|
||||
if (slice.length != lrange.length) return; /* Insertion is not allowed, only overwriting. */
|
||||
[_document performAtomicBlock:^{
|
||||
uint16_t addr = (uint16_t) lrange.location;
|
||||
uint16_t bank_backup = 0;
|
||||
GB_gameboy_t *gb = _document.gameboy;
|
||||
switch (_mode) {
|
||||
case GBMemoryROM:
|
||||
bank_backup = gb->mbc_rom_bank;
|
||||
gb->mbc_rom_bank = self.selectedBank;
|
||||
if (slice.length != range.length) return; /* Insertion is not allowed, only overwriting. */
|
||||
// Do everything in 0x1000 chunks, never cross a 0x1000 boundary
|
||||
if ((range.location & 0xF000) != ((range.location + range.length) & 0xF000)) {
|
||||
size_t partial = 0x1000 - (range.location & 0xFFF);
|
||||
if (slice.length - partial) {
|
||||
[self insertByteSlice:[slice subsliceWithRange:HFRangeMake(partial, slice.length - partial)]
|
||||
inRange:HFRangeMake(range.location + partial, range.length - partial)];
|
||||
}
|
||||
range.length = partial;
|
||||
}
|
||||
range.location += self.base;
|
||||
|
||||
GB_gameboy_t *gb = _document.gameboy;
|
||||
|
||||
|
||||
switch (range.location >> 12) {
|
||||
case 0x0:
|
||||
case 0x1:
|
||||
case 0x2:
|
||||
case 0x3:
|
||||
case 0x4:
|
||||
case 0x5:
|
||||
case 0x6:
|
||||
case 0x7: {
|
||||
return; // ROM not writeable
|
||||
}
|
||||
case 0x8:
|
||||
case 0x9: {
|
||||
uint16_t bank;
|
||||
size_t size;
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_VRAM, &size, &bank);
|
||||
if (_mode != GBMemoryEntireSpace) {
|
||||
bank = self.selectedBank & (size / 0x2000 - 1);
|
||||
}
|
||||
uint8_t sliceData[range.length];
|
||||
[slice copyBytes:sliceData range:HFRangeMake(0, range.length)];
|
||||
memcpy(data + bank * 0x2000 + range.location - 0x8000, sliceData, range.length);
|
||||
break;
|
||||
}
|
||||
case 0xA:
|
||||
case 0xB: {
|
||||
// Some carts are special, use memory write directly in full mem mode
|
||||
if (_mode == GBMemoryEntireSpace) {
|
||||
case 0xF:
|
||||
slow_path:
|
||||
[_document performAtomicBlock:^{
|
||||
uint8_t sliceData[range.length];
|
||||
[slice copyBytes:sliceData range:HFRangeMake(0, range.length)];
|
||||
for (unsigned i = 0; i < range.length; i++) {
|
||||
GB_write_memory(gb, range.location + i, sliceData[i]);
|
||||
}
|
||||
}];
|
||||
break;
|
||||
case GBMemoryVRAM:
|
||||
bank_backup = gb->cgb_vram_bank;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->cgb_vram_bank = self.selectedBank;
|
||||
}
|
||||
else {
|
||||
uint16_t bank;
|
||||
size_t size;
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_CART_RAM, &size, &bank);
|
||||
bank = self.selectedBank & (size / 0x2000 - 1);
|
||||
if (size == 0) {
|
||||
// Nothing to write to
|
||||
}
|
||||
addr += 0x8000;
|
||||
break;
|
||||
case GBMemoryExternalRAM:
|
||||
bank_backup = gb->mbc_ram_bank;
|
||||
gb->mbc_ram_bank = self.selectedBank;
|
||||
addr += 0xA000;
|
||||
break;
|
||||
case GBMemoryRAM:
|
||||
bank_backup = gb->cgb_ram_bank;
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->cgb_ram_bank = self.selectedBank;
|
||||
else if (range.location + range.length - 0xA000 > size) {
|
||||
goto slow_path;
|
||||
}
|
||||
else {
|
||||
uint8_t sliceData[range.length];
|
||||
[slice copyBytes:sliceData range:HFRangeMake(0, range.length)];
|
||||
memcpy(data + bank * 0x2000 + range.location - 0xA000, sliceData, range.length);
|
||||
}
|
||||
addr += 0xC000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
uint8_t values[lrange.length];
|
||||
[slice copyBytes:values range:HFRangeMake(0, lrange.length)];
|
||||
uint8_t *src = values;
|
||||
unsigned long long length = lrange.length;
|
||||
while (length) {
|
||||
[_document writeMemory:addr++ value:*(src++)];
|
||||
length--;
|
||||
case 0xC:
|
||||
case 0xE: {
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, NULL, NULL);
|
||||
uint8_t sliceData[range.length];
|
||||
[slice copyBytes:sliceData range:HFRangeMake(0, range.length)];
|
||||
memcpy(data + (range.location & 0xFFF), sliceData, range.length);
|
||||
break;
|
||||
}
|
||||
switch (_mode) {
|
||||
case GBMemoryROM:
|
||||
gb->mbc_rom_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryVRAM:
|
||||
gb->cgb_vram_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryExternalRAM:
|
||||
gb->mbc_ram_bank = bank_backup;
|
||||
break;
|
||||
case GBMemoryRAM:
|
||||
gb->cgb_ram_bank = bank_backup;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
case 0xD: {
|
||||
uint16_t bank;
|
||||
size_t size;
|
||||
uint8_t *data = GB_get_direct_access(gb, GB_DIRECT_ACCESS_RAM, &size, &bank);
|
||||
if (_mode != GBMemoryEntireSpace) {
|
||||
bank = self.selectedBank & (size / 0x1000 - 1);
|
||||
}
|
||||
uint8_t sliceData[range.length];
|
||||
[slice copyBytes:sliceData range:HFRangeMake(0, range.length)];
|
||||
|
||||
memcpy(data + bank * 0x1000 + range.location - 0xD000, sliceData, range.length);
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#import "Document.h"
|
||||
|
||||
@interface GBObjectView : NSView
|
||||
- (void)reloadData:(Document *)document;
|
||||
@end
|
|
@ -0,0 +1,134 @@
|
|||
#import "GBObjectView.h"
|
||||
|
||||
@interface GBObjectViewItem : NSObject
|
||||
@property IBOutlet NSView *view;
|
||||
@property IBOutlet NSImageView *image;
|
||||
@property IBOutlet NSTextField *oamAddress;
|
||||
@property IBOutlet NSTextField *position;
|
||||
@property IBOutlet NSTextField *attributes;
|
||||
@property IBOutlet NSTextField *tile;
|
||||
@property IBOutlet NSTextField *tileAddress;
|
||||
@property IBOutlet NSImageView *warningIcon;
|
||||
@property IBOutlet NSBox *verticalLine;
|
||||
@end
|
||||
|
||||
@implementation GBObjectViewItem
|
||||
{
|
||||
@public
|
||||
uint32_t _lastImageData[128];
|
||||
uint8_t _lastHeight;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation GBObjectView
|
||||
{
|
||||
NSMutableArray<GBObjectViewItem *> *_items;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
_items = [NSMutableArray array];
|
||||
CGFloat height = self.frame.size.height;
|
||||
for (unsigned i = 0; i < 40; i++) {
|
||||
GBObjectViewItem *item = [[GBObjectViewItem alloc] init];
|
||||
[_items addObject:item];
|
||||
[[NSBundle mainBundle] loadNibNamed:@"GBObjectViewItem" owner:item topLevelObjects:nil];
|
||||
item.view.hidden = true;
|
||||
[self addSubview:item.view];
|
||||
[item.view setFrameOrigin:NSMakePoint((i % 4) * 120, height - (i / 4 * 68) - 68)];
|
||||
item.oamAddress.toolTip = @"OAM address";
|
||||
item.position.toolTip = @"Position";
|
||||
item.attributes.toolTip = @"Attributes";
|
||||
item.tile.toolTip = @"Tile index";
|
||||
item.tileAddress.toolTip = @"Tile address";
|
||||
item.warningIcon.toolTip = @"Dropped: too many objects in line";
|
||||
if ((i % 4) == 3) {
|
||||
[item.verticalLine removeFromSuperview];
|
||||
}
|
||||
item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reloadData:(Document *)document
|
||||
{
|
||||
GB_oam_info_t *info = document.oamInfo;
|
||||
uint8_t length = document.oamCount;
|
||||
bool cgb = GB_is_cgb(document.gb);
|
||||
uint8_t height = document.oamHeight;
|
||||
NSFont *font = [document debuggerFontOfSize:11];
|
||||
NSFont *boldFont = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask];
|
||||
|
||||
for (unsigned i = 0; i < 40; i++) {
|
||||
GBObjectViewItem *item = _items[i];
|
||||
if (i >= length) {
|
||||
item.view.hidden = true;
|
||||
}
|
||||
else {
|
||||
item.view.hidden = false;
|
||||
|
||||
item.oamAddress.font = boldFont;
|
||||
item.position.font = font;
|
||||
item.attributes.font = font;
|
||||
item.tile.font = font;
|
||||
item.tileAddress.font = font;
|
||||
|
||||
item.oamAddress.stringValue = [NSString stringWithFormat:@"$%04X", info[i].oam_addr];
|
||||
item.position.stringValue = [NSString stringWithFormat:@"(%d, %d)",
|
||||
((signed)(unsigned)info[i].x) - 8,
|
||||
((signed)(unsigned)info[i].y) - 16];
|
||||
item.tile.stringValue = [NSString stringWithFormat:@"$%02X", info[i].tile];
|
||||
item.tileAddress.stringValue = [NSString stringWithFormat:@"$%04X", 0x8000 + info[i].tile * 0x10];
|
||||
item.warningIcon.hidden = !info[i].obscured_by_line_limit;
|
||||
if (cgb) {
|
||||
item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d%d",
|
||||
info[i].flags & 0x80? 'P' : '-',
|
||||
info[i].flags & 0x40? 'Y' : '-',
|
||||
info[i].flags & 0x20? 'X' : '-',
|
||||
info[i].flags & 0x08? 1 : 0,
|
||||
info[i].flags & 0x07];
|
||||
}
|
||||
else {
|
||||
item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d",
|
||||
info[i].flags & 0x80? 'P' : '-',
|
||||
info[i].flags & 0x40? 'Y' : '-',
|
||||
info[i].flags & 0x20? 'X' : '-',
|
||||
info[i].flags & 0x10? 1 : 0];
|
||||
}
|
||||
size_t imageSize = 8 * 4 * height;
|
||||
if (height == item->_lastHeight && memcmp(item->_lastImageData, info[i].image, imageSize) == 0) {
|
||||
continue;
|
||||
}
|
||||
memcpy(item->_lastImageData, info[i].image, imageSize);
|
||||
item->_lastHeight = height;
|
||||
item.image.image = [Document imageFromData:[NSData dataWithBytesNoCopy:info[i].image
|
||||
length:64 * 4 * 2
|
||||
freeWhenDone:false]
|
||||
width:8
|
||||
height:height
|
||||
scale:32.0 / height];
|
||||
}
|
||||
}
|
||||
|
||||
NSRect frame = self.frame;
|
||||
CGFloat newHeight = MAX(68 * ((length + 3) / 4), self.superview.frame.size.height);
|
||||
frame.origin.y -= newHeight - frame.size.height;
|
||||
frame.size.height = newHeight;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
if (@available(macOS 10.14, *)) {
|
||||
[[NSColor alternatingContentBackgroundColors].lastObject setFill];
|
||||
}
|
||||
else {
|
||||
[[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill];
|
||||
}
|
||||
NSRect frame = self.frame;
|
||||
for (unsigned i = 1; i <= 5; i++) {
|
||||
NSRectFill(NSMakeRect(0, frame.size.height - i * 68 * 2, frame.size.width, 68));
|
||||
}
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,94 @@
|
|||
<?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">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="GBObjectViewItem">
|
||||
<connections>
|
||||
<outlet property="attributes" destination="AVH-lh-aVu" id="F5l-vc-9YB"/>
|
||||
<outlet property="image" destination="GWF-Vk-eLx" id="cQa-lM-SqU"/>
|
||||
<outlet property="oamAddress" destination="HiB-x7-HCb" id="LLX-HU-t00"/>
|
||||
<outlet property="position" destination="bxJ-ig-rox" id="woq-X2-lCK"/>
|
||||
<outlet property="tile" destination="Kco-1J-bKc" id="C8u-jH-bCh"/>
|
||||
<outlet property="tileAddress" destination="ptE-vl-9HD" id="9K3-Oq-1vs"/>
|
||||
<outlet property="verticalLine" destination="Q91-eq-oeo" id="6kc-qh-cFx"/>
|
||||
<outlet property="view" destination="c22-O7-iKe" id="50f-xf-9Gg"/>
|
||||
<outlet property="warningIcon" destination="dQV-cO-218" id="Rxi-Wq-Upl"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="120" height="68"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kco-1J-bKc">
|
||||
<rect key="frame" x="44" y="4" width="58" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="left" title="$32" id="Aps-81-wR7">
|
||||
<font key="font" size="11" name="Menlo-Regular"/>
|
||||
<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="ptE-vl-9HD">
|
||||
<rect key="frame" x="0.0" y="4" width="44" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="center" title="$8320" id="t2G-E2-vt5">
|
||||
<font key="font" size="11" name="Menlo-Regular"/>
|
||||
<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="bxJ-ig-rox">
|
||||
<rect key="frame" x="44" y="36" width="76" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="(32,16)" id="oac-yZ-h47">
|
||||
<font key="font" size="11" name="Menlo-Regular"/>
|
||||
<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="AVH-lh-aVu">
|
||||
<rect key="frame" x="44" y="20" width="76" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="---04" id="tJX-6t-5Kx">
|
||||
<font key="font" size="11" name="Menlo-Regular"/>
|
||||
<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="HiB-x7-HCb">
|
||||
<rect key="frame" x="44" y="52" width="76" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="left" title="$FE32" id="ysm-jq-PKy">
|
||||
<font key="font" size="11" name="Menlo-Bold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="GWF-Vk-eLx" customClass="GBImageView">
|
||||
<rect key="frame" x="2" y="24" width="40" height="40"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" id="AJz-KH-eeo"/>
|
||||
</imageView>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dQV-cO-218">
|
||||
<rect key="frame" x="100" y="4" width="16" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSCaution" id="TQB-Vp-9Jm"/>
|
||||
</imageView>
|
||||
<box horizontalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="Q91-eq-oeo">
|
||||
<rect key="frame" x="116" y="4" width="5" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="132" y="149"/>
|
||||
</customView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSCaution" width="32" height="32"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -1,6 +1,7 @@
|
|||
#import "GBOpenGLView.h"
|
||||
#import "GBView.h"
|
||||
#include <OpenGL/gl.h>
|
||||
#import "NSObject+DefaultsObserver.h"
|
||||
#import <OpenGL/gl.h>
|
||||
|
||||
@implementation GBOpenGLView
|
||||
|
||||
|
@ -27,13 +28,13 @@
|
|||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat *)format
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil];
|
||||
return [super initWithFrame:frameRect pixelFormat:format];
|
||||
}
|
||||
__unsafe_unretained GBOpenGLView *weakSelf = self;
|
||||
self = [super initWithFrame:frameRect pixelFormat:format];
|
||||
[self observeStandardDefaultsKey:@"GBFilter" withBlock:^(id newValue) {
|
||||
weakSelf.shader = nil;
|
||||
[weakSelf setNeedsDisplay:true];
|
||||
|
||||
- (void) filterChanged
|
||||
{
|
||||
self.shader = nil;
|
||||
[self setNeedsDisplay:true];
|
||||
}];
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
#import <AppKit/AppKit.h>
|
||||
#import <Core/gb.h>
|
||||
|
||||
@interface GBPaletteEditorController : NSObject<NSTableViewDataSource, NSTableViewDelegate>
|
||||
@property (weak) IBOutlet NSColorWell *colorWell0;
|
||||
@property (weak) IBOutlet NSColorWell *colorWell1;
|
||||
@property (weak) IBOutlet NSColorWell *colorWell2;
|
||||
@property (weak) IBOutlet NSColorWell *colorWell3;
|
||||
@property (weak) IBOutlet NSColorWell *colorWell4;
|
||||
@property (weak) IBOutlet NSButton *disableLCDColorCheckbox;
|
||||
@property (weak) IBOutlet NSButton *manualModeCheckbox;
|
||||
@property (weak) IBOutlet NSSlider *brightnessSlider;
|
||||
@property (weak) IBOutlet NSSlider *hueSlider;
|
||||
@property (weak) IBOutlet NSSlider *hueStrengthSlider;
|
||||
@property (weak) IBOutlet NSTableView *themesList;
|
||||
@property (weak) IBOutlet NSMenu *menu;
|
||||
@property (weak) IBOutlet NSSegmentedControl *segmentControl;
|
||||
@property IBOutlet NSMenu *segmentMenu;
|
||||
+ (const GB_palette_t *)userPalette;
|
||||
@end
|
|
@ -0,0 +1,408 @@
|
|||
#import "GBPaletteEditorController.h"
|
||||
#import "GBHueSliderCell.h"
|
||||
#import "GBApp.h"
|
||||
#import <Core/gb.h>
|
||||
|
||||
#define MAGIC 'SBPL'
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
uint32_t magic;
|
||||
bool manual:1;
|
||||
bool disabled_lcd_color:1;
|
||||
unsigned padding:6;
|
||||
struct GB_color_s colors[5];
|
||||
int32_t brightness_bias;
|
||||
uint32_t hue_bias;
|
||||
uint32_t hue_bias_strength;
|
||||
} theme_t;
|
||||
|
||||
static double blend(double from, double to, double position)
|
||||
{
|
||||
return from * (1 - position) + to * position;
|
||||
}
|
||||
|
||||
@implementation NSColor (GBColor)
|
||||
|
||||
- (struct GB_color_s)gbColor
|
||||
{
|
||||
NSColor *sRGB = [self colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
|
||||
return (struct GB_color_s){round(sRGB.redComponent * 255), round(sRGB.greenComponent * 255), round(sRGB.blueComponent * 255)};
|
||||
}
|
||||
|
||||
- (uint32_t)intValue
|
||||
{
|
||||
struct GB_color_s color = self.gbColor;
|
||||
return (color.r << 0) | (color.g << 8) | (color.b << 16) | 0xFF000000;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GBPaletteEditorController
|
||||
|
||||
- (NSArray<NSColorWell *> *)colorWells
|
||||
{
|
||||
return @[_colorWell0, _colorWell1, _colorWell2, _colorWell3, _colorWell4];
|
||||
}
|
||||
|
||||
- (void)updateEnabledControls
|
||||
{
|
||||
if (self.manualModeCheckbox.state) {
|
||||
_brightnessSlider.enabled = false;
|
||||
_hueSlider.enabled = false;
|
||||
_hueStrengthSlider.enabled = false;
|
||||
_colorWell1.enabled = true;
|
||||
_colorWell2.enabled = true;
|
||||
_colorWell3.enabled = true;
|
||||
if (!(_colorWell4.enabled = self.disableLCDColorCheckbox.state)) {
|
||||
_colorWell4.color = _colorWell3.color;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_colorWell1.enabled = false;
|
||||
_colorWell2.enabled = false;
|
||||
_colorWell3.enabled = false;
|
||||
_colorWell4.enabled = true;
|
||||
_brightnessSlider.enabled = true;
|
||||
_hueSlider.enabled = true;
|
||||
_hueStrengthSlider.enabled = true;
|
||||
[self updateAutoColors];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSColor *)autoColorAtPositon:(double)position
|
||||
{
|
||||
NSColor *first = [_colorWell0.color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
|
||||
NSColor *second = [_colorWell4.color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
|
||||
double brightness = 1 / pow(4, (_brightnessSlider.doubleValue - 128) / 128.0);
|
||||
position = pow(position, brightness);
|
||||
NSColor *hue = _hueSlider.colorValue;
|
||||
double bias = _hueStrengthSlider.doubleValue / 256.0;
|
||||
double red = 1 / pow(4, (hue.redComponent * 2 - 1) * bias);
|
||||
double green = 1 / pow(4, (hue.greenComponent * 2 - 1) * bias);
|
||||
double blue = 1 / pow(4, (hue.blueComponent * 2 - 1) * bias);
|
||||
NSColor *ret = [NSColor colorWithRed:blend(first.redComponent, second.redComponent, pow(position, red))
|
||||
green:blend(first.greenComponent, second.greenComponent, pow(position, green))
|
||||
blue:blend(first.blueComponent, second.blueComponent, pow(position, blue))
|
||||
alpha:1.0];
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (IBAction)updateAutoColors:(id)sender
|
||||
{
|
||||
if (!self.manualModeCheckbox.state) {
|
||||
[self updateAutoColors];
|
||||
}
|
||||
else {
|
||||
[self savePalette:sender];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateAutoColors
|
||||
{
|
||||
if (_disableLCDColorCheckbox.state) {
|
||||
_colorWell1.color = [self autoColorAtPositon:8 / 25.0];
|
||||
_colorWell2.color = [self autoColorAtPositon:16 / 25.0];
|
||||
_colorWell3.color = [self autoColorAtPositon:24 / 25.0];
|
||||
}
|
||||
else {
|
||||
_colorWell1.color = [self autoColorAtPositon:1 / 3.0];
|
||||
_colorWell2.color = [self autoColorAtPositon:2 / 3.0];
|
||||
_colorWell3.color = _colorWell4.color;
|
||||
}
|
||||
[self savePalette:nil];
|
||||
}
|
||||
|
||||
- (IBAction)disabledLCDColorCheckboxChanged:(id)sender
|
||||
{
|
||||
[self updateEnabledControls];
|
||||
}
|
||||
|
||||
- (IBAction)manualModeChanged:(id)sender
|
||||
{
|
||||
[self updateEnabledControls];
|
||||
}
|
||||
|
||||
- (IBAction)updateColor4:(id)sender
|
||||
{
|
||||
if (!self.disableLCDColorCheckbox.state) {
|
||||
self.colorWell4.color = self.colorWell3.color;
|
||||
}
|
||||
[self savePalette:self];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
|
||||
if (themes.count == 0) {
|
||||
[defaults setObject:@"Untitled Palette" forKey:@"GBCurrentTheme"];
|
||||
[self savePalette:nil];
|
||||
return 1;
|
||||
}
|
||||
return themes.count;
|
||||
}
|
||||
|
||||
-(void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
NSString *oldName = [self tableView:tableView objectValueForTableColumn:tableColumn row:row];
|
||||
if ([oldName isEqualToString:object]) {
|
||||
return;
|
||||
}
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
|
||||
NSString *newName = object;
|
||||
unsigned i = 2;
|
||||
if (!newName.length) {
|
||||
newName = @"Untitled Palette";
|
||||
}
|
||||
while (themes[newName]) {
|
||||
newName = [NSString stringWithFormat:@"%@ %d", object, i];
|
||||
}
|
||||
themes[newName] = themes[oldName];
|
||||
[themes removeObjectForKey:oldName];
|
||||
if ([oldName isEqualToString:[defaults stringForKey:@"GBCurrentTheme"]]) {
|
||||
[defaults setObject:newName forKey:@"GBCurrentTheme"];
|
||||
}
|
||||
[defaults setObject:themes forKey:@"GBThemes"];
|
||||
[tableView reloadData];
|
||||
[self awakeFromNib];
|
||||
}
|
||||
|
||||
- (IBAction)deleteTheme:(id)sender
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSString *name = [defaults stringForKey:@"GBCurrentTheme"];
|
||||
NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
|
||||
[themes removeObjectForKey:name];
|
||||
[defaults setObject:themes forKey:@"GBThemes"];
|
||||
[_themesList reloadData];
|
||||
[self awakeFromNib];
|
||||
}
|
||||
|
||||
- (void)tableViewSelectionDidChange:(NSNotification *)notification
|
||||
{
|
||||
NSString *name = [self tableView:nil objectValueForTableColumn:nil row:_themesList.selectedRow];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:name forKey:@"GBCurrentTheme"];
|
||||
[self loadPalette];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
|
||||
}
|
||||
|
||||
- (void)tableViewSelectionIsChanging:(NSNotification *)notification
|
||||
{
|
||||
[self tableViewSelectionDidChange:notification];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
|
||||
NSString *theme = [defaults stringForKey:@"GBCurrentTheme"];
|
||||
if (theme && themes[theme]) {
|
||||
unsigned index = [[themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] indexOfObject:theme];
|
||||
[_themesList selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:false];
|
||||
[_themesList scrollRowToVisible:index];
|
||||
}
|
||||
else {
|
||||
[_themesList selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:false];
|
||||
[_themesList scrollRowToVisible:0];
|
||||
}
|
||||
[self tableViewSelectionDidChange:nil];
|
||||
if (@available(macOS 10.10, *)) {
|
||||
_themesList.enclosingScrollView.contentView.automaticallyAdjustsContentInsets = false;
|
||||
_themesList.enclosingScrollView.contentView.contentInsets = NSEdgeInsetsMake(0, 0, 10, 0);
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)addTheme:(id)sender
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
|
||||
NSString *newName = @"Untitled Palette";
|
||||
unsigned i = 2;
|
||||
while (themes[newName]) {
|
||||
newName = [NSString stringWithFormat:@"Untitled Palette %d", i++];
|
||||
}
|
||||
[defaults setObject:newName forKey:@"GBCurrentTheme"];
|
||||
[self savePalette:sender];
|
||||
[_themesList reloadData];
|
||||
[self awakeFromNib];
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
|
||||
return [themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)][row];
|
||||
}
|
||||
|
||||
- (void)loadPalette
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *theme = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]];
|
||||
NSArray *colors = theme[@"Colors"];
|
||||
if (colors.count == 5) {
|
||||
unsigned i = 0;
|
||||
for (NSNumber *color in colors) {
|
||||
uint32_t c = [color unsignedIntValue];
|
||||
self.colorWells[i++].color = [NSColor colorWithRed:(c & 0xFF) / 255.0
|
||||
green:((c >> 8) & 0xFF) / 255.0
|
||||
blue:((c >> 16) & 0xFF) / 255.0
|
||||
alpha:1.0];
|
||||
}
|
||||
}
|
||||
_disableLCDColorCheckbox.state = [theme[@"DisabledLCDColor"] boolValue];
|
||||
_manualModeCheckbox.state = [theme[@"Manual"] boolValue];
|
||||
_brightnessSlider.doubleValue = [theme[@"BrightnessBias"] doubleValue] * 128 + 128;
|
||||
_hueSlider.doubleValue = [theme[@"HueBias"] doubleValue] * 360;
|
||||
_hueStrengthSlider.doubleValue = [theme[@"HueBiasStrength"] doubleValue] * 256;
|
||||
[self updateEnabledControls];
|
||||
}
|
||||
|
||||
- (IBAction)savePalette:(id)sender
|
||||
{
|
||||
NSDictionary *theme = @{
|
||||
@"Colors":
|
||||
@[@(_colorWell0.color.intValue),
|
||||
@(_colorWell1.color.intValue),
|
||||
@(_colorWell2.color.intValue),
|
||||
@(_colorWell3.color.intValue),
|
||||
@(_colorWell4.color.intValue)],
|
||||
@"DisabledLCDColor": _disableLCDColorCheckbox.state? @YES : @NO,
|
||||
@"Manual": _manualModeCheckbox.state? @YES : @NO,
|
||||
@"BrightnessBias": @((_brightnessSlider.doubleValue - 128) / 128.0),
|
||||
@"HueBias": @(_hueSlider.doubleValue / 360.0),
|
||||
@"HueBiasStrength": @(_hueStrengthSlider.doubleValue / 256.0)
|
||||
};
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
|
||||
themes[[defaults stringForKey:@"GBCurrentTheme"]] = theme;
|
||||
[defaults setObject:themes forKey:@"GBThemes"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
|
||||
}
|
||||
|
||||
+ (const GB_palette_t *)userPalette
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
switch ([defaults integerForKey:@"GBColorPalette"]) {
|
||||
case 1: return &GB_PALETTE_DMG;
|
||||
case 2: return &GB_PALETTE_MGB;
|
||||
case 3: return &GB_PALETTE_GBL;
|
||||
default: return &GB_PALETTE_GREY;
|
||||
case -1: {
|
||||
static GB_palette_t customPalette;
|
||||
NSArray *colors = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]][@"Colors"];
|
||||
if (colors.count == 5) {
|
||||
unsigned i = 0;
|
||||
for (NSNumber *color in colors) {
|
||||
uint32_t c = [color unsignedIntValue];
|
||||
customPalette.colors[i++] = (struct GB_color_s) {c, c >> 8, c >> 16};
|
||||
}
|
||||
}
|
||||
return &customPalette;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)export:(id)sender
|
||||
{
|
||||
NSSavePanel *savePanel = [NSSavePanel savePanel];
|
||||
[savePanel setAllowedFileTypes:@[@"sbp"]];
|
||||
savePanel.nameFieldStringValue = [NSString stringWithFormat:@"%@.sbp", [[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"]];
|
||||
if ([savePanel runModal] == NSModalResponseOK) {
|
||||
theme_t theme = {0,};
|
||||
theme.magic = MAGIC;
|
||||
theme.manual = _manualModeCheckbox.state;
|
||||
theme.disabled_lcd_color = _disableLCDColorCheckbox.state;
|
||||
unsigned i = 0;
|
||||
for (NSColorWell *well in self.colorWells) {
|
||||
theme.colors[i++] = well.color.gbColor;
|
||||
}
|
||||
theme.brightness_bias = (_brightnessSlider.doubleValue - 128) * (0x40000000 / 128);
|
||||
theme.hue_bias = round(_hueSlider.doubleValue * (0x80000000 / 360.0));
|
||||
theme.hue_bias_strength = (_hueStrengthSlider.doubleValue) * (0x80000000 / 256);
|
||||
size_t size = sizeof(theme);
|
||||
if (theme.manual) {
|
||||
size = theme.disabled_lcd_color? 5 + 5 * sizeof(theme.colors[0]) : 5 + 4 * sizeof(theme.colors[0]);
|
||||
}
|
||||
[[NSData dataWithBytes:&theme length:size] writeToURL:savePanel.URL atomically:false];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)import:(id)sender
|
||||
{
|
||||
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
||||
[openPanel setAllowedFileTypes:@[@"sbp"]];
|
||||
if ([openPanel runModal] == NSModalResponseOK) {
|
||||
NSData *data = [NSData dataWithContentsOfURL:openPanel.URL];
|
||||
theme_t theme = {0,};
|
||||
memcpy(&theme, data.bytes, MIN(sizeof(theme), data.length));
|
||||
if (theme.magic != MAGIC) {
|
||||
NSBeep();
|
||||
return;
|
||||
}
|
||||
_manualModeCheckbox.state = theme.manual;
|
||||
_disableLCDColorCheckbox.state = theme.disabled_lcd_color;
|
||||
unsigned i = 0;
|
||||
for (NSColorWell *well in self.colorWells) {
|
||||
well.color = [NSColor colorWithRed:theme.colors[i].r / 255.0
|
||||
green:theme.colors[i].g / 255.0
|
||||
blue:theme.colors[i].b / 255.0
|
||||
alpha:1.0];
|
||||
i++;
|
||||
}
|
||||
if (!theme.disabled_lcd_color) {
|
||||
_colorWell4.color = _colorWell3.color;
|
||||
}
|
||||
_brightnessSlider.doubleValue = theme.brightness_bias / (0x40000000 / 128.0) + 128;
|
||||
_hueSlider.doubleValue = theme.hue_bias / (0x80000000 / 360.0);
|
||||
_hueStrengthSlider.doubleValue = theme.hue_bias_strength / (0x80000000 / 256.0);
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
|
||||
NSString *baseName = openPanel.URL.lastPathComponent.stringByDeletingPathExtension;
|
||||
NSString *newName = baseName;
|
||||
i = 2;
|
||||
while (themes[newName]) {
|
||||
newName = [NSString stringWithFormat:@"%@ %d", baseName, i++];
|
||||
}
|
||||
[defaults setObject:newName forKey:@"GBCurrentTheme"];
|
||||
[self savePalette:sender];
|
||||
[self.themesList reloadData];
|
||||
[self awakeFromNib];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)segmentAction:(NSSegmentedControl *)sender
|
||||
{
|
||||
switch (sender.selectedSegment) {
|
||||
case 0: [self addTheme:sender]; return;
|
||||
case 1: [self deleteTheme:sender]; return;
|
||||
case 3: {
|
||||
NSSize buttonSize = _segmentControl.bounds.size;
|
||||
[_segmentMenu popUpMenuPositioningItem:_segmentMenu.itemArray.lastObject
|
||||
atLocation:NSMakePoint(buttonSize.width,
|
||||
0)
|
||||
inView:sender];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)restoreDefaultPalettes:(id)sender
|
||||
{
|
||||
[(GBApp *)NSApp updateThemesDefault:true];
|
||||
[self awakeFromNib];
|
||||
}
|
||||
|
||||
- (IBAction)done:(NSButton *)sender
|
||||
{
|
||||
[sender.window.sheetParent endSheet:sender.window];
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
static id singleton = nil;
|
||||
if (singleton) return singleton;
|
||||
return (singleton = [super init]);
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,6 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#import "Document.h"
|
||||
|
||||
@interface GBPaletteView : NSView
|
||||
- (void)reloadData:(Document *)document;
|
||||
@end
|
|
@ -0,0 +1,93 @@
|
|||
#import "GBPaletteView.h"
|
||||
|
||||
@interface GBPaletteViewItem : NSObject
|
||||
@property IBOutlet NSView *view;
|
||||
@property (strong) IBOutlet NSTextField *label;
|
||||
@property (strong) IBOutlet NSTextField *color0;
|
||||
@property (strong) IBOutlet NSTextField *color1;
|
||||
@property (strong) IBOutlet NSTextField *color2;
|
||||
@property (strong) IBOutlet NSTextField *color3;
|
||||
@end
|
||||
|
||||
@implementation GBPaletteViewItem
|
||||
@end
|
||||
|
||||
@implementation GBPaletteView
|
||||
{
|
||||
NSMutableArray<NSTextField *> *_colors;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
_colors = [NSMutableArray array];
|
||||
CGFloat height = self.frame.size.height;
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
GBPaletteViewItem *item = [[GBPaletteViewItem alloc] init];
|
||||
[[NSBundle mainBundle] loadNibNamed:@"GBPaletteViewRow" owner:item topLevelObjects:nil];
|
||||
[self addSubview:item.view];
|
||||
[item.view setFrameOrigin:NSMakePoint(0, height - (i * 24) - 24)];
|
||||
item.label.stringValue = [NSString stringWithFormat:@"%@ %d", i < 8? @"Background" : @"Object", i % 8];
|
||||
item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin;
|
||||
[_colors addObject:item.color0];
|
||||
[_colors addObject:item.color1];
|
||||
[_colors addObject:item.color2];
|
||||
[_colors addObject:item.color3];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reloadData:(Document *)document
|
||||
{
|
||||
GB_gameboy_t *gb = document.gb;
|
||||
uint8_t *bg = GB_get_direct_access(gb, GB_DIRECT_ACCESS_BGP, NULL, NULL);
|
||||
uint8_t *obj = GB_get_direct_access(gb, GB_DIRECT_ACCESS_OBP, NULL, NULL);
|
||||
NSFont *font = [document debuggerFontOfSize:13];
|
||||
|
||||
for (unsigned i = 0; i < 4 * 8 * 2; i++) {
|
||||
uint8_t index = i % (4 * 8);
|
||||
uint8_t *palette = i >= 4 * 8 ? obj : bg;
|
||||
uint16_t color = (palette[(index << 1) + 1] << 8) | palette[(index << 1)];
|
||||
uint32_t nativeColor = GB_convert_rgb15(gb, color, false);
|
||||
|
||||
uint8_t r = color & 0x1F,
|
||||
g = (color >> 5) & 0x1F,
|
||||
b = (color >> 10) & 0x1F;
|
||||
|
||||
NSTextField *field = _colors[i];
|
||||
field.stringValue = [NSString stringWithFormat:@"$%04X", color];
|
||||
field.textColor = r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor];
|
||||
field.toolTip = [NSString stringWithFormat:@"Red: %d, Green: %d, Blue: %d", r, g, b];
|
||||
field.font = font;
|
||||
field.backgroundColor = [NSColor colorWithRed:(nativeColor & 0xFF) / 255.0
|
||||
green:((nativeColor >> 8) & 0xFF) / 255.0
|
||||
blue:((nativeColor >> 16) & 0xFF) / 255.0
|
||||
alpha:1.0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
NSRect frame = self.frame;
|
||||
if (@available(macOS 10.14, *)) {
|
||||
[[NSColor alternatingContentBackgroundColors].lastObject setFill];
|
||||
}
|
||||
else {
|
||||
[[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill];
|
||||
}
|
||||
for (unsigned i = 1; i <= 8; i++) {
|
||||
NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2, frame.size.width, 24));
|
||||
}
|
||||
|
||||
if (@available(macOS 10.14, *)) {
|
||||
[[NSColor alternatingContentBackgroundColors].firstObject setFill];
|
||||
}
|
||||
else {
|
||||
[[NSColor controlBackgroundColor] setFill];
|
||||
}
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2 - 24, frame.size.width, 24));
|
||||
}
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,74 @@
|
|||
<?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">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="GBPaletteViewItem">
|
||||
<connections>
|
||||
<outlet property="color0" destination="ypt-t4-Mf3" id="Bam-dX-hHk"/>
|
||||
<outlet property="color1" destination="KkX-Z8-Sqi" id="uCl-UT-PWu"/>
|
||||
<outlet property="color2" destination="jDk-Ej-4yI" id="Xjs-el-m3s"/>
|
||||
<outlet property="color3" destination="7PI-YE-fTk" id="7J8-aH-LEO"/>
|
||||
<outlet property="label" destination="NOK-yI-LKh" id="AK3-g5-H7a"/>
|
||||
<outlet property="view" destination="c22-O7-iKe" id="DPx-8k-YQB"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<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"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Background 0" id="qM4-cY-SDE">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="139" y="31"/>
|
||||
</customView>
|
||||
</objects>
|
||||
</document>
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBPanel : NSPanel
|
||||
@property (weak) IBOutlet NSWindow *ownerWindow;
|
||||
@end
|
|
@ -0,0 +1,11 @@
|
|||
#import "GBPanel.h"
|
||||
|
||||
@implementation GBPanel
|
||||
- (void)becomeKeyWindow
|
||||
{
|
||||
if ([_ownerWindow canBecomeMainWindow]) {
|
||||
[_ownerWindow makeMainWindow];
|
||||
}
|
||||
[super becomeKeyWindow];
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,6 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBPreferenceButton : NSButton
|
||||
@property (nonatomic) IBInspectable NSString *preferenceName;
|
||||
@property IBInspectable BOOL invertValue;
|
||||
@end
|
|
@ -0,0 +1,30 @@
|
|||
#import "GBPreferenceButton.h"
|
||||
#import "NSObject+DefaultsObserver.h"
|
||||
|
||||
@implementation GBPreferenceButton
|
||||
|
||||
- (BOOL)sendAction:(SEL)action to:(id)target
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:self.state ^ self.invertValue forKey:_preferenceName];
|
||||
return [super sendAction:action to:target];
|
||||
}
|
||||
|
||||
- (void)updateValue
|
||||
{
|
||||
if (!_preferenceName) return;
|
||||
self.state = [[NSUserDefaults standardUserDefaults] boolForKey:_preferenceName] ^ self.invertValue;
|
||||
}
|
||||
|
||||
- (void)setPreferenceName:(NSString *)preferenceName
|
||||
{
|
||||
_preferenceName = preferenceName;
|
||||
[self observeStandardDefaultsKey:_preferenceName selector:@selector(updateValue)];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
[super viewDidMoveToWindow];
|
||||
[self updateValue];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,9 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBPreferenceMenuItem : NSMenuItem
|
||||
@property (nonatomic) IBInspectable NSString *preferenceValue;
|
||||
@end
|
||||
|
||||
@interface GBPreferencePopUpButton : NSPopUpButton
|
||||
@property (nonatomic) IBInspectable NSString *preferenceName;
|
||||
@end
|
|
@ -0,0 +1,51 @@
|
|||
#import "GBPreferencePopUpButton.h"
|
||||
#import "NSObject+DefaultsObserver.h"
|
||||
|
||||
@implementation GBPreferenceMenuItem
|
||||
@end
|
||||
|
||||
@implementation GBPreferencePopUpButton
|
||||
|
||||
- (BOOL)sendAction:(SEL)action to:(id)target
|
||||
{
|
||||
GBPreferenceMenuItem *item = (GBPreferenceMenuItem *)self.selectedItem;
|
||||
if ([item isKindOfClass:[GBPreferenceMenuItem class]]) {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:item.preferenceValue forKey:_preferenceName];
|
||||
}
|
||||
else {
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:item.tag forKey:_preferenceName];
|
||||
}
|
||||
return [super sendAction:action to:target];
|
||||
}
|
||||
|
||||
- (void)updateValue
|
||||
{
|
||||
if (!_preferenceName) return;
|
||||
NSString *stringValue = [[NSUserDefaults standardUserDefaults] objectForKey:_preferenceName];
|
||||
if ([stringValue isKindOfClass:[NSString class]]) {
|
||||
for (GBPreferenceMenuItem *item in self.menu.itemArray) {
|
||||
if ([item isKindOfClass:[GBPreferenceMenuItem class]] &&
|
||||
[item.preferenceValue isEqualToString:stringValue]) {
|
||||
[self selectItem:item];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
[self selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:_preferenceName]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPreferenceName:(NSString *)preferenceName
|
||||
{
|
||||
_preferenceName = preferenceName;
|
||||
[self observeStandardDefaultsKey:_preferenceName selector:@selector(updateValue)];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
[super viewDidMoveToWindow];
|
||||
[self updateValue];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,6 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBPreferencesSlider : NSSlider
|
||||
@property (nonatomic) IBInspectable NSString *preferenceName;
|
||||
@property IBInspectable unsigned denominator;
|
||||
@end
|
|
@ -0,0 +1,29 @@
|
|||
#import "GBPreferencesSlider.h"
|
||||
#import "NSObject+DefaultsObserver.h"
|
||||
|
||||
@implementation GBPreferencesSlider
|
||||
|
||||
- (BOOL)sendAction:(SEL)action to:(id)target
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:self.doubleValue / (self.denominator ?: 1) forKey:_preferenceName];
|
||||
return [super sendAction:action to:target];
|
||||
}
|
||||
|
||||
- (void)updateValue
|
||||
{
|
||||
if (!_preferenceName) return;
|
||||
self.doubleValue = [[NSUserDefaults standardUserDefaults] doubleForKey:_preferenceName] * (self.denominator ?: 1);
|
||||
}
|
||||
|
||||
- (void)setPreferenceName:(NSString *)preferenceName
|
||||
{
|
||||
_preferenceName = preferenceName;
|
||||
[self observeStandardDefaultsKey:_preferenceName selector:@selector(updateValue)];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
[super viewDidMoveToWindow];
|
||||
[self updateValue];
|
||||
}
|
||||
@end
|
|
@ -1,32 +1,25 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
#import "GBPaletteEditorController.h"
|
||||
#import "GBTitledPopUpButton.h"
|
||||
|
||||
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, JOYListener>
|
||||
@property (nonatomic, strong) IBOutlet NSTableView *controlsTableView;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *graphicsFilterPopupButton;
|
||||
@property (nonatomic, strong) IBOutlet NSButton *analogControlsCheckbox;
|
||||
@property (nonatomic, strong) IBOutlet NSButton *aspectRatioCheckbox;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *colorPalettePopupButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *displayBorderPopupButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *rewindPopupButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *rtcPopupButton;
|
||||
@property (nonatomic, strong) IBOutlet NSButton *configureJoypadButton;
|
||||
@property (nonatomic, strong) IBOutlet NSButton *skipButton;
|
||||
@property (nonatomic, strong) IBOutlet NSMenuItem *bootROMsFolderItem;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButtonCell *bootROMsButton;
|
||||
@property (nonatomic, strong) IBOutlet NSPopUpButton *rumbleModePopupButton;
|
||||
@property (nonatomic, weak) IBOutlet NSSlider *temperatureSlider;
|
||||
@property (nonatomic, weak) IBOutlet NSSlider *interferenceSlider;
|
||||
@property (nonatomic, weak) IBOutlet NSPopUpButton *dmgPopupButton;
|
||||
@property (nonatomic, weak) IBOutlet NSPopUpButton *sgbPopupButton;
|
||||
@property (nonatomic, weak) IBOutlet NSPopUpButton *cgbPopupButton;
|
||||
@property (nonatomic, weak) IBOutlet NSPopUpButton *preferredJoypadButton;
|
||||
@property (nonatomic, weak) IBOutlet NSPopUpButton *playerListButton;
|
||||
@property (nonatomic, weak) IBOutlet NSButton *autoUpdatesCheckbox;
|
||||
@property (weak) IBOutlet NSSlider *volumeSlider;
|
||||
@property (weak) IBOutlet NSButton *OSDCheckbox;
|
||||
@property (weak) IBOutlet NSButton *screenshotFilterCheckbox;
|
||||
@property IBOutlet NSTableView *controlsTableView;
|
||||
@property IBOutlet NSButton *configureJoypadButton;
|
||||
@property IBOutlet NSButton *skipButton;
|
||||
@property IBOutlet NSMenuItem *bootROMsFolderItem;
|
||||
@property IBOutlet NSPopUpButtonCell *bootROMsButton;
|
||||
@property IBOutlet NSPopUpButton *preferredJoypadButton;
|
||||
@property IBOutlet NSPopUpButton *playerListButton;
|
||||
@property IBOutlet GBPaletteEditorController *paletteEditorController;
|
||||
@property IBOutlet NSWindow *paletteEditor;
|
||||
@property IBOutlet NSWindow *joyconsSheet;
|
||||
@property IBOutlet NSPopUpButton *colorCorrectionPopupButton;
|
||||
@property IBOutlet NSPopUpButton *highpassFilterPopupButton;
|
||||
@property IBOutlet NSPopUpButton *colorPalettePopupButton;
|
||||
@property IBOutlet NSPopUpButton *hotkey1PopupButton;
|
||||
@property IBOutlet NSPopUpButton *hotkey2PopupButton;
|
||||
|
||||
@property IBOutlet GBTitledPopUpButton *fontPopupButton;
|
||||
@property IBOutlet NSStepper *fontSizeStepper;
|
||||
@end
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#import "GBPreferencesWindow.h"
|
||||
#import "GBJoyConManager.h"
|
||||
#import "NSString+StringForKey.h"
|
||||
#import "GBButtons.h"
|
||||
#import "BigSurToolbar.h"
|
||||
#import "GBViewMetal.h"
|
||||
#import "GBWarningPopover.h"
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
@implementation GBPreferencesWindow
|
||||
|
@ -13,57 +15,12 @@
|
|||
NSString *joystick_being_configured;
|
||||
bool joypad_wait;
|
||||
|
||||
NSPopUpButton *_graphicsFilterPopupButton;
|
||||
NSPopUpButton *_highpassFilterPopupButton;
|
||||
NSPopUpButton *_colorCorrectionPopupButton;
|
||||
NSPopUpButton *_frameBlendingModePopupButton;
|
||||
NSPopUpButton *_colorPalettePopupButton;
|
||||
NSPopUpButton *_displayBorderPopupButton;
|
||||
NSPopUpButton *_rewindPopupButton;
|
||||
NSPopUpButton *_rtcPopupButton;
|
||||
NSButton *_aspectRatioCheckbox;
|
||||
NSButton *_analogControlsCheckbox;
|
||||
NSEventModifierFlags previousModifiers;
|
||||
|
||||
NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton;
|
||||
NSPopUpButton *_preferredJoypadButton;
|
||||
NSPopUpButton *_rumbleModePopupButton;
|
||||
NSSlider *_temperatureSlider;
|
||||
NSSlider *_interferenceSlider;
|
||||
NSSlider *_volumeSlider;
|
||||
NSButton *_autoUpdatesCheckbox;
|
||||
NSButton *_OSDCheckbox;
|
||||
NSButton *_screenshotFilterCheckbox;
|
||||
}
|
||||
|
||||
+ (NSArray *)filterList
|
||||
{
|
||||
/* The filter list as ordered in the popup button */
|
||||
static NSArray * filters = nil;
|
||||
if (!filters) {
|
||||
filters = @[
|
||||
@"NearestNeighbor",
|
||||
@"Bilinear",
|
||||
@"SmoothBilinear",
|
||||
@"MonoLCD",
|
||||
@"LCD",
|
||||
@"CRT",
|
||||
@"Scale2x",
|
||||
@"Scale4x",
|
||||
@"AAScale2x",
|
||||
@"AAScale4x",
|
||||
@"HQ2x",
|
||||
@"OmniScale",
|
||||
@"OmniScaleLegacy",
|
||||
@"AAOmniScaleLegacy",
|
||||
];
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
- (NSWindowToolbarStyle)toolbarStyle
|
||||
{
|
||||
return NSWindowToolbarStylePreference;
|
||||
return NSWindowToolbarStyleExpanded;
|
||||
}
|
||||
|
||||
- (void)close
|
||||
|
@ -75,160 +32,24 @@
|
|||
[super close];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)graphicsFilterPopupButton
|
||||
static inline NSString *keyEquivalentString(NSMenuItem *item)
|
||||
{
|
||||
return _graphicsFilterPopupButton;
|
||||
}
|
||||
|
||||
- (void)setGraphicsFilterPopupButton:(NSPopUpButton *)graphicsFilterPopupButton
|
||||
{
|
||||
_graphicsFilterPopupButton = graphicsFilterPopupButton;
|
||||
NSString *filter = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
|
||||
[_graphicsFilterPopupButton selectItemAtIndex:[[[self class] filterList] indexOfObject:filter]];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)highpassFilterPopupButton
|
||||
{
|
||||
return _highpassFilterPopupButton;
|
||||
}
|
||||
|
||||
- (void)setColorCorrectionPopupButton:(NSPopUpButton *)colorCorrectionPopupButton
|
||||
{
|
||||
_colorCorrectionPopupButton = colorCorrectionPopupButton;
|
||||
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"];
|
||||
[_colorCorrectionPopupButton selectItemAtIndex:mode];
|
||||
}
|
||||
|
||||
|
||||
- (NSPopUpButton *)colorCorrectionPopupButton
|
||||
{
|
||||
return _colorCorrectionPopupButton;
|
||||
}
|
||||
|
||||
- (void)setTemperatureSlider:(NSSlider *)temperatureSlider
|
||||
{
|
||||
_temperatureSlider = temperatureSlider;
|
||||
[temperatureSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"] * 256];
|
||||
}
|
||||
|
||||
- (NSSlider *)temperatureSlider
|
||||
{
|
||||
return _temperatureSlider;
|
||||
}
|
||||
|
||||
- (void)setInterferenceSlider:(NSSlider *)interferenceSlider
|
||||
{
|
||||
_interferenceSlider = interferenceSlider;
|
||||
[interferenceSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"] * 256];
|
||||
}
|
||||
|
||||
- (NSSlider *)interferenceSlider
|
||||
{
|
||||
return _interferenceSlider;
|
||||
}
|
||||
|
||||
- (void)setVolumeSlider:(NSSlider *)volumeSlider
|
||||
{
|
||||
_volumeSlider = volumeSlider;
|
||||
[volumeSlider setDoubleValue:[[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"] * 256];
|
||||
}
|
||||
|
||||
- (NSSlider *)volumeSlider
|
||||
{
|
||||
return _volumeSlider;
|
||||
}
|
||||
|
||||
- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton
|
||||
{
|
||||
_frameBlendingModePopupButton = frameBlendingModePopupButton;
|
||||
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
|
||||
[_frameBlendingModePopupButton selectItemAtIndex:mode];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)frameBlendingModePopupButton
|
||||
{
|
||||
return _frameBlendingModePopupButton;
|
||||
}
|
||||
|
||||
- (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton
|
||||
{
|
||||
_colorPalettePopupButton = colorPalettePopupButton;
|
||||
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"];
|
||||
[_colorPalettePopupButton selectItemAtIndex:mode];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)colorPalettePopupButton
|
||||
{
|
||||
return _colorPalettePopupButton;
|
||||
}
|
||||
|
||||
- (void)setDisplayBorderPopupButton:(NSPopUpButton *)displayBorderPopupButton
|
||||
{
|
||||
_displayBorderPopupButton = displayBorderPopupButton;
|
||||
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"];
|
||||
[_displayBorderPopupButton selectItemWithTag:mode];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)displayBorderPopupButton
|
||||
{
|
||||
return _displayBorderPopupButton;
|
||||
}
|
||||
|
||||
- (void)setRumbleModePopupButton:(NSPopUpButton *)rumbleModePopupButton
|
||||
{
|
||||
_rumbleModePopupButton = rumbleModePopupButton;
|
||||
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"];
|
||||
[_rumbleModePopupButton selectItemWithTag:mode];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)rumbleModePopupButton
|
||||
{
|
||||
return _rumbleModePopupButton;
|
||||
}
|
||||
|
||||
- (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton
|
||||
{
|
||||
_rewindPopupButton = rewindPopupButton;
|
||||
NSInteger length = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"];
|
||||
[_rewindPopupButton selectItemWithTag:length];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)rewindPopupButton
|
||||
{
|
||||
return _rewindPopupButton;
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)rtcPopupButton
|
||||
{
|
||||
return _rtcPopupButton;
|
||||
}
|
||||
|
||||
- (void)setRtcPopupButton:(NSPopUpButton *)rtcPopupButton
|
||||
{
|
||||
_rtcPopupButton = rtcPopupButton;
|
||||
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"];
|
||||
[_rtcPopupButton selectItemAtIndex:mode];
|
||||
}
|
||||
|
||||
- (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton
|
||||
{
|
||||
_highpassFilterPopupButton = highpassFilterPopupButton;
|
||||
[_highpassFilterPopupButton selectItemAtIndex:[[[NSUserDefaults standardUserDefaults] objectForKey:@"GBHighpassFilter"] unsignedIntegerValue]];
|
||||
return [NSString stringWithFormat:@"%s%@", (item.keyEquivalentModifierMask & NSEventModifierFlagShift)? "^":"", item.keyEquivalent];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||
{
|
||||
if (self.playerListButton.selectedTag == 0) {
|
||||
return GBButtonCount;
|
||||
return GBKeyboardButtonCount;
|
||||
}
|
||||
return GBGameBoyButtonCount;
|
||||
return GBPerPlayerButtonCount;
|
||||
}
|
||||
|
||||
- (unsigned) usesForKey:(unsigned) key
|
||||
{
|
||||
unsigned ret = 0;
|
||||
for (unsigned player = 4; player--;) {
|
||||
for (unsigned button = player == 0? GBButtonCount:GBGameBoyButtonCount; button--;) {
|
||||
for (unsigned button = player == 0? GBKeyboardButtonCount:GBPerPlayerButtonCount; button--;) {
|
||||
NSNumber *other = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(button, player)];
|
||||
if (other && [other unsignedIntValue] == key) {
|
||||
ret++;
|
||||
|
@ -245,7 +66,7 @@
|
|||
}
|
||||
|
||||
if (is_button_being_modified && button_being_modified == row) {
|
||||
return @"Select a new key...";
|
||||
return @"Select a new key…";
|
||||
}
|
||||
|
||||
NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];
|
||||
|
@ -304,109 +125,66 @@
|
|||
previousModifiers = event.modifierFlags;
|
||||
}
|
||||
|
||||
- (IBAction)graphicFilterChanged:(NSPopUpButton *)sender
|
||||
- (void)updatePalettesMenu
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]]
|
||||
forKey:@"GBFilter"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)highpassFilterChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
|
||||
forKey:@"GBHighpassFilter"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)changeAnalogControls:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
|
||||
forKey:@"GBAnalogControls"];
|
||||
}
|
||||
|
||||
- (IBAction)changeAspectRatio:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState
|
||||
forKey:@"GBAspectRatioUnkept"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBAspectChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)colorCorrectionChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
|
||||
forKey:@"GBColorCorrection"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)lightTemperatureChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
|
||||
forKey:@"GBLightTemperature"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBLightTemperatureChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)interferenceVolumeChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
|
||||
forKey:@"GBInterferenceVolume"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBInterferenceVolumeChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)volumeChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender doubleValue] / 256.0)
|
||||
forKey:@"GBVolume"];
|
||||
}
|
||||
|
||||
- (IBAction)franeBlendingModeChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
|
||||
forKey:@"GBFrameBlendingMode"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFrameBlendingModeChanged" object:nil];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
|
||||
NSMenu *menu = _colorPalettePopupButton.menu;
|
||||
while (menu.itemArray.count != 4) {
|
||||
[menu removeItemAtIndex:4];
|
||||
}
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
for (NSString *name in [themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]) {
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:name action:nil keyEquivalent:@""];
|
||||
item.tag = -2;
|
||||
[menu addItem:item];
|
||||
}
|
||||
if (themes) {
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Custom…" action:nil keyEquivalent:@""];
|
||||
item.tag = -1;
|
||||
[menu addItem:item];
|
||||
}
|
||||
|
||||
- (IBAction)colorPaletteChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
|
||||
forKey:@"GBColorPalette"];
|
||||
signed tag = [sender selectedItem].tag;
|
||||
if (tag == -2) {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(-1)
|
||||
forKey:@"GBColorPalette"];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[sender selectedItem].title
|
||||
forKey:@"GBCurrentTheme"];
|
||||
|
||||
}
|
||||
else if (tag == -1) {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(-1)
|
||||
forKey:@"GBColorPalette"];
|
||||
[_paletteEditorController awakeFromNib];
|
||||
[self beginSheet:_paletteEditor completionHandler:^(NSModalResponse returnCode) {
|
||||
[self updatePalettesMenu];
|
||||
[_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""];
|
||||
}];
|
||||
}
|
||||
else {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
|
||||
forKey:@"GBColorPalette"];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)displayBorderChanged:(id)sender
|
||||
- (IBAction)hotkey1Changed:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
|
||||
forKey:@"GBBorderMode"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:keyEquivalentString([sender selectedItem])
|
||||
forKey:@"GBJoypadHotkey1"];
|
||||
}
|
||||
|
||||
- (IBAction)rumbleModeChanged:(id)sender
|
||||
- (IBAction)hotkey2Changed:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag)
|
||||
forKey:@"GBRumbleMode"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRumbleModeChanged" object:nil];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:keyEquivalentString([sender selectedItem])
|
||||
forKey:@"GBJoypadHotkey2"];
|
||||
}
|
||||
|
||||
- (IBAction)rewindLengthChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||
forKey:@"GBRewindLength"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)rtcModeChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem])
|
||||
forKey:@"GBRTCMode"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRTCModeChanged" object:nil];
|
||||
|
||||
}
|
||||
|
||||
- (IBAction)changeAutoUpdates:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
|
||||
forKey:@"GBAutoUpdatesEnabled"];
|
||||
}
|
||||
|
||||
- (IBAction) configureJoypad:(id)sender
|
||||
{
|
||||
|
@ -427,7 +205,7 @@
|
|||
if (joystick_configuration_state == GBUnderclock) {
|
||||
[self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :<
|
||||
}
|
||||
else if (joystick_configuration_state < GBButtonCount) {
|
||||
else if (joystick_configuration_state < GBTotalButtonCount) {
|
||||
[self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
|
||||
}
|
||||
else {
|
||||
|
@ -449,7 +227,7 @@
|
|||
|
||||
if (!button.isPressed) return;
|
||||
if (joystick_configuration_state == -1) return;
|
||||
if (joystick_configuration_state == GBButtonCount) return;
|
||||
if (joystick_configuration_state == GBTotalButtonCount) return;
|
||||
if (!joystick_being_configured) {
|
||||
joystick_being_configured = controller.uniqueID;
|
||||
}
|
||||
|
@ -488,9 +266,13 @@
|
|||
[GBB] = JOYButtonUsageB,
|
||||
[GBSelect] = JOYButtonUsageSelect,
|
||||
[GBStart] = JOYButtonUsageStart,
|
||||
[GBRapidA] = GBJoyKitRapidA,
|
||||
[GBRapidB] = GBJoyKitRapidB,
|
||||
[GBTurbo] = JOYButtonUsageL1,
|
||||
[GBRewind] = JOYButtonUsageL2,
|
||||
[GBUnderclock] = JOYButtonUsageR1,
|
||||
[GBHotkey1] = GBJoyKitHotkey1,
|
||||
[GBHotkey2] = GBJoyKitHotkey2,
|
||||
};
|
||||
|
||||
if (joystick_configuration_state == GBUnderclock) {
|
||||
|
@ -498,7 +280,6 @@
|
|||
double max = 0;
|
||||
for (JOYAxis *axis in controller.axes) {
|
||||
if ((axis.value > 0.5 || (axis.equivalentButtonUsage == button.usage)) && axis.value >= max) {
|
||||
max = axis.value;
|
||||
mapping[@"AnalogUnderclock"] = @(axis.uniqueID);
|
||||
break;
|
||||
}
|
||||
|
@ -525,28 +306,6 @@
|
|||
[self advanceConfigurationStateMachine];
|
||||
}
|
||||
|
||||
- (NSButton *)analogControlsCheckbox
|
||||
{
|
||||
return _analogControlsCheckbox;
|
||||
}
|
||||
|
||||
- (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox
|
||||
{
|
||||
_analogControlsCheckbox = analogControlsCheckbox;
|
||||
[_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]];
|
||||
}
|
||||
|
||||
- (NSButton *)aspectRatioCheckbox
|
||||
{
|
||||
return _aspectRatioCheckbox;
|
||||
}
|
||||
|
||||
- (void)setAspectRatioCheckbox:(NSButton *)aspectRatioCheckbox
|
||||
{
|
||||
_aspectRatioCheckbox = aspectRatioCheckbox;
|
||||
[_aspectRatioCheckbox setState: ![[NSUserDefaults standardUserDefaults] boolForKey:@"GBAspectRatioUnkept"]];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[super awakeFromNib];
|
||||
|
@ -554,6 +313,89 @@
|
|||
[[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil];
|
||||
[JOYController registerListener:self];
|
||||
joystick_configuration_state = -1;
|
||||
[self refreshJoypadMenu:nil];
|
||||
|
||||
NSString *keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBJoypadHotkey1"];
|
||||
for (NSMenuItem *item in _hotkey1PopupButton.menu.itemArray) {
|
||||
if ([keyEquivalent isEqualToString:keyEquivalentString(item)]) {
|
||||
[_hotkey1PopupButton selectItem:item];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
keyEquivalent = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBJoypadHotkey2"];
|
||||
for (NSMenuItem *item in _hotkey2PopupButton.menu.itemArray) {
|
||||
if ([keyEquivalent isEqualToString:keyEquivalentString(item)]) {
|
||||
[_hotkey2PopupButton selectItem:item];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[self updatePalettesMenu];
|
||||
NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorPalette"];
|
||||
if (mode >= 0) {
|
||||
[_colorPalettePopupButton selectItemWithTag:mode];
|
||||
}
|
||||
else {
|
||||
[_colorPalettePopupButton selectItemWithTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"] ?: @""];
|
||||
}
|
||||
|
||||
_fontSizeStepper.intValue = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"];
|
||||
[self updateFonts];
|
||||
}
|
||||
|
||||
- (IBAction)fontSizeChanged:(id)sender
|
||||
{
|
||||
NSString *selectedFont = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBDebuggerFont"];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:[sender intValue] forKey:@"GBDebuggerFontSize"];
|
||||
[_fontPopupButton setDisplayTitle:[NSString stringWithFormat:@"%@ %upt", selectedFont, (unsigned)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"]]];
|
||||
}
|
||||
|
||||
- (IBAction)fontChanged:(id)sender
|
||||
{
|
||||
NSString *selectedFont = _fontPopupButton.selectedItem.title;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:selectedFont forKey:@"GBDebuggerFont"];
|
||||
[_fontPopupButton setDisplayTitle:[NSString stringWithFormat:@"%@ %upt", selectedFont, (unsigned)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"]]];
|
||||
|
||||
}
|
||||
|
||||
- (void)updateFonts
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSFontManager *fontManager = [NSFontManager sharedFontManager];
|
||||
NSArray *allFamilies = [fontManager availableFontFamilies];
|
||||
NSMutableSet *families = [NSMutableSet set];
|
||||
for (NSString *family in allFamilies) {
|
||||
if ([fontManager fontNamed:family hasTraits:NSFixedPitchFontMask]) {
|
||||
[families addObject:family];
|
||||
}
|
||||
}
|
||||
|
||||
bool hasSFMono = false;
|
||||
if (@available(macOS 10.15, *)) {
|
||||
hasSFMono = [[NSFont monospacedSystemFontOfSize:12 weight:NSFontWeightRegular].displayName containsString:@"SF"];
|
||||
}
|
||||
|
||||
if (hasSFMono) {
|
||||
[families addObject:@"SF Mono"];
|
||||
}
|
||||
|
||||
NSArray *sortedFamilies = [[families allObjects] sortedArrayUsingSelector:@selector(compare:)];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (![families containsObject:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBDebuggerFont"]]) {
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"GBDebuggerFont"];
|
||||
}
|
||||
|
||||
[_fontPopupButton.menu removeAllItems];
|
||||
for (NSString *family in sortedFamilies) {
|
||||
[_fontPopupButton addItemWithTitle:family];
|
||||
}
|
||||
NSString *selectedFont = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBDebuggerFont"];
|
||||
[_fontPopupButton selectItemWithTitle:selectedFont];
|
||||
[_fontPopupButton setDisplayTitle:[NSString stringWithFormat:@"%@ %upt", selectedFont, (unsigned)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDebuggerFontSize"]]];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
|
@ -606,77 +448,6 @@
|
|||
[self updateBootROMFolderButton];
|
||||
}
|
||||
|
||||
- (void)setDmgPopupButton:(NSPopUpButton *)dmgPopupButton
|
||||
{
|
||||
_dmgPopupButton = dmgPopupButton;
|
||||
[_dmgPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"]];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)dmgPopupButton
|
||||
{
|
||||
return _dmgPopupButton;
|
||||
}
|
||||
|
||||
- (void)setSgbPopupButton:(NSPopUpButton *)sgbPopupButton
|
||||
{
|
||||
_sgbPopupButton = sgbPopupButton;
|
||||
[_sgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"]];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)sgbPopupButton
|
||||
{
|
||||
return _sgbPopupButton;
|
||||
}
|
||||
|
||||
- (void)setCgbPopupButton:(NSPopUpButton *)cgbPopupButton
|
||||
{
|
||||
_cgbPopupButton = cgbPopupButton;
|
||||
[_cgbPopupButton selectItemWithTag:[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)cgbPopupButton
|
||||
{
|
||||
return _cgbPopupButton;
|
||||
}
|
||||
|
||||
- (IBAction)dmgModelChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||
forKey:@"GBDMGModel"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBDMGModelChanged" object:nil];
|
||||
|
||||
}
|
||||
|
||||
- (IBAction)sgbModelChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||
forKey:@"GBSGBModel"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBSGBModelChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)cgbModelChanged:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||
forKey:@"GBCGBModel"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBCGBModelChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)reloadButtonsData:(id)sender
|
||||
{
|
||||
[self.controlsTableView reloadData];
|
||||
}
|
||||
|
||||
- (void)setPreferredJoypadButton:(NSPopUpButton *)preferredJoypadButton
|
||||
{
|
||||
_preferredJoypadButton = preferredJoypadButton;
|
||||
[self refreshJoypadMenu:nil];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)preferredJoypadButton
|
||||
{
|
||||
return _preferredJoypadButton;
|
||||
}
|
||||
|
||||
- (void)controllerConnected:(JOYController *)controller
|
||||
{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
|
@ -740,55 +511,48 @@
|
|||
[[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"];
|
||||
}
|
||||
|
||||
- (NSButton *)autoUpdatesCheckbox
|
||||
- (IBAction)displayColorCorrectionHelp:(id)sender
|
||||
{
|
||||
return _autoUpdatesCheckbox;
|
||||
[GBWarningPopover popoverWithContents:
|
||||
GB_inline_const(NSString *[], {
|
||||
[GB_COLOR_CORRECTION_DISABLED] = @"Colors are directly interpreted as sRGB, resulting in unbalanced colors and inaccurate hues.",
|
||||
[GB_COLOR_CORRECTION_CORRECT_CURVES] = @"Colors have their brightness corrected, but hues remain unbalanced.",
|
||||
[GB_COLOR_CORRECTION_MODERN_BALANCED] = @"Emulates a modern display. Blue contrast is moderately enhanced at the cost of slight hue inaccuracy.",
|
||||
[GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST] = @"Like Modern – Balanced, but further boosts the contrast of greens and magentas that is lacking on the original hardware.",
|
||||
[GB_COLOR_CORRECTION_REDUCE_CONTRAST] = @"Slightly reduce the contrast to better represent the tint and contrast of the original display.",
|
||||
[GB_COLOR_CORRECTION_LOW_CONTRAST] = @"Harshly reduce the contrast to accurately represent the tint and low contrast of the original display.",
|
||||
[GB_COLOR_CORRECTION_MODERN_ACCURATE] = @"Emulates a modern display. Colors have their hues and brightness corrected.",
|
||||
}) [self.colorCorrectionPopupButton.selectedItem.tag]
|
||||
title:self.colorCorrectionPopupButton.selectedItem.title
|
||||
onView:sender
|
||||
timeout:6
|
||||
preferredEdge:NSRectEdgeMaxX];
|
||||
}
|
||||
|
||||
- (void)setAutoUpdatesCheckbox:(NSButton *)autoUpdatesCheckbox
|
||||
- (IBAction)displayHighPassHelp:(id)sender
|
||||
{
|
||||
_autoUpdatesCheckbox = autoUpdatesCheckbox;
|
||||
[_autoUpdatesCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAutoUpdatesEnabled"]];
|
||||
[GBWarningPopover popoverWithContents:
|
||||
GB_inline_const(NSString *[], {
|
||||
[GB_HIGHPASS_OFF] = @"No high-pass filter will be applied. DC offset will be kept, pausing and resuming will trigger audio pops.",
|
||||
[GB_HIGHPASS_ACCURATE] = @"An accurate high-pass filter will be applied, removing the DC offset while somewhat attenuating the bass.",
|
||||
[GB_HIGHPASS_REMOVE_DC_OFFSET] = @"A high-pass filter will be applied to the DC offset itself, removing the DC offset while preserving the waveform.",
|
||||
}) [self.highpassFilterPopupButton.selectedItem.tag]
|
||||
title:self.highpassFilterPopupButton.selectedItem.title
|
||||
onView:sender
|
||||
timeout:6
|
||||
preferredEdge:NSRectEdgeMaxX];
|
||||
}
|
||||
|
||||
- (NSButton *)OSDCheckbox
|
||||
- (IBAction)arrangeJoyCons:(id)sender
|
||||
{
|
||||
return _OSDCheckbox;
|
||||
[GBJoyConManager sharedInstance].arrangementMode = true;
|
||||
[self beginSheet:self.joyconsSheet completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)setOSDCheckbox:(NSButton *)OSDCheckbox
|
||||
- (IBAction)closeJoyConsSheet:(id)sender
|
||||
{
|
||||
_OSDCheckbox = OSDCheckbox;
|
||||
[_OSDCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBOSDEnabled"]];
|
||||
}
|
||||
|
||||
- (IBAction)changeOSDEnabled:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState
|
||||
forKey:@"GBOSDEnabled"];
|
||||
|
||||
}
|
||||
|
||||
- (IBAction)changeFilterScreenshots:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:[(NSButton *)sender state] == NSOnState
|
||||
forKey:@"GBFilterScreenshots"];
|
||||
}
|
||||
|
||||
- (NSButton *)screenshotFilterCheckbox
|
||||
{
|
||||
return _screenshotFilterCheckbox;
|
||||
}
|
||||
|
||||
- (void)setScreenshotFilterCheckbox:(NSButton *)screenshotFilterCheckbox
|
||||
{
|
||||
_screenshotFilterCheckbox = screenshotFilterCheckbox;
|
||||
if (![GBViewMetal isSupported]) {
|
||||
[_screenshotFilterCheckbox setEnabled:false];
|
||||
}
|
||||
else {
|
||||
[_screenshotFilterCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]];
|
||||
}
|
||||
[self endSheet:self.joyconsSheet];
|
||||
[GBJoyConManager sharedInstance].arrangementMode = false;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qxJ-pH-d0y">
|
||||
<rect key="frame" x="61.5" y="127" width="39" height="23"/>
|
||||
<rect key="frame" x="63.5" y="128" width="38" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Play" imagePosition="only" alignment="center" alternateImage="Pause" state="on" borderStyle="border" focusRingType="none" inset="2" id="3ZK-br-UrS">
|
||||
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
|
||||
|
@ -54,37 +54,14 @@
|
|||
<action selector="togglePause:" target="-2" id="AUe-I7-nOK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
|
||||
<rect key="frame" x="19.5" y="127" width="38" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Rewind" imagePosition="only" alignment="center" state="on" borderStyle="border" focusRingType="none" inset="2" id="ZIF-TP-Fqn">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="jug-AS-bW7"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
|
||||
<rect key="frame" x="106" y="127" width="131" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" 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"/>
|
||||
<menu key="menu" id="Knp-Ok-Pb4"/>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="HET-AT-CfQ"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SRS-M5-VVL">
|
||||
<rect key="frame" x="240.5" y="127" width="72" height="23"/>
|
||||
<rect key="frame" x="244.5" y="128" width="68" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="cmq-I8-cFL">
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment toolTip="Previous Track" image="Previous" width="33"/>
|
||||
<segment toolTip="Next Track" image="Next" width="32" tag="1"/>
|
||||
<segment toolTip="Previous Track" image="Previous" width="31"/>
|
||||
<segment toolTip="Next Track" image="Next" width="30" tag="1"/>
|
||||
</segments>
|
||||
</segmentedCell>
|
||||
<connections>
|
||||
|
@ -114,6 +91,29 @@
|
|||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
|
||||
<rect key="frame" x="20.5" y="128" width="38" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Rewind" imagePosition="only" alignment="center" state="on" borderStyle="border" focusRingType="none" inset="2" id="ZIF-TP-Fqn">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="jug-AS-bW7"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
|
||||
<rect key="frame" x="107" y="127" width="131" height="25"/>
|
||||
<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"/>
|
||||
<menu key="menu" id="Knp-Ok-Pb4"/>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="HET-AT-CfQ"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="67" y="292.5"/>
|
||||
</customView>
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<?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">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="Document">
|
||||
<connections>
|
||||
<outlet property="gbsAuthor" destination="gaD-ZH-Beh" id="2i7-BD-bJ2"/>
|
||||
<outlet property="gbsCopyright" destination="2dl-dH-E3J" id="LnT-Vb-pN6"/>
|
||||
<outlet property="gbsNextPrevButton" destination="SRS-M5-VVL" id="YEN-01-wRX"/>
|
||||
<outlet property="gbsPlayPauseButton" destination="qxJ-pH-d0y" id="qk8-8I-9u5"/>
|
||||
<outlet property="gbsPlayerView" destination="c22-O7-iKe" id="A1w-e5-EQE"/>
|
||||
<outlet property="gbsRewindButton" destination="0yD-Sp-Ilo" id="FgR-xd-JW5"/>
|
||||
<outlet property="gbsTitle" destination="H3v-X3-48q" id="DCl-wL-oy8"/>
|
||||
<outlet property="gbsTracks" destination="I1T-VS-Vse" id="Vk4-GP-RjB"/>
|
||||
<outlet property="gbsVisualizer" destination="Q3o-bK-DIN" id="1YC-C5-Je6"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<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">
|
||||
<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">
|
||||
<font key="font" metaFont="systemBold" size="16"/>
|
||||
<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" 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">
|
||||
<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>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qxJ-pH-d0y">
|
||||
<rect key="frame" x="57" y="124" width="50" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" bezelStyle="rounded" image="Play" imagePosition="only" alignment="center" alternateImage="Pause" state="on" borderStyle="border" focusRingType="none" inset="2" id="3ZK-br-UrS">
|
||||
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="togglePause:" target="-2" id="AUe-I7-nOK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
|
||||
<rect key="frame" x="105" y="127" width="141" height="25"/>
|
||||
<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"/>
|
||||
<menu key="menu" id="Knp-Ok-Pb4"/>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="HET-AT-CfQ"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SRS-M5-VVL">
|
||||
<rect key="frame" x="247" y="129" width="68" height="24"/>
|
||||
<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"/>
|
||||
<segments>
|
||||
<segment toolTip="Previous Track" image="Previous" width="31"/>
|
||||
<segment toolTip="Next Track" image="Next" width="30" tag="1"/>
|
||||
</segments>
|
||||
</segmentedCell>
|
||||
<connections>
|
||||
<action selector="gbsNextPrevPushed:" target="-2" id="roN-Iy-tDQ"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="b9A-cd-ias">
|
||||
<rect key="frame" x="0.0" y="117" width="332" height="5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
<customView appearanceType="darkAqua" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tRy-Gw-IaG" customClass="GBOptionalVisualEffectView">
|
||||
<rect key="frame" x="0.0" y="24" width="332" height="95"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q3o-bK-DIN" customClass="GBVisualizerView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="332" height="95"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
</customView>
|
||||
<textField 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">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
|
||||
<rect key="frame" x="13" y="124" width="50" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" bezelStyle="rounded" image="Rewind" imagePosition="only" alignment="center" state="on" borderStyle="border" focusRingType="none" inset="2" id="ZIF-TP-Fqn">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="jug-AS-bW7"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="67" y="292.5"/>
|
||||
</customView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="Next" width="16" height="10"/>
|
||||
<image name="Pause" width="10" height="10"/>
|
||||
<image name="Play" width="10" height="10"/>
|
||||
<image name="Previous" width="16" height="10"/>
|
||||
<image name="Rewind" width="10" height="10"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -19,6 +19,12 @@
|
|||
return [super dividerColor];
|
||||
}
|
||||
|
||||
- (void)drawDividerInRect:(NSRect)rect
|
||||
{
|
||||
[self.dividerColor set];
|
||||
NSRectFill(rect);
|
||||
}
|
||||
|
||||
/* Mavericks comaptibility */
|
||||
- (NSArray<NSView *> *)arrangedSubviews
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
#import <Core/gb.h>
|
||||
|
||||
@interface GBTerminalTextFieldCell : NSTextFieldCell
|
||||
@property (nonatomic) GB_gameboy_t *gb;
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#import <Carbon/Carbon.h>
|
||||
#import "GBTerminalTextFieldCell.h"
|
||||
#import "NSTextFieldCell+Inset.h"
|
||||
|
||||
@interface GBTerminalTextView : NSTextView
|
||||
{
|
||||
@public __weak NSTextField *_field;
|
||||
}
|
||||
@property GB_gameboy_t *gb;
|
||||
@end
|
||||
|
||||
|
@ -10,7 +14,7 @@
|
|||
GBTerminalTextView *field_editor;
|
||||
}
|
||||
|
||||
- (NSTextView *)fieldEditorForView:(NSView *)controlView
|
||||
- (NSTextView *)fieldEditorForView:(NSTextField *)controlView
|
||||
{
|
||||
if (field_editor) {
|
||||
field_editor.gb = self.gb;
|
||||
|
@ -19,6 +23,10 @@
|
|||
field_editor = [[GBTerminalTextView alloc] init];
|
||||
[field_editor setFieldEditor:true];
|
||||
field_editor.gb = self.gb;
|
||||
field_editor->_field = (NSTextField *)controlView;
|
||||
((NSTextFieldCell *)controlView.cell).textInset =
|
||||
field_editor.textContainerInset =
|
||||
NSMakeSize(0, 2);
|
||||
return field_editor;
|
||||
}
|
||||
|
||||
|
@ -37,7 +45,7 @@
|
|||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return NULL;
|
||||
return nil;
|
||||
}
|
||||
lines = [[NSMutableOrderedSet alloc] init];
|
||||
return self;
|
||||
|
@ -185,14 +193,21 @@
|
|||
return [super resignFirstResponder];
|
||||
}
|
||||
|
||||
-(void)drawRect:(NSRect)dirtyRect
|
||||
- (NSColor *)backgroundColor
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
[super drawRect:dirtyRect];
|
||||
if (reverse_search_mode && [super string].length == 0) {
|
||||
NSMutableDictionary *attributes = [self.typingAttributes mutableCopy];
|
||||
NSColor *color = [attributes[NSForegroundColorAttributeName] colorWithAlphaComponent:0.5];
|
||||
[attributes setObject:color forKey:NSForegroundColorAttributeName];
|
||||
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)];
|
||||
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 2)];
|
||||
}
|
||||
else {
|
||||
[super drawRect:dirtyRect];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBTintedImageCell : NSImageCell
|
||||
@property NSColor *tint;
|
||||
@end
|
|
@ -0,0 +1,20 @@
|
|||
#import "GBTintedImageCell.h"
|
||||
|
||||
@implementation GBTintedImageCell
|
||||
|
||||
- (NSImage *)image
|
||||
{
|
||||
if (!self.tint || !super.image.isTemplate) {
|
||||
return [super image];
|
||||
}
|
||||
|
||||
NSImage *tinted = [super.image copy];
|
||||
[tinted lockFocus];
|
||||
[self.tint set];
|
||||
NSRectFillUsingOperation((NSRect){.size = tinted.size}, NSCompositeSourceIn);
|
||||
[tinted unlockFocus];
|
||||
tinted.template = false;
|
||||
return tinted;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBTitledPopUpButton : NSPopUpButton
|
||||
@property NSString *displayTitle;
|
||||
@end
|
|
@ -0,0 +1,22 @@
|
|||
#import "GBTitledPopUpButton.h"
|
||||
|
||||
@implementation GBTitledPopUpButton
|
||||
|
||||
- (void)setDisplayTitle:(NSString *)displayTitle
|
||||
{
|
||||
if (!displayTitle) {
|
||||
((NSPopUpButtonCell *)self.cell).usesItemFromMenu = true;
|
||||
((NSPopUpButtonCell *)self.cell).menuItem = nil;
|
||||
return;
|
||||
}
|
||||
((NSPopUpButtonCell *)self.cell).usesItemFromMenu = false;
|
||||
((NSPopUpButtonCell *)self.cell).menuItem = [[NSMenuItem alloc] initWithTitle:displayTitle action:nil keyEquivalent:@""];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)displayTitle
|
||||
{
|
||||
return ((NSPopUpButtonCell *)self.cell).menuItem.title;
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,31 +1,16 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
#import "GBOSDView.h"
|
||||
#import "GBViewBase.h"
|
||||
|
||||
|
||||
@class Document;
|
||||
|
||||
typedef enum {
|
||||
GB_FRAME_BLENDING_MODE_DISABLED,
|
||||
GB_FRAME_BLENDING_MODE_SIMPLE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE,
|
||||
GB_FRAME_BLENDING_MODE_ACCURATE_ODD,
|
||||
} GB_frame_blending_mode_t;
|
||||
|
||||
@interface GBView : NSView<JOYListener>
|
||||
- (void) flip;
|
||||
- (uint32_t *) pixels;
|
||||
@interface GBView : GBViewBase<JOYListener>
|
||||
@property (nonatomic, weak) IBOutlet Document *document;
|
||||
@property (nonatomic) GB_gameboy_t *gb;
|
||||
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
|
||||
@property (nonatomic, getter=isMouseHidingEnabled) bool mouseHidingEnabled;
|
||||
@property (nonatomic) bool isRewinding;
|
||||
@property (nonatomic, strong) NSView *internalView;
|
||||
@property (weak) GBOSDView *osdView;
|
||||
- (void) createInternalView;
|
||||
- (uint32_t *)currentBuffer;
|
||||
- (uint32_t *)previousBuffer;
|
||||
- (void)screenSizeChanged;
|
||||
- (void)setRumble: (double)amp;
|
||||
- (NSImage *)renderToImage;
|
||||
- (void)setRumble: (double)amp;
|
||||
@end
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#import "GBViewMetal.h"
|
||||
#import "GBButtons.h"
|
||||
#import "NSString+StringForKey.h"
|
||||
#import "NSObject+DefaultsObserver.h"
|
||||
#import "Document.h"
|
||||
|
||||
#define JOYSTICK_HIGH 0x4000
|
||||
|
@ -104,8 +105,6 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
|
||||
@implementation GBView
|
||||
{
|
||||
uint32_t *image_buffers[3];
|
||||
unsigned char current_buffer;
|
||||
bool mouse_hidden;
|
||||
NSTrackingArea *tracking_area;
|
||||
bool _mouseHidingEnabled;
|
||||
|
@ -116,8 +115,13 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
bool analogClockMultiplierValid;
|
||||
NSEventModifierFlags previousModifiers;
|
||||
JOYController *lastController;
|
||||
GB_frame_blending_mode_t _frameBlendingMode;
|
||||
bool _turbo;
|
||||
bool _mouseControlEnabled;
|
||||
NSMutableDictionary<NSNumber *, JOYController *> *_controllerMapping;
|
||||
unsigned _lastPlayerCount;
|
||||
|
||||
bool _rapidA[4], _rapidB[4];
|
||||
uint8_t _rapidACount[4], _rapidBCount[4];
|
||||
}
|
||||
|
||||
+ (instancetype)alloc
|
||||
|
@ -136,18 +140,19 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
return [super allocWithZone:zone];
|
||||
}
|
||||
|
||||
- (void) createInternalView
|
||||
{
|
||||
assert(false && "createInternalView must not be inherited");
|
||||
}
|
||||
|
||||
- (void) _init
|
||||
{
|
||||
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil];
|
||||
__unsafe_unretained GBView *weakSelf = self;
|
||||
[self observeStandardDefaultsKey:@"GBAspectRatioUnkept" withBlock:^(id newValue) {
|
||||
[weakSelf setFrame:weakSelf.superview.frame];
|
||||
}];
|
||||
[self observeStandardDefaultsKey:@"JoyKitDefaultControllers" withBlock:^(id newValue) {
|
||||
[weakSelf reassignControllers];
|
||||
}];
|
||||
tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseMoved
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:tracking_area];
|
||||
|
@ -156,57 +161,98 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
[self addSubview:self.internalView];
|
||||
self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
[JOYController registerListener:self];
|
||||
_mouseControlEnabled = true;
|
||||
[self reassignControllers];
|
||||
}
|
||||
|
||||
- (void)controllerConnected:(JOYController *)controller
|
||||
{
|
||||
[self reassignControllers];
|
||||
}
|
||||
|
||||
- (void)controllerDisconnected:(JOYController *)controller
|
||||
{
|
||||
[self reassignControllers];
|
||||
}
|
||||
|
||||
- (unsigned)playerCount
|
||||
{
|
||||
if (self.document.partner) {
|
||||
return 2;
|
||||
}
|
||||
if (!_gb) {
|
||||
return 1;
|
||||
}
|
||||
return GB_get_player_count(_gb);
|
||||
}
|
||||
|
||||
- (void)reassignControllers
|
||||
{
|
||||
unsigned playerCount = self.playerCount;
|
||||
/* Don't assign controlelrs if there's only one player, allow all controllers. */
|
||||
if (playerCount == 1) {
|
||||
_controllerMapping = [NSMutableDictionary dictionary];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_controllerMapping) {
|
||||
_controllerMapping = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
for (NSNumber *player in [_controllerMapping copy]) {
|
||||
if (player.unsignedIntValue >= playerCount || !_controllerMapping[player].connected) {
|
||||
[_controllerMapping removeObjectForKey:player];
|
||||
}
|
||||
}
|
||||
|
||||
_lastPlayerCount = playerCount;
|
||||
for (unsigned i = 0; i < playerCount; i++) {
|
||||
NSString *preferredJoypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"]
|
||||
objectForKey:n2s(i)];
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
if (!controller.connected) continue;
|
||||
if ([controller.uniqueID isEqual:preferredJoypad]) {
|
||||
_controllerMapping[@(i)] = controller;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tryAssigningController:(JOYController *)controller
|
||||
{
|
||||
unsigned playerCount = self.playerCount;
|
||||
if (playerCount == 1) return;
|
||||
if (_controllerMapping.count == playerCount) return;
|
||||
if ([_controllerMapping.allValues containsObject:controller]) return;
|
||||
for (unsigned i = 0; i < playerCount; i++) {
|
||||
if (!_controllerMapping[@(i)]) {
|
||||
_controllerMapping[@(i)] = controller;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary<NSNumber *, JOYController *> *)controllerMapping
|
||||
{
|
||||
if (_lastPlayerCount != self.playerCount) {
|
||||
[self reassignControllers];
|
||||
}
|
||||
|
||||
return _controllerMapping;
|
||||
}
|
||||
|
||||
- (void)screenSizeChanged
|
||||
{
|
||||
if (image_buffers[0]) free(image_buffers[0]);
|
||||
if (image_buffers[1]) free(image_buffers[1]);
|
||||
if (image_buffers[2]) free(image_buffers[2]);
|
||||
|
||||
size_t buffer_size = sizeof(image_buffers[0][0]) * GB_get_screen_width(_gb) * GB_get_screen_height(_gb);
|
||||
|
||||
image_buffers[0] = calloc(1, buffer_size);
|
||||
image_buffers[1] = calloc(1, buffer_size);
|
||||
image_buffers[2] = calloc(1, buffer_size);
|
||||
[super screenSizeChanged];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self setFrame:self.superview.frame];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) ratioKeepingChanged
|
||||
{
|
||||
[self setFrame:self.superview.frame];
|
||||
}
|
||||
|
||||
- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode
|
||||
{
|
||||
_frameBlendingMode = frameBlendingMode;
|
||||
[self setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
|
||||
- (GB_frame_blending_mode_t)frameBlendingMode
|
||||
{
|
||||
if (_frameBlendingMode == GB_FRAME_BLENDING_MODE_ACCURATE) {
|
||||
if (!_gb || GB_is_sgb(_gb)) {
|
||||
return GB_FRAME_BLENDING_MODE_SIMPLE;
|
||||
}
|
||||
return GB_is_odd_frame(_gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN;
|
||||
}
|
||||
return _frameBlendingMode;
|
||||
}
|
||||
- (unsigned char) numberOfBuffers
|
||||
{
|
||||
return _frameBlendingMode? 3 : 2;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
free(image_buffers[0]);
|
||||
free(image_buffers[1]);
|
||||
free(image_buffers[2]);
|
||||
if (mouse_hidden) {
|
||||
mouse_hidden = false;
|
||||
[NSCursor unhide];
|
||||
|
@ -215,6 +261,7 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
[self setRumble:0];
|
||||
[JOYController unregisterListener:self];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if (!(self = [super initWithCoder:coder])) {
|
||||
|
@ -293,18 +340,24 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
}
|
||||
if ((!analogClockMultiplierValid && clockMultiplier > 1) ||
|
||||
_turbo || (analogClockMultiplierValid && analogClockMultiplier > 1)) {
|
||||
[self.osdView displayText:@"Fast forwarding..."];
|
||||
[self.osdView displayText:@"Fast forwarding…"];
|
||||
}
|
||||
else if ((!analogClockMultiplierValid && clockMultiplier < 1) ||
|
||||
(analogClockMultiplierValid && analogClockMultiplier < 1)) {
|
||||
[self.osdView displayText:@"Slow motion..."];
|
||||
[self.osdView displayText:@"Slow motion…"];
|
||||
}
|
||||
current_buffer = (current_buffer + 1) % self.numberOfBuffers;
|
||||
}
|
||||
|
||||
- (uint32_t *) pixels
|
||||
{
|
||||
return image_buffers[(current_buffer + 1) % self.numberOfBuffers];
|
||||
|
||||
for (unsigned i = GB_get_player_count(_gb); i--;) {
|
||||
if (_rapidA[i]) {
|
||||
_rapidACount[i]++;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_A, i, !(_rapidACount[i] & 2));
|
||||
}
|
||||
if (_rapidB[i]) {
|
||||
_rapidBCount[i]++;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_B, i, !(_rapidBCount[i] & 2));
|
||||
}
|
||||
}
|
||||
[super flip];
|
||||
}
|
||||
|
||||
-(void)keyDown:(NSEvent *)theEvent
|
||||
|
@ -331,7 +384,7 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
player_count = 2;
|
||||
}
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
for (GBButton button = 0; button < GBButtonCount; button++) {
|
||||
for (GBButton button = 0; button < GBKeyboardButtonCount; button++) {
|
||||
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
|
||||
if (!key) continue;
|
||||
|
||||
|
@ -362,17 +415,38 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
case GBRapidA:
|
||||
_rapidA[player] = true;
|
||||
_rapidACount[player] = 0;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_A, player, true);
|
||||
break;
|
||||
|
||||
case GBRapidB:
|
||||
_rapidB[player] = true;
|
||||
_rapidBCount[player] = 0;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_B, player, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (self.document.partner) {
|
||||
if (player == 0) {
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, true);
|
||||
if ((GB_key_t)button <= GB_KEY_DOWN) {
|
||||
GB_set_use_faux_analog_inputs(_gb, 0, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, true);
|
||||
if ((GB_key_t)button <= GB_KEY_DOWN) {
|
||||
GB_set_use_faux_analog_inputs(self.document.partner.gb, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
|
||||
if ((GB_key_t)button <= GB_KEY_DOWN) {
|
||||
GB_set_use_faux_analog_inputs(_gb, player, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -405,7 +479,7 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
player_count = 2;
|
||||
}
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
for (GBButton button = 0; button < GBButtonCount; button++) {
|
||||
for (GBButton button = 0; button < GBKeyboardButtonCount; button++) {
|
||||
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
|
||||
if (!key) continue;
|
||||
|
||||
|
@ -431,6 +505,16 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
underclockKeyDown = false;
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
case GBRapidA:
|
||||
_rapidA[player] = false;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_A, player, false);
|
||||
break;
|
||||
|
||||
case GBRapidB:
|
||||
_rapidB[player] = false;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_B, player, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (self.document.partner) {
|
||||
|
@ -459,9 +543,34 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
[lastController setRumbleAmplitude:amp];
|
||||
}
|
||||
|
||||
- (bool)shouldControllerUseJoystickForMotion:(JOYController *)controller
|
||||
{
|
||||
if (!_gb) return false;
|
||||
if (!GB_has_accelerometer(_gb)) return false;
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return true;
|
||||
for (JOYAxes3D *axes in controller.axes3D) {
|
||||
if (axes.usage == JOYAxes3DUsageOrientation || axes.usage == JOYAxes3DUsageAcceleration) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
- (bool)allowController
|
||||
{
|
||||
if ([self.window isMainWindow]) return true;
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBAllowBackgroundControllers"]) {
|
||||
if ([(Document *)[NSApplication sharedApplication].orderedDocuments.firstObject mainWindow] == self.window) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis
|
||||
{
|
||||
if (![self.window isMainWindow]) return;
|
||||
if (!_gb) return;
|
||||
if (![self allowController]) return;
|
||||
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
|
||||
if (!mapping) {
|
||||
|
@ -481,58 +590,156 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
}
|
||||
}
|
||||
|
||||
- (bool)controller:(JOYController *)controller applicableForPlayer:(unsigned)player effectivePlayer:(unsigned *)effectivePlayer effectiveGB:(GB_gameboy_t **)effectiveGB
|
||||
{
|
||||
NSDictionary<NSNumber *, JOYController *> *controllerMapping = [self controllerMapping];
|
||||
|
||||
JOYController *preferredJoypad = controllerMapping[@(player)];
|
||||
if (preferredJoypad && preferredJoypad != controller) return false; // The player has a different assigned controller
|
||||
if (!preferredJoypad && self.playerCount != 1) return false; // The player has no assigned controller in multiplayer mode, prevent controller inputs
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[controller setPlayerLEDs:[controller LEDMaskForPlayer:player]];
|
||||
});
|
||||
|
||||
*effectiveGB = _gb;
|
||||
*effectivePlayer = player;
|
||||
|
||||
if (player && self.document.partner) {
|
||||
*effectiveGB = self.document.partner.gb;
|
||||
*effectivePlayer = 0;
|
||||
if (controller != self.document.partner.view->lastController) {
|
||||
[self setRumble:0];
|
||||
self.document.partner.view->lastController = controller;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (controller != lastController) {
|
||||
[self setRumble:0];
|
||||
lastController = controller;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes
|
||||
{
|
||||
if (!_gb) return;
|
||||
/* Always handle only the most dominant 2D input. */
|
||||
for (JOYAxes2D *otherAxes in controller.axes2D) {
|
||||
if (otherAxes == axes) continue;
|
||||
if (otherAxes.distance > axes.distance) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
if ([self shouldControllerUseJoystickForMotion:controller] && !self.mouseControlsActive) {
|
||||
GB_set_accelerometer_values(_gb, -axes.value.x, -axes.value.y);
|
||||
}
|
||||
else if ([defaults boolForKey:@"GBFauxAnalogInputs"]) {
|
||||
unsigned playerCount = self.playerCount;
|
||||
for (unsigned player = 0; player < playerCount; player++) {
|
||||
unsigned effectivePlayer;
|
||||
GB_gameboy_t *effectiveGB;
|
||||
if (![self controller:controller applicableForPlayer:player effectivePlayer:&effectivePlayer effectiveGB:&effectiveGB]) continue;
|
||||
|
||||
GB_set_use_faux_analog_inputs(effectiveGB, effectivePlayer, true);
|
||||
NSPoint position = axes.value;
|
||||
GB_set_faux_analog_inputs(effectiveGB, effectivePlayer, position.x, position.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller movedAxes3D:(JOYAxes3D *)axes
|
||||
{
|
||||
if (!_gb) return;
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return;
|
||||
if (self.mouseControlsActive) return;
|
||||
if (controller != lastController) return;
|
||||
// When using Joy-Cons in dual-controller grip, ignore motion data from the left Joy-Con
|
||||
if (controller.joyconType == JOYJoyConTypeDual) {
|
||||
for (JOYController *child in [(JOYCombinedController *)controller children]) {
|
||||
if (child.joyconType != JOYJoyConTypeRight && [child.axes3D containsObject:axes]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary<NSNumber *, JOYController *> *controllerMapping = [self controllerMapping];
|
||||
GB_gameboy_t *effectiveGB = _gb;
|
||||
|
||||
if (self.document.partner) {
|
||||
if (controllerMapping[@1] == controller) {
|
||||
effectiveGB = self.document.partner.gb;
|
||||
}
|
||||
if (controllerMapping[@0] != controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (axes.usage == JOYAxes3DUsageOrientation) {
|
||||
for (JOYAxes3D *axes in controller.axes3D) {
|
||||
// Only use orientation if there's no acceleration axes
|
||||
if (axes.usage == JOYAxes3DUsageAcceleration) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
JOYPoint3D point = axes.normalizedValue;
|
||||
GB_set_accelerometer_values(effectiveGB, point.x, point.z);
|
||||
}
|
||||
else if (axes.usage == JOYAxes3DUsageAcceleration) {
|
||||
JOYPoint3D point = axes.gUnitsValue;
|
||||
GB_set_accelerometer_values(effectiveGB, point.x, point.z);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
|
||||
{
|
||||
if (![self.window isMainWindow]) return;
|
||||
if (!_gb) return;
|
||||
if (![self allowController]) return;
|
||||
_mouseControlEnabled = false;
|
||||
if (button.type == JOYButtonTypeAxes2DEmulated && [self shouldControllerUseJoystickForMotion:controller]) return;
|
||||
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
if (self.document.partner) {
|
||||
player_count = 2;
|
||||
}
|
||||
[self tryAssigningController:controller];
|
||||
|
||||
unsigned playerCount = self.playerCount;
|
||||
|
||||
IOPMAssertionID assertionID;
|
||||
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
|
||||
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"]
|
||||
objectForKey:n2s(player)];
|
||||
if (player_count != 1 && // Single player, accpet inputs from all joypads
|
||||
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
|
||||
![preferred_joypad isEqualToString:controller.uniqueID]) {
|
||||
continue;
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[controller setPlayerLEDs:[controller LEDMaskForPlayer:player]];
|
||||
});
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
bool fauxAnalog = [defaults boolForKey:@"GBFauxAnalogInputs"];
|
||||
|
||||
for (unsigned player = 0; player < playerCount; player++) {
|
||||
unsigned effectivePlayer;
|
||||
GB_gameboy_t *effectiveGB;
|
||||
if (![self controller:controller applicableForPlayer:player effectivePlayer:&effectivePlayer effectiveGB:&effectiveGB]) continue;
|
||||
|
||||
NSDictionary *mapping = [defaults dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
|
||||
if (!mapping) {
|
||||
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
|
||||
mapping = [defaults dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
|
||||
}
|
||||
|
||||
JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: button.usage;
|
||||
if (!mapping && usage >= JOYButtonUsageGeneric0) {
|
||||
usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3];
|
||||
usage = GB_inline_const(JOYButtonUsage[], {JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX})[(usage - JOYButtonUsageGeneric0) & 3];
|
||||
}
|
||||
|
||||
GB_gameboy_t *effectiveGB = _gb;
|
||||
unsigned effectivePlayer = player;
|
||||
|
||||
if (player && self.document.partner) {
|
||||
effectiveGB = self.document.partner.gb;
|
||||
effectivePlayer = 0;
|
||||
if (controller != self.document.partner.view->lastController) {
|
||||
[self setRumble:0];
|
||||
self.document.partner.view->lastController = controller;
|
||||
if (usage >= JOYButtonUsageDPadLeft && usage <= JOYButtonUsageDPadDown) {
|
||||
if (fauxAnalog && button.type == JOYButtonTypeAxes2DEmulated) {
|
||||
// This isn't a real button, it's an emulated Axes2D. We want to handle it as an Axes2D instead
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (controller != lastController) {
|
||||
[self setRumble:0];
|
||||
lastController = controller;
|
||||
else {
|
||||
// User used a digital direction input, revert to non-analog inputs
|
||||
GB_set_use_faux_analog_inputs(effectiveGB, effectivePlayer, false);
|
||||
}
|
||||
}
|
||||
|
||||
switch (usage) {
|
||||
switch ((unsigned)usage) {
|
||||
|
||||
case JOYButtonUsageNone: break;
|
||||
case JOYButtonUsageA: GB_set_key_state_for_player(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break;
|
||||
|
@ -577,7 +784,16 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
case JOYButtonUsageDPadUp: GB_set_key_state_for_player(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break;
|
||||
case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break;
|
||||
|
||||
default:
|
||||
case GBJoyKitRapidA:
|
||||
_rapidA[effectivePlayer] = button.isPressed;
|
||||
_rapidACount[effectivePlayer] = 0;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_A, effectivePlayer, button.isPressed);
|
||||
break;
|
||||
|
||||
case GBJoyKitRapidB:
|
||||
_rapidB[effectivePlayer] = button.isPressed;
|
||||
_rapidBCount[effectivePlayer] = 0;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_B, effectivePlayer, button.isPressed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -588,11 +804,18 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
return true;
|
||||
}
|
||||
|
||||
- (bool)mouseControlsActive
|
||||
{
|
||||
return _gb && GB_is_inited(_gb) && GB_has_accelerometer(_gb) &&
|
||||
_mouseControlEnabled && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7AllowMouse"];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)theEvent
|
||||
{
|
||||
if (!mouse_hidden) {
|
||||
mouse_hidden = true;
|
||||
if (_mouseHidingEnabled) {
|
||||
if (_mouseHidingEnabled &&
|
||||
!self.mouseControlsActive) {
|
||||
[NSCursor hide];
|
||||
}
|
||||
}
|
||||
|
@ -610,6 +833,46 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
[super mouseExited:theEvent];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
_mouseControlEnabled = true;
|
||||
if (self.mouseControlsActive) {
|
||||
if (event.type == NSEventTypeLeftMouseDown) {
|
||||
GB_set_key_state(_gb, GB_KEY_A, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent *)event
|
||||
{
|
||||
if (self.mouseControlsActive) {
|
||||
if (event.type == NSEventTypeLeftMouseUp) {
|
||||
GB_set_key_state(_gb, GB_KEY_A, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event
|
||||
{
|
||||
if (self.mouseControlsActive) {
|
||||
NSPoint point = [self convertPoint:[event locationInWindow] toView:nil];
|
||||
|
||||
point.x /= self.frame.size.width;
|
||||
point.x *= 2;
|
||||
point.x -= 1;
|
||||
|
||||
point.y /= self.frame.size.height;
|
||||
point.y *= 2;
|
||||
point.y -= 1;
|
||||
|
||||
if (GB_get_screen_width(_gb) != 160) { // has border
|
||||
point.x *= 256 / 160.0;
|
||||
point.y *= 224 / 114.0;
|
||||
}
|
||||
GB_set_accelerometer_values(_gb, -point.x, point.y);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMouseHidingEnabled:(bool)mouseHidingEnabled
|
||||
{
|
||||
if (mouseHidingEnabled == _mouseHidingEnabled) return;
|
||||
|
@ -642,16 +905,6 @@ static const uint8_t workboy_vk_to_key[] = {
|
|||
previousModifiers = event.modifierFlags;
|
||||
}
|
||||
|
||||
- (uint32_t *)currentBuffer
|
||||
{
|
||||
return image_buffers[current_buffer];
|
||||
}
|
||||
|
||||
- (uint32_t *)previousBuffer
|
||||
{
|
||||
return image_buffers[(current_buffer + 2) % self.numberOfBuffers];
|
||||
}
|
||||
|
||||
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
|
||||
{
|
||||
NSPasteboard *pboard = [sender draggingPasteboard];
|
||||
|
|