2022-02-19 23:58:59 +00:00
|
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
|
|
#import <CoreAudio/CoreAudio.h>
|
|
|
|
|
#import <Core/gb.h>
|
|
|
|
|
#import "GBAudioClient.h"
|
|
|
|
|
#import "Document.h"
|
|
|
|
|
#import "AppDelegate.h"
|
|
|
|
|
#import "HexFiend/HexFiend.h"
|
|
|
|
|
#import "GBMemoryByteArray.h"
|
|
|
|
|
#import "GBWarningPopover.h"
|
|
|
|
|
#import "GBCheatWindowController.h"
|
|
|
|
|
#import "GBTerminalTextFieldCell.h"
|
|
|
|
|
#import "BigSurToolbar.h"
|
2021-11-05 17:07:27 +00:00
|
|
|
|
#import "GBPaletteEditorController.h"
|
2022-02-19 23:58:59 +00:00
|
|
|
|
#import "GBObjectView.h"
|
2022-02-20 12:23:49 +00:00
|
|
|
|
#import "GBPaletteView.h"
|
2016-03-30 20:07:55 +00:00
|
|
|
|
|
2022-02-25 23:47:47 +00:00
|
|
|
|
@implementation NSString (relativePath)
|
|
|
|
|
|
|
|
|
|
- (NSString *)pathRelativeToDirectory:(NSString *)directory
|
|
|
|
|
{
|
|
|
|
|
NSMutableArray<NSString *> *baseComponents = [[directory pathComponents] mutableCopy];
|
|
|
|
|
NSMutableArray<NSString *> *selfComponents = [[self pathComponents] mutableCopy];
|
|
|
|
|
|
|
|
|
|
while (baseComponents.count) {
|
|
|
|
|
if (![baseComponents.firstObject isEqualToString:selfComponents.firstObject]) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[baseComponents removeObjectAtIndex:0];
|
|
|
|
|
[selfComponents removeObjectAtIndex:0];
|
|
|
|
|
}
|
|
|
|
|
while (baseComponents.count) {
|
|
|
|
|
[baseComponents removeObjectAtIndex:0];
|
|
|
|
|
[selfComponents insertObject:@".." atIndex:0];
|
|
|
|
|
}
|
|
|
|
|
return [selfComponents componentsJoinedByString:@"/"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
2021-11-06 23:10:58 +00:00
|
|
|
|
#define GB_MODEL_PAL_BIT_OLD 0x1000
|
|
|
|
|
|
2016-08-19 11:54:54 +00:00
|
|
|
|
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */
|
2016-10-26 21:14:02 +00:00
|
|
|
|
/* Todo: Split into category files! This is so messy!!! */
|
2016-08-19 11:54:54 +00:00
|
|
|
|
|
2018-01-13 11:16:33 +00:00
|
|
|
|
enum model {
|
|
|
|
|
MODEL_NONE,
|
|
|
|
|
MODEL_DMG,
|
|
|
|
|
MODEL_CGB,
|
|
|
|
|
MODEL_AGB,
|
2018-11-10 23:16:32 +00:00
|
|
|
|
MODEL_SGB,
|
2021-10-23 20:28:54 +00:00
|
|
|
|
MODEL_MGB,
|
2018-01-13 11:16:33 +00:00
|
|
|
|
};
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
@interface Document ()
|
|
|
|
|
{
|
2018-06-21 18:23:57 +00:00
|
|
|
|
|
|
|
|
|
NSMutableAttributedString *pending_console_output;
|
|
|
|
|
NSRecursiveLock *console_output_lock;
|
|
|
|
|
NSTimer *console_output_timer;
|
2020-11-13 21:07:35 +00:00
|
|
|
|
NSTimer *hex_timer;
|
2018-06-21 18:23:57 +00:00
|
|
|
|
|
2016-07-03 18:32:58 +00:00
|
|
|
|
bool fullScreen;
|
2016-07-17 21:39:43 +00:00
|
|
|
|
bool in_sync_input;
|
2016-08-12 19:49:17 +00:00
|
|
|
|
HFController *hex_controller;
|
2016-07-03 18:32:58 +00:00
|
|
|
|
|
2016-03-30 20:31:34 +00:00
|
|
|
|
NSString *lastConsoleInput;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
HFLineCountingRepresenter *lineRep;
|
2016-10-02 21:26:12 +00:00
|
|
|
|
|
|
|
|
|
CVImageBufferRef cameraImage;
|
|
|
|
|
AVCaptureSession *cameraSession;
|
|
|
|
|
AVCaptureConnection *cameraConnection;
|
|
|
|
|
AVCaptureStillImageOutput *cameraOutput;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
|
2022-02-19 23:58:59 +00:00
|
|
|
|
GB_oam_info_t _oamInfo[40];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
|
|
|
|
|
NSMutableData *currentPrinterImageData;
|
2020-11-13 21:07:35 +00:00
|
|
|
|
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory;
|
2017-02-24 16:15:31 +00:00
|
|
|
|
|
|
|
|
|
bool rom_warning_issued;
|
|
|
|
|
|
|
|
|
|
NSMutableString *capturedOutput;
|
2017-05-26 17:16:19 +00:00
|
|
|
|
bool logToSideView;
|
|
|
|
|
bool shouldClearSideView;
|
2018-01-13 11:16:33 +00:00
|
|
|
|
enum model current_model;
|
2018-02-10 12:42:14 +00:00
|
|
|
|
|
|
|
|
|
bool rewind;
|
2018-12-01 15:16:50 +00:00
|
|
|
|
bool modelsChanging;
|
2019-06-15 20:22:27 +00:00
|
|
|
|
|
|
|
|
|
NSCondition *audioLock;
|
|
|
|
|
GB_sample_t *audioBuffer;
|
|
|
|
|
size_t audioBufferSize;
|
|
|
|
|
size_t audioBufferPosition;
|
|
|
|
|
size_t audioBufferNeeded;
|
2021-12-29 15:21:06 +00:00
|
|
|
|
double _volume;
|
2020-02-08 11:28:46 +00:00
|
|
|
|
|
|
|
|
|
bool borderModeChanged;
|
2020-11-13 21:07:35 +00:00
|
|
|
|
|
|
|
|
|
/* Link cable*/
|
|
|
|
|
Document *master;
|
|
|
|
|
Document *slave;
|
|
|
|
|
signed linkOffset;
|
|
|
|
|
bool linkCableBit;
|
2022-05-20 23:17:59 +00:00
|
|
|
|
|
|
|
|
|
NSSavePanel *_audioSavePanel;
|
|
|
|
|
bool _isRecordingAudio;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-10 12:28:50 +00:00
|
|
|
|
@property GBAudioClient *audioClient;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
- (void) vblank;
|
2016-06-18 17:29:11 +00:00
|
|
|
|
- (void) log: (const char *) log withAttributes: (GB_log_attributes) attributes;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
- (char *) getDebuggerInput;
|
|
|
|
|
- (char *) getAsyncDebuggerInput;
|
2016-10-02 21:26:12 +00:00
|
|
|
|
- (void) cameraRequestUpdate;
|
|
|
|
|
- (uint8_t) cameraGetPixelAtX:(uint8_t)x andY:(uint8_t)y;
|
2017-01-13 20:26:44 +00:00
|
|
|
|
- (void) printImage:(uint32_t *)image height:(unsigned) height
|
|
|
|
|
topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin
|
|
|
|
|
exposure:(unsigned) exposure;
|
2019-06-15 20:22:27 +00:00
|
|
|
|
- (void) gotNewSample:(GB_sample_t *)sample;
|
2019-10-19 16:26:04 +00:00
|
|
|
|
- (void) rumbleChanged:(double)amp;
|
2020-01-29 18:29:30 +00:00
|
|
|
|
- (void) loadBootROM:(GB_boot_rom_t)type;
|
2020-11-13 21:07:35 +00:00
|
|
|
|
- (void)linkCableBitStart:(bool)bit;
|
|
|
|
|
- (bool)linkCableBitEnd;
|
|
|
|
|
- (void)infraredStateChanged:(bool)state;
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
@end
|
|
|
|
|
|
2020-01-29 18:29:30 +00:00
|
|
|
|
static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
|
|
|
|
|
{
|
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
|
|
|
|
[self loadBootROM: type];
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 22:59:51 +00:00
|
|
|
|
static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2017-04-17 17:16:17 +00:00
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[self vblank];
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
|
static void consoleLog(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes)
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2017-04-17 17:16:17 +00:00
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[self log:string withAttributes: attributes];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *consoleInput(GB_gameboy_t *gb)
|
|
|
|
|
{
|
2017-04-17 17:16:17 +00:00
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
2017-04-19 20:26:39 +00:00
|
|
|
|
return [self getDebuggerInput];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-07-17 21:39:43 +00:00
|
|
|
|
static char *asyncConsoleInput(GB_gameboy_t *gb)
|
|
|
|
|
{
|
2017-04-17 17:16:17 +00:00
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
2017-04-19 20:26:39 +00:00
|
|
|
|
char *ret = [self getAsyncDebuggerInput];
|
|
|
|
|
return ret;
|
2016-07-17 21:39:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
|
static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2017-01-13 20:26:44 +00:00
|
|
|
|
return (r << 0) | (g << 8) | (b << 16) | 0xFF000000;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-02 21:26:12 +00:00
|
|
|
|
static void cameraRequestUpdate(GB_gameboy_t *gb)
|
|
|
|
|
{
|
2017-04-17 17:16:17 +00:00
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
2016-10-02 21:26:12 +00:00
|
|
|
|
[self cameraRequestUpdate];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
|
|
|
|
|
{
|
2017-04-17 17:16:17 +00:00
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
2016-10-02 21:26:12 +00:00
|
|
|
|
return [self cameraGetPixelAtX:x andY:y];
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-13 20:26:44 +00:00
|
|
|
|
static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
|
|
|
|
|
uint8_t top_margin, uint8_t bottom_margin, uint8_t exposure)
|
|
|
|
|
{
|
2017-04-17 17:16:17 +00:00
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
2017-01-13 20:26:44 +00:00
|
|
|
|
[self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure];
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-19 16:31:24 +00:00
|
|
|
|
static void setWorkboyTime(GB_gameboy_t *gb, time_t t)
|
|
|
|
|
{
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] setInteger:time(NULL) - t forKey:@"GBWorkboyTimeOffset"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static time_t getWorkboyTime(GB_gameboy_t *gb)
|
|
|
|
|
{
|
|
|
|
|
return time(NULL) - [[NSUserDefaults standardUserDefaults] integerForKey:@"GBWorkboyTimeOffset"];
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-15 20:22:27 +00:00
|
|
|
|
static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
|
|
|
|
|
{
|
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
|
|
|
|
[self gotNewSample:sample];
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-19 16:26:04 +00:00
|
|
|
|
static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|
|
|
|
{
|
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
|
|
|
|
[self rumbleChanged:amp];
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-13 21:07:35 +00:00
|
|
|
|
|
|
|
|
|
static void linkCableBitStart(GB_gameboy_t *gb, bool bit_to_send)
|
|
|
|
|
{
|
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
|
|
|
|
[self linkCableBitStart:bit_to_send];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool linkCableBitEnd(GB_gameboy_t *gb)
|
|
|
|
|
{
|
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
|
|
|
|
return [self linkCableBitEnd];
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-20 22:52:54 +00:00
|
|
|
|
static void infraredStateChanged(GB_gameboy_t *gb, bool on)
|
2020-11-13 21:07:35 +00:00
|
|
|
|
{
|
|
|
|
|
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
|
|
|
|
[self infraredStateChanged:on];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
@implementation Document
|
|
|
|
|
{
|
|
|
|
|
GB_gameboy_t gb;
|
|
|
|
|
volatile bool running;
|
|
|
|
|
volatile bool stopping;
|
|
|
|
|
NSConditionLock *has_debugger_input;
|
|
|
|
|
NSMutableArray *debugger_input_queue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 17:37:57 +00:00
|
|
|
|
- (instancetype)init
|
|
|
|
|
{
|
2016-03-30 20:07:55 +00:00
|
|
|
|
self = [super init];
|
|
|
|
|
if (self) {
|
|
|
|
|
has_debugger_input = [[NSConditionLock alloc] initWithCondition:0];
|
|
|
|
|
debugger_input_queue = [[NSMutableArray alloc] init];
|
2018-06-21 18:23:57 +00:00
|
|
|
|
console_output_lock = [[NSRecursiveLock alloc] init];
|
2019-06-15 20:22:27 +00:00
|
|
|
|
audioLock = [[NSCondition alloc] init];
|
2021-12-29 15:21:06 +00:00
|
|
|
|
_volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-20 21:48:02 +00:00
|
|
|
|
- (NSString *)bootROMPathForName:(NSString *)name
|
|
|
|
|
{
|
|
|
|
|
NSURL *url = [[NSUserDefaults standardUserDefaults] URLForKey:@"GBBootROMsFolder"];
|
|
|
|
|
if (url) {
|
|
|
|
|
NSString *path = [url path];
|
|
|
|
|
path = [path stringByAppendingPathComponent:name];
|
|
|
|
|
path = [path stringByAppendingPathExtension:@"bin"];
|
|
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [[NSBundle mainBundle] pathForResource:name ofType:@"bin"];
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-01 15:16:50 +00:00
|
|
|
|
- (GB_model_t)internalModel
|
|
|
|
|
{
|
|
|
|
|
switch (current_model) {
|
|
|
|
|
case MODEL_DMG:
|
|
|
|
|
return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBDMGModel"];
|
|
|
|
|
|
|
|
|
|
case MODEL_NONE:
|
|
|
|
|
case MODEL_CGB:
|
|
|
|
|
return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"];
|
|
|
|
|
|
2021-02-25 20:42:02 +00:00
|
|
|
|
case MODEL_SGB: {
|
|
|
|
|
GB_model_t model = (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBSGBModel"];
|
|
|
|
|
if (model == (GB_MODEL_SGB | GB_MODEL_PAL_BIT_OLD)) {
|
|
|
|
|
model = GB_MODEL_SGB_PAL;
|
|
|
|
|
}
|
|
|
|
|
return model;
|
|
|
|
|
}
|
2018-12-01 15:16:50 +00:00
|
|
|
|
|
2021-10-23 20:28:54 +00:00
|
|
|
|
case MODEL_MGB:
|
|
|
|
|
return GB_MODEL_MGB;
|
|
|
|
|
|
2018-12-01 15:16:50 +00:00
|
|
|
|
case MODEL_AGB:
|
|
|
|
|
return GB_MODEL_AGB;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-29 12:19:11 +00:00
|
|
|
|
- (void) updatePalette
|
|
|
|
|
{
|
2021-11-05 17:07:27 +00:00
|
|
|
|
GB_set_palette(&gb, [GBPaletteEditorController userPalette]);
|
2020-01-29 12:19:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-08 11:28:46 +00:00
|
|
|
|
- (void) updateBorderMode
|
|
|
|
|
{
|
|
|
|
|
borderModeChanged = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-29 13:50:31 +00:00
|
|
|
|
- (void) updateRumbleMode
|
|
|
|
|
{
|
|
|
|
|
GB_set_rumble_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-02 21:26:12 +00:00
|
|
|
|
- (void) initCommon
|
|
|
|
|
{
|
2018-12-01 15:16:50 +00:00
|
|
|
|
GB_init(&gb, [self internalModel]);
|
2017-04-17 17:16:17 +00:00
|
|
|
|
GB_set_user_data(&gb, (__bridge void *)(self));
|
2020-01-29 18:29:30 +00:00
|
|
|
|
GB_set_boot_rom_load_callback(&gb, (GB_boot_rom_load_callback_t)boot_rom_load);
|
2016-06-18 17:29:11 +00:00
|
|
|
|
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
|
|
|
|
|
GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog);
|
|
|
|
|
GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
|
2016-07-17 21:39:43 +00:00
|
|
|
|
GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput);
|
2017-10-12 14:22:22 +00:00
|
|
|
|
GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]);
|
2020-12-26 21:33:01 +00:00
|
|
|
|
GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]);
|
2020-12-30 22:06:36 +00:00
|
|
|
|
GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]);
|
2020-02-08 11:28:46 +00:00
|
|
|
|
GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]);
|
2020-01-29 12:19:11 +00:00
|
|
|
|
[self updatePalette];
|
2016-06-18 17:29:11 +00:00
|
|
|
|
GB_set_rgb_encode_callback(&gb, rgbEncode);
|
2016-10-02 21:26:12 +00:00
|
|
|
|
GB_set_camera_get_pixel_callback(&gb, cameraGetPixel);
|
|
|
|
|
GB_set_camera_update_request_callback(&gb, cameraRequestUpdate);
|
2017-08-15 18:59:53 +00:00
|
|
|
|
GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]);
|
2018-02-10 12:42:14 +00:00
|
|
|
|
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
|
2021-02-25 20:12:14 +00:00
|
|
|
|
GB_set_rtc_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]);
|
2019-06-15 20:22:27 +00:00
|
|
|
|
GB_apu_set_sample_callback(&gb, audioCallback);
|
2019-10-19 16:26:04 +00:00
|
|
|
|
GB_set_rumble_callback(&gb, rumbleCallback);
|
2020-11-13 21:07:35 +00:00
|
|
|
|
GB_set_infrared_callback(&gb, infraredStateChanged);
|
2020-04-29 13:50:31 +00:00
|
|
|
|
[self updateRumbleMode];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-08 12:38:04 +00:00
|
|
|
|
- (void) updateMinSize
|
|
|
|
|
{
|
|
|
|
|
self.mainWindow.contentMinSize = NSMakeSize(GB_get_screen_width(&gb), GB_get_screen_height(&gb));
|
|
|
|
|
if (self.mainWindow.contentView.bounds.size.width < GB_get_screen_width(&gb) ||
|
|
|
|
|
self.mainWindow.contentView.bounds.size.width < GB_get_screen_height(&gb)) {
|
|
|
|
|
[self.mainWindow zoom:nil];
|
|
|
|
|
}
|
2021-05-30 17:55:04 +00:00
|
|
|
|
self.osdView.usesSGBScale = GB_get_screen_width(&gb) == 256;
|
2020-02-08 12:38:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
- (void) vblank
|
|
|
|
|
{
|
2021-04-10 13:10:10 +00:00
|
|
|
|
if (_gbsVisualizer) {
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[_gbsVisualizer setNeedsDisplay:true];
|
2021-04-10 13:10:10 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[self.view flip];
|
2020-02-08 11:28:46 +00:00
|
|
|
|
if (borderModeChanged) {
|
|
|
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
|
|
|
size_t previous_width = GB_get_screen_width(&gb);
|
|
|
|
|
GB_set_border_mode(&gb, (GB_border_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBBorderMode"]);
|
|
|
|
|
if (GB_get_screen_width(&gb) != previous_width) {
|
|
|
|
|
[self.view screenSizeChanged];
|
2020-02-08 12:38:04 +00:00
|
|
|
|
[self updateMinSize];
|
2020-02-08 11:28:46 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
borderModeChanged = false;
|
|
|
|
|
}
|
2016-06-18 17:29:11 +00:00
|
|
|
|
GB_set_pixels_output(&gb, self.view.pixels);
|
2017-09-27 19:09:26 +00:00
|
|
|
|
if (self.vramWindow.isVisible) {
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2021-01-13 19:52:18 +00:00
|
|
|
|
self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0;
|
2017-09-27 19:09:26 +00:00
|
|
|
|
[self reloadVRAMData: nil];
|
|
|
|
|
});
|
|
|
|
|
}
|
2018-02-10 12:42:14 +00:00
|
|
|
|
if (self.view.isRewinding) {
|
|
|
|
|
rewind = true;
|
2021-05-30 17:55:04 +00:00
|
|
|
|
[self.osdView displayText:@"Rewinding..."];
|
2018-02-10 12:42:14 +00:00
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-15 20:22:27 +00:00
|
|
|
|
- (void)gotNewSample:(GB_sample_t *)sample
|
|
|
|
|
{
|
2021-04-10 13:10:10 +00:00
|
|
|
|
if (_gbsVisualizer) {
|
|
|
|
|
[_gbsVisualizer addSample:sample];
|
|
|
|
|
}
|
2019-06-15 20:22:27 +00:00
|
|
|
|
[audioLock lock];
|
2021-12-29 15:21:06 +00:00
|
|
|
|
if (_audioClient.isPlaying) {
|
2019-06-15 20:22:27 +00:00
|
|
|
|
if (audioBufferPosition == audioBufferSize) {
|
|
|
|
|
if (audioBufferSize >= 0x4000) {
|
|
|
|
|
audioBufferPosition = 0;
|
|
|
|
|
[audioLock unlock];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (audioBufferSize == 0) {
|
|
|
|
|
audioBufferSize = 512;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
audioBufferSize += audioBufferSize >> 2;
|
|
|
|
|
}
|
|
|
|
|
audioBuffer = realloc(audioBuffer, sizeof(*sample) * audioBufferSize);
|
|
|
|
|
}
|
2021-12-29 15:21:06 +00:00
|
|
|
|
if (_volume != 1) {
|
|
|
|
|
sample->left *= _volume;
|
|
|
|
|
sample->right *= _volume;
|
2021-05-21 15:12:29 +00:00
|
|
|
|
}
|
2019-06-15 20:22:27 +00:00
|
|
|
|
audioBuffer[audioBufferPosition++] = *sample;
|
|
|
|
|
}
|
|
|
|
|
if (audioBufferPosition == audioBufferNeeded) {
|
|
|
|
|
[audioLock signal];
|
|
|
|
|
audioBufferNeeded = 0;
|
|
|
|
|
}
|
|
|
|
|
[audioLock unlock];
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-19 16:26:04 +00:00
|
|
|
|
- (void)rumbleChanged:(double)amp
|
|
|
|
|
{
|
|
|
|
|
[_view setRumble:amp];
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-13 21:07:35 +00:00
|
|
|
|
- (void) preRun
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2018-11-14 22:21:21 +00:00
|
|
|
|
GB_set_pixels_output(&gb, self.view.pixels);
|
2016-06-18 17:29:11 +00:00
|
|
|
|
GB_set_sample_rate(&gb, 96000);
|
2021-12-29 15:21:06 +00:00
|
|
|
|
_audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
|
2019-06-15 20:22:27 +00:00
|
|
|
|
[audioLock lock];
|
|
|
|
|
|
|
|
|
|
if (audioBufferPosition < nFrames) {
|
|
|
|
|
audioBufferNeeded = nFrames;
|
2021-12-29 15:03:44 +00:00
|
|
|
|
[audioLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.125]];
|
2019-06-15 20:22:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-01 21:11:42 +00:00
|
|
|
|
if (stopping || GB_debugger_is_stopped(&gb)) {
|
2020-02-28 12:05:29 +00:00
|
|
|
|
memset(buffer, 0, nFrames * sizeof(*buffer));
|
|
|
|
|
[audioLock unlock];
|
2020-03-06 15:37:04 +00:00
|
|
|
|
return;
|
2020-02-28 12:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 14:53:28 +00:00
|
|
|
|
if (audioBufferPosition < nFrames) {
|
|
|
|
|
// Not enough audio
|
|
|
|
|
memset(buffer, 0, (nFrames - audioBufferPosition) * sizeof(*buffer));
|
|
|
|
|
memcpy(buffer, audioBuffer, audioBufferPosition * sizeof(*buffer));
|
|
|
|
|
audioBufferPosition = 0;
|
|
|
|
|
}
|
|
|
|
|
else if (audioBufferPosition < nFrames + 4800) {
|
2019-06-15 20:22:27 +00:00
|
|
|
|
memcpy(buffer, audioBuffer, nFrames * sizeof(*buffer));
|
|
|
|
|
memmove(audioBuffer, audioBuffer + nFrames, (audioBufferPosition - nFrames) * sizeof(*buffer));
|
|
|
|
|
audioBufferPosition = audioBufferPosition - nFrames;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
memcpy(buffer, audioBuffer + (audioBufferPosition - nFrames), nFrames * sizeof(*buffer));
|
|
|
|
|
audioBufferPosition = 0;
|
|
|
|
|
}
|
|
|
|
|
[audioLock unlock];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
} andSampleRate:96000];
|
2016-08-21 18:58:33 +00:00
|
|
|
|
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) {
|
2021-12-29 15:21:06 +00:00
|
|
|
|
[_audioClient start];
|
2016-08-21 18:58:33 +00:00
|
|
|
|
}
|
2021-10-23 10:36:58 +00:00
|
|
|
|
hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:true];
|
2016-08-12 19:49:17 +00:00
|
|
|
|
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
|
2020-05-23 11:50:54 +00:00
|
|
|
|
|
2020-05-30 19:46:06 +00:00
|
|
|
|
/* Clear pending alarms, don't play alarms while playing */
|
|
|
|
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
|
|
|
|
|
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
|
|
|
|
for (NSUserNotification *notification in [center scheduledNotifications]) {
|
2021-01-13 19:52:18 +00:00
|
|
|
|
if ([notification.identifier isEqualToString:self.fileURL.path]) {
|
2020-05-30 19:46:06 +00:00
|
|
|
|
[center removeScheduledNotification:notification];
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-05-23 11:50:54 +00:00
|
|
|
|
}
|
2020-05-30 19:46:06 +00:00
|
|
|
|
|
|
|
|
|
for (NSUserNotification *notification in [center deliveredNotifications]) {
|
2021-01-13 19:52:18 +00:00
|
|
|
|
if ([notification.identifier isEqualToString:self.fileURL.path]) {
|
2020-05-30 19:46:06 +00:00
|
|
|
|
[center removeDeliveredNotification:notification];
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-05-23 11:50:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-13 21:07:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|
|
|
|
{
|
|
|
|
|
unsigned *ret = malloc(sizeof(*ret) * 0x100);
|
|
|
|
|
for (unsigned i = 0; i < 0x100; i++) {
|
|
|
|
|
ret[i] = i * frequency;
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) run
|
|
|
|
|
{
|
|
|
|
|
assert(!master);
|
|
|
|
|
[self preRun];
|
|
|
|
|
if (slave) {
|
|
|
|
|
[slave preRun];
|
|
|
|
|
unsigned *masterTable = multiplication_table_for_frequency(GB_get_clock_rate(&gb));
|
|
|
|
|
unsigned *slaveTable = multiplication_table_for_frequency(GB_get_clock_rate(&slave->gb));
|
|
|
|
|
while (running) {
|
|
|
|
|
if (linkOffset <= 0) {
|
|
|
|
|
linkOffset += slaveTable[GB_run(&gb)];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
linkOffset -= masterTable[GB_run(&slave->gb)];
|
2018-02-10 12:42:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-13 21:07:35 +00:00
|
|
|
|
free(masterTable);
|
|
|
|
|
free(slaveTable);
|
|
|
|
|
[slave postRun];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
while (running) {
|
|
|
|
|
if (rewind) {
|
|
|
|
|
rewind = false;
|
|
|
|
|
GB_rewind_pop(&gb);
|
|
|
|
|
if (!GB_rewind_pop(&gb)) {
|
|
|
|
|
rewind = self.view.isRewinding;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
GB_run(&gb);
|
|
|
|
|
}
|
2018-02-10 12:42:14 +00:00
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2020-11-13 21:07:35 +00:00
|
|
|
|
[self postRun];
|
|
|
|
|
stopping = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)postRun
|
|
|
|
|
{
|
2016-08-12 19:49:17 +00:00
|
|
|
|
[hex_timer invalidate];
|
2019-06-15 20:22:27 +00:00
|
|
|
|
[audioLock lock];
|
|
|
|
|
memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer));
|
|
|
|
|
audioBufferPosition = audioBufferNeeded;
|
|
|
|
|
[audioLock signal];
|
|
|
|
|
[audioLock unlock];
|
2021-12-29 15:21:06 +00:00
|
|
|
|
[_audioClient stop];
|
|
|
|
|
_audioClient = nil;
|
2021-10-23 10:36:58 +00:00
|
|
|
|
self.view.mouseHidingEnabled = false;
|
2022-02-25 23:47:47 +00:00
|
|
|
|
GB_save_battery(&gb, self.savPath.UTF8String);
|
|
|
|
|
GB_save_cheats(&gb, self.chtPath.UTF8String);
|
2020-05-23 11:50:54 +00:00
|
|
|
|
unsigned time_to_alarm = GB_time_to_alarm(&gb);
|
|
|
|
|
|
|
|
|
|
if (time_to_alarm) {
|
2020-08-21 21:56:12 +00:00
|
|
|
|
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = (id)[NSApp delegate];
|
2020-05-23 11:50:54 +00:00
|
|
|
|
NSUserNotification *notification = [[NSUserNotification alloc] init];
|
2021-01-13 19:52:18 +00:00
|
|
|
|
NSString *friendlyName = [[self.fileURL lastPathComponent] stringByDeletingPathExtension];
|
2020-05-23 11:50:54 +00:00
|
|
|
|
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil];
|
|
|
|
|
friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""];
|
|
|
|
|
friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
2020-11-13 21:07:35 +00:00
|
|
|
|
|
2020-05-23 11:50:54 +00:00
|
|
|
|
notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName];
|
|
|
|
|
notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName];
|
2021-01-13 19:52:18 +00:00
|
|
|
|
notification.identifier = self.fileURL.path;
|
2020-05-23 11:50:54 +00:00
|
|
|
|
notification.deliveryDate = [NSDate dateWithTimeIntervalSinceNow:time_to_alarm];
|
|
|
|
|
notification.soundName = NSUserNotificationDefaultSoundName;
|
2020-05-30 19:46:06 +00:00
|
|
|
|
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification];
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"];
|
2020-05-23 11:50:54 +00:00
|
|
|
|
}
|
2020-05-01 15:16:33 +00:00
|
|
|
|
[_view setRumble:0];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) start
|
|
|
|
|
{
|
2021-04-02 22:29:43 +00:00
|
|
|
|
self.gbsPlayPauseButton.state = true;
|
2021-01-13 19:52:18 +00:00
|
|
|
|
self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSWindowStyleMaskFullScreen) != 0;
|
2020-11-13 21:07:35 +00:00
|
|
|
|
if (master) {
|
|
|
|
|
[master start];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (running) return;
|
2022-02-25 12:29:21 +00:00
|
|
|
|
running = true;
|
2016-06-18 14:20:40 +00:00
|
|
|
|
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) stop
|
|
|
|
|
{
|
2021-04-02 22:29:43 +00:00
|
|
|
|
self.gbsPlayPauseButton.state = false;
|
2020-11-13 21:07:35 +00:00
|
|
|
|
if (master) {
|
|
|
|
|
if (!master->running) return;
|
|
|
|
|
GB_debugger_set_disabled(&gb, true);
|
|
|
|
|
if (GB_debugger_is_stopped(&gb)) {
|
|
|
|
|
[self interruptDebugInputRead];
|
|
|
|
|
}
|
|
|
|
|
[master stop];
|
|
|
|
|
GB_debugger_set_disabled(&gb, false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
if (!running) return;
|
2017-04-17 17:16:17 +00:00
|
|
|
|
GB_debugger_set_disabled(&gb, true);
|
|
|
|
|
if (GB_debugger_is_stopped(&gb)) {
|
2017-04-19 20:26:39 +00:00
|
|
|
|
[self interruptDebugInputRead];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2020-02-15 17:23:04 +00:00
|
|
|
|
[audioLock lock];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
stopping = true;
|
2020-02-28 12:05:29 +00:00
|
|
|
|
[audioLock signal];
|
2020-02-15 17:23:04 +00:00
|
|
|
|
[audioLock unlock];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
running = false;
|
2020-03-06 15:37:04 +00:00
|
|
|
|
while (stopping) {
|
|
|
|
|
[audioLock lock];
|
|
|
|
|
[audioLock signal];
|
|
|
|
|
[audioLock unlock];
|
|
|
|
|
}
|
2017-04-17 17:16:17 +00:00
|
|
|
|
GB_debugger_set_disabled(&gb, false);
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-29 18:29:30 +00:00
|
|
|
|
- (void) loadBootROM: (GB_boot_rom_t)type
|
2018-11-23 17:59:15 +00:00
|
|
|
|
{
|
2020-01-29 18:29:30 +00:00
|
|
|
|
static NSString *const names[] = {
|
2021-11-03 22:32:15 +00:00
|
|
|
|
[GB_BOOT_ROM_DMG_0] = @"dmg0_boot",
|
2020-01-29 18:29:30 +00:00
|
|
|
|
[GB_BOOT_ROM_DMG] = @"dmg_boot",
|
|
|
|
|
[GB_BOOT_ROM_MGB] = @"mgb_boot",
|
|
|
|
|
[GB_BOOT_ROM_SGB] = @"sgb_boot",
|
|
|
|
|
[GB_BOOT_ROM_SGB2] = @"sgb2_boot",
|
2021-11-03 22:32:15 +00:00
|
|
|
|
[GB_BOOT_ROM_CGB_0] = @"cgb0_boot",
|
2020-01-29 18:29:30 +00:00
|
|
|
|
[GB_BOOT_ROM_CGB] = @"cgb_boot",
|
|
|
|
|
[GB_BOOT_ROM_AGB] = @"agb_boot",
|
|
|
|
|
};
|
|
|
|
|
GB_load_boot_rom(&gb, [[self bootROMPathForName:names[type]] UTF8String]);
|
2018-11-23 17:59:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
- (IBAction)reset:(id)sender
|
|
|
|
|
{
|
|
|
|
|
[self stop];
|
2018-11-23 17:59:15 +00:00
|
|
|
|
size_t old_width = GB_get_screen_width(&gb);
|
|
|
|
|
|
2018-06-20 21:48:02 +00:00
|
|
|
|
if ([sender tag] != MODEL_NONE) {
|
|
|
|
|
current_model = (enum model)[sender tag];
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-16 23:06:13 +00:00
|
|
|
|
GB_switch_model_and_reset(&gb, [self internalModel]);
|
2018-11-23 17:59:15 +00:00
|
|
|
|
|
|
|
|
|
if (old_width != GB_get_screen_width(&gb)) {
|
|
|
|
|
[self.view screenSizeChanged];
|
|
|
|
|
}
|
2019-01-05 21:58:18 +00:00
|
|
|
|
|
2020-02-08 12:38:04 +00:00
|
|
|
|
[self updateMinSize];
|
2019-01-05 21:58:18 +00:00
|
|
|
|
|
2016-04-08 10:10:01 +00:00
|
|
|
|
if ([sender tag] != 0) {
|
|
|
|
|
/* User explictly selected a model, save the preference */
|
2018-01-13 11:16:33 +00:00
|
|
|
|
[[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_DMG forKey:@"EmulateDMG"];
|
2018-11-10 23:16:32 +00:00
|
|
|
|
[[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_SGB forKey:@"EmulateSGB"];
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_AGB forKey:@"EmulateAGB"];
|
2021-10-23 20:28:54 +00:00
|
|
|
|
[[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_MGB forKey:@"EmulateMGB"];
|
2016-04-08 10:10:01 +00:00
|
|
|
|
}
|
2017-04-17 17:16:17 +00:00
|
|
|
|
|
|
|
|
|
/* Reload the ROM, SAV and SYM files */
|
|
|
|
|
[self loadROM];
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[self start];
|
2016-08-19 11:54:54 +00:00
|
|
|
|
|
|
|
|
|
if (hex_controller) {
|
|
|
|
|
/* Verify bank sanity, especially when switching models. */
|
|
|
|
|
[(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:0];
|
2018-06-16 11:46:16 +00:00
|
|
|
|
[self hexUpdateBank:self.memoryBankInput ignoreErrors:true];
|
2016-08-19 11:54:54 +00:00
|
|
|
|
}
|
2021-05-30 17:55:04 +00:00
|
|
|
|
|
|
|
|
|
char title[17];
|
|
|
|
|
GB_get_rom_title(&gb, title);
|
|
|
|
|
[self.osdView displayText:[NSString stringWithFormat:@"SameBoy v" GB_VERSION "\n%s\n%08X", title, GB_get_rom_crc32(&gb)]];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)togglePause:(id)sender
|
|
|
|
|
{
|
2020-11-13 21:07:35 +00:00
|
|
|
|
if (master) {
|
|
|
|
|
[master togglePause:sender];
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
if (running) {
|
|
|
|
|
[self stop];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
|
{
|
2017-01-14 17:45:07 +00:00
|
|
|
|
[cameraSession stopRunning];
|
2019-07-19 12:50:36 +00:00
|
|
|
|
self.view.gb = NULL;
|
2016-06-18 17:29:11 +00:00
|
|
|
|
GB_free(&gb);
|
2016-10-02 21:26:12 +00:00
|
|
|
|
if (cameraImage) {
|
|
|
|
|
CVBufferRelease(cameraImage);
|
|
|
|
|
}
|
2019-06-15 20:22:27 +00:00
|
|
|
|
if (audioBuffer) {
|
|
|
|
|
free(audioBuffer);
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 17:37:57 +00:00
|
|
|
|
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
|
|
|
|
|
{
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[super windowControllerDidLoadNib:aController];
|
2020-04-11 15:27:31 +00:00
|
|
|
|
// Interface Builder bug?
|
|
|
|
|
[self.consoleWindow setContentSize:self.consoleWindow.minSize];
|
2017-01-24 21:10:50 +00:00
|
|
|
|
/* Close Open Panels, if any */
|
|
|
|
|
for (NSWindow *window in [[NSApplication sharedApplication] windows]) {
|
|
|
|
|
if ([window isKindOfClass:[NSOpenPanel class]]) {
|
|
|
|
|
[(NSOpenPanel *)window cancel:self];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-26 17:16:19 +00:00
|
|
|
|
NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init];
|
|
|
|
|
[paragraph_style setLineSpacing:2];
|
2018-07-11 16:46:27 +00:00
|
|
|
|
|
2017-05-26 17:16:19 +00:00
|
|
|
|
self.debuggerSideViewInput.font = [NSFont userFixedPitchFontOfSize:12];
|
|
|
|
|
self.debuggerSideViewInput.textColor = [NSColor whiteColor];
|
|
|
|
|
self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style;
|
|
|
|
|
[self.debuggerSideViewInput setString:@"registers\nbacktrace\n"];
|
2020-06-04 23:10:05 +00:00
|
|
|
|
((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb;
|
2017-05-26 17:16:19 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateSideView)
|
|
|
|
|
name:NSTextDidChangeNotification
|
|
|
|
|
object:self.debuggerSideViewInput];
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
self.consoleOutput.textContainerInset = NSMakeSize(4, 4);
|
|
|
|
|
[self.view becomeFirstResponder];
|
2020-03-26 18:54:18 +00:00
|
|
|
|
self.view.frameBlendingMode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
|
2016-06-18 14:43:39 +00:00
|
|
|
|
CGRect window_frame = self.mainWindow.frame;
|
|
|
|
|
window_frame.size.width = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowWidth"],
|
|
|
|
|
window_frame.size.width);
|
|
|
|
|
window_frame.size.height = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowHeight"],
|
|
|
|
|
window_frame.size.height);
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[self.mainWindow setFrame:window_frame display:true];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
self.vramStatusLabel.cell.backgroundStyle = NSBackgroundStyleRaised;
|
2021-07-11 20:12:46 +00:00
|
|
|
|
|
|
|
|
|
NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height;
|
|
|
|
|
CGRect vram_window_rect = self.vramWindow.frame;
|
|
|
|
|
vram_window_rect.size.height = 384 + height_diff + 48;
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[self.vramWindow setFrame:vram_window_rect display:true animate:false];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
|
2018-09-15 14:57:59 +00:00
|
|
|
|
|
2021-01-13 19:52:18 +00:00
|
|
|
|
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [self.fileURL.path lastPathComponent]];
|
2020-04-09 12:29:49 +00:00
|
|
|
|
self.debuggerSplitView.dividerColor = [NSColor clearColor];
|
2020-08-22 00:27:57 +00:00
|
|
|
|
if (@available(macOS 11.0, *)) {
|
|
|
|
|
self.memoryWindow.toolbarStyle = NSWindowToolbarStyleExpanded;
|
|
|
|
|
self.printerFeedWindow.toolbarStyle = NSWindowToolbarStyleUnifiedCompact;
|
|
|
|
|
[self.printerFeedWindow.toolbar removeItemAtIndex:1];
|
|
|
|
|
self.printerFeedWindow.toolbar.items.firstObject.image =
|
|
|
|
|
[NSImage imageWithSystemSymbolName:@"square.and.arrow.down"
|
|
|
|
|
accessibilityDescription:@"Save"];
|
|
|
|
|
self.printerFeedWindow.toolbar.items.lastObject.image =
|
|
|
|
|
[NSImage imageWithSystemSymbolName:@"printer"
|
|
|
|
|
accessibilityDescription:@"Print"];
|
|
|
|
|
}
|
2020-06-10 20:28:33 +00:00
|
|
|
|
|
2017-08-15 18:59:53 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateHighpassFilter)
|
|
|
|
|
name:@"GBHighpassFilterChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2017-10-12 14:22:22 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateColorCorrectionMode)
|
|
|
|
|
name:@"GBColorCorrectionChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2020-12-25 12:14:17 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateLightTemperature)
|
|
|
|
|
name:@"GBLightTemperatureChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2020-12-30 22:06:36 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateInterferenceVolume)
|
|
|
|
|
name:@"GBInterferenceVolumeChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2020-03-26 18:54:18 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateFrameBlendingMode)
|
|
|
|
|
name:@"GBFrameBlendingModeChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2020-01-29 12:19:11 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updatePalette)
|
|
|
|
|
name:@"GBColorPaletteChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2020-02-08 11:28:46 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateBorderMode)
|
|
|
|
|
name:@"GBBorderModeChanged"
|
|
|
|
|
object:nil];
|
2020-01-29 12:19:11 +00:00
|
|
|
|
|
2020-04-29 13:50:31 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateRumbleMode)
|
|
|
|
|
name:@"GBRumbleModeChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2018-02-10 12:42:14 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateRewindLength)
|
|
|
|
|
name:@"GBRewindLengthChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2021-02-25 20:12:14 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateRTCMode)
|
|
|
|
|
name:@"GBRTCModeChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
|
|
|
|
|
2018-12-01 15:16:50 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(dmgModelChanged)
|
|
|
|
|
name:@"GBDMGModelChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(sgbModelChanged)
|
|
|
|
|
name:@"GBSGBModelChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(cgbModelChanged)
|
|
|
|
|
name:@"GBCGBModelChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2021-12-29 15:21:06 +00:00
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
selector:@selector(updateVolume)
|
|
|
|
|
name:@"GBVolumeChanged"
|
|
|
|
|
object:nil];
|
|
|
|
|
|
2017-02-24 13:14:00 +00:00
|
|
|
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) {
|
2018-11-23 17:59:15 +00:00
|
|
|
|
current_model = MODEL_DMG;
|
2017-02-24 13:14:00 +00:00
|
|
|
|
}
|
2018-11-10 23:16:32 +00:00
|
|
|
|
else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateSGB"]) {
|
2018-11-23 17:59:15 +00:00
|
|
|
|
current_model = MODEL_SGB;
|
2018-11-10 23:16:32 +00:00
|
|
|
|
}
|
2021-10-23 20:28:54 +00:00
|
|
|
|
else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateMGB"]) {
|
|
|
|
|
current_model = MODEL_MGB;
|
|
|
|
|
}
|
2017-02-24 13:14:00 +00:00
|
|
|
|
else {
|
2018-11-23 17:59:15 +00:00
|
|
|
|
current_model = [[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]? MODEL_AGB : MODEL_CGB;
|
2017-02-24 13:14:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-23 17:59:15 +00:00
|
|
|
|
[self initCommon];
|
|
|
|
|
self.view.gb = &gb;
|
2021-05-30 17:55:04 +00:00
|
|
|
|
self.view.osdView = _osdView;
|
2018-11-23 17:59:15 +00:00
|
|
|
|
[self.view screenSizeChanged];
|
2021-04-19 17:57:28 +00:00
|
|
|
|
if ([self loadROM]) {
|
|
|
|
|
_mainWindow.alphaValue = 0; // Hack hack ugly hack
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
[self close];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[self reset:nil];
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 19:49:17 +00:00
|
|
|
|
- (void) initMemoryView
|
|
|
|
|
{
|
|
|
|
|
hex_controller = [[HFController alloc] init];
|
|
|
|
|
[hex_controller setBytesPerColumn:1];
|
|
|
|
|
[hex_controller setEditMode:HFOverwriteMode];
|
|
|
|
|
|
|
|
|
|
[hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]];
|
|
|
|
|
|
|
|
|
|
/* Here we're going to make three representers - one for the hex, one for the ASCII, and one for the scrollbar. To lay these all out properly, we'll use a fourth HFLayoutRepresenter. */
|
|
|
|
|
HFLayoutRepresenter *layoutRep = [[HFLayoutRepresenter alloc] init];
|
|
|
|
|
HFHexTextRepresenter *hexRep = [[HFHexTextRepresenter alloc] init];
|
|
|
|
|
HFStringEncodingTextRepresenter *asciiRep = [[HFStringEncodingTextRepresenter alloc] init];
|
|
|
|
|
HFVerticalScrollerRepresenter *scrollRep = [[HFVerticalScrollerRepresenter alloc] init];
|
2016-08-19 11:54:54 +00:00
|
|
|
|
lineRep = [[HFLineCountingRepresenter alloc] init];
|
2016-08-12 19:49:17 +00:00
|
|
|
|
HFStatusBarRepresenter *statusRep = [[HFStatusBarRepresenter alloc] init];
|
|
|
|
|
|
|
|
|
|
lineRep.lineNumberFormat = HFLineNumberFormatHexadecimal;
|
|
|
|
|
|
|
|
|
|
/* Add all our reps to the controller. */
|
|
|
|
|
[hex_controller addRepresenter:layoutRep];
|
|
|
|
|
[hex_controller addRepresenter:hexRep];
|
|
|
|
|
[hex_controller addRepresenter:asciiRep];
|
|
|
|
|
[hex_controller addRepresenter:scrollRep];
|
|
|
|
|
[hex_controller addRepresenter:lineRep];
|
|
|
|
|
[hex_controller addRepresenter:statusRep];
|
|
|
|
|
|
|
|
|
|
/* Tell the layout rep which reps it should lay out. */
|
|
|
|
|
[layoutRep addRepresenter:hexRep];
|
|
|
|
|
[layoutRep addRepresenter:scrollRep];
|
|
|
|
|
[layoutRep addRepresenter:asciiRep];
|
|
|
|
|
[layoutRep addRepresenter:lineRep];
|
|
|
|
|
[layoutRep addRepresenter:statusRep];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[(NSView *)[hexRep view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
|
|
|
|
|
|
|
|
|
/* Grab the layout rep's view and stick it into our container. */
|
|
|
|
|
NSView *layoutView = [layoutRep view];
|
|
|
|
|
NSRect layoutViewFrame = self.memoryView.frame;
|
|
|
|
|
[layoutView setFrame:layoutViewFrame];
|
|
|
|
|
[layoutView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin];
|
|
|
|
|
[self.memoryView addSubview:layoutView];
|
2016-08-19 11:54:54 +00:00
|
|
|
|
|
|
|
|
|
self.memoryBankItem.enabled = false;
|
2016-08-12 19:49:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 17:37:57 +00:00
|
|
|
|
+ (BOOL)autosavesInPlace
|
|
|
|
|
{
|
2021-10-23 10:36:58 +00:00
|
|
|
|
return true;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 17:37:57 +00:00
|
|
|
|
- (NSString *)windowNibName
|
|
|
|
|
{
|
2016-03-30 20:07:55 +00:00
|
|
|
|
// Override returning the nib file name of the document
|
|
|
|
|
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
|
|
|
|
|
return @"Document";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type
|
|
|
|
|
{
|
2021-10-23 10:36:58 +00:00
|
|
|
|
return true;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-02 22:29:43 +00:00
|
|
|
|
- (IBAction)changeGBSTrack:(id)sender
|
|
|
|
|
{
|
2021-04-10 13:10:10 +00:00
|
|
|
|
if (!running) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
2021-04-02 22:29:43 +00:00
|
|
|
|
[self performAtomicBlock:^{
|
|
|
|
|
GB_gbs_switch_track(&gb, self.gbsTracks.indexOfSelectedItem);
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
- (IBAction)gbsNextPrevPushed:(id)sender
|
|
|
|
|
{
|
|
|
|
|
if (self.gbsNextPrevButton.selectedSegment == 0) {
|
|
|
|
|
// Previous
|
|
|
|
|
if (self.gbsTracks.indexOfSelectedItem == 0) {
|
|
|
|
|
[self.gbsTracks selectItemAtIndex:self.gbsTracks.numberOfItems - 1];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[self.gbsTracks selectItemAtIndex:self.gbsTracks.indexOfSelectedItem - 1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Next
|
|
|
|
|
if (self.gbsTracks.indexOfSelectedItem == self.gbsTracks.numberOfItems - 1) {
|
|
|
|
|
[self.gbsTracks selectItemAtIndex: 0];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[self.gbsTracks selectItemAtIndex:self.gbsTracks.indexOfSelectedItem + 1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[self changeGBSTrack:sender];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)prepareGBSInterface: (GB_gbs_info_t *)info
|
|
|
|
|
{
|
|
|
|
|
GB_set_rendering_disabled(&gb, true);
|
|
|
|
|
_view = nil;
|
2021-08-01 12:11:33 +00:00
|
|
|
|
for (NSView *view in [_mainWindow.contentView.subviews copy]) {
|
2021-04-02 22:29:43 +00:00
|
|
|
|
[view removeFromSuperview];
|
|
|
|
|
}
|
2022-07-01 19:46:03 +00:00
|
|
|
|
if (@available(macOS 11, *)) {
|
|
|
|
|
[[NSBundle mainBundle] loadNibNamed:@"GBS11" owner:self topLevelObjects:nil];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil];
|
|
|
|
|
}
|
2021-04-02 22:29:43 +00:00
|
|
|
|
[_mainWindow setContentSize:self.gbsPlayerView.bounds.size];
|
|
|
|
|
_mainWindow.styleMask &= ~NSWindowStyleMaskResizable;
|
2021-04-05 20:08:43 +00:00
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{ // Cocoa is weird, no clue why it's needed
|
|
|
|
|
[_mainWindow standardWindowButton:NSWindowZoomButton].enabled = false;
|
|
|
|
|
});
|
2021-04-02 22:29:43 +00:00
|
|
|
|
[_mainWindow.contentView addSubview:self.gbsPlayerView];
|
2021-04-10 13:10:10 +00:00
|
|
|
|
_mainWindow.movableByWindowBackground = true;
|
|
|
|
|
[_mainWindow setContentBorderThickness:24 forEdge:NSRectEdgeMinY];
|
|
|
|
|
|
2021-04-02 22:29:43 +00:00
|
|
|
|
self.gbsTitle.stringValue = [NSString stringWithCString:info->title encoding:NSISOLatin1StringEncoding] ?: @"GBS Player";
|
|
|
|
|
self.gbsAuthor.stringValue = [NSString stringWithCString:info->author encoding:NSISOLatin1StringEncoding] ?: @"Unknown Composer";
|
|
|
|
|
NSString *copyright = [NSString stringWithCString:info->copyright encoding:NSISOLatin1StringEncoding];
|
|
|
|
|
if (copyright) {
|
|
|
|
|
copyright = [@"©" stringByAppendingString:copyright];
|
|
|
|
|
}
|
|
|
|
|
self.gbsCopyright.stringValue = copyright ?: @"Missing copyright information";
|
|
|
|
|
for (unsigned i = 0; i < info->track_count; i++) {
|
|
|
|
|
[self.gbsTracks addItemWithTitle:[NSString stringWithFormat:@"Track %u", i + 1]];
|
|
|
|
|
}
|
|
|
|
|
[self.gbsTracks selectItemAtIndex:info->first_track];
|
|
|
|
|
self.gbsPlayPauseButton.image.template = true;
|
|
|
|
|
self.gbsPlayPauseButton.alternateImage.template = true;
|
|
|
|
|
self.gbsRewindButton.image.template = true;
|
|
|
|
|
for (unsigned i = 0; i < 2; i++) {
|
|
|
|
|
[self.gbsNextPrevButton imageForSegment:i].template = true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 15:21:06 +00:00
|
|
|
|
if (!_audioClient.isPlaying) {
|
|
|
|
|
[_audioClient start];
|
2021-04-02 22:29:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (@available(macOS 10.10, *)) {
|
|
|
|
|
_mainWindow.titlebarAppearsTransparent = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-25 23:47:47 +00:00
|
|
|
|
- (bool)isCartContainer
|
|
|
|
|
{
|
|
|
|
|
return [self.fileName.pathExtension.lowercaseString isEqualToString:@"gbcart"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)savPath
|
|
|
|
|
{
|
|
|
|
|
if (self.isCartContainer) {
|
|
|
|
|
return [self.fileName stringByAppendingPathComponent:@"battery.sav"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)chtPath
|
|
|
|
|
{
|
|
|
|
|
if (self.isCartContainer) {
|
|
|
|
|
return [self.fileName stringByAppendingPathComponent:@"cheats.cht"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)saveStatePath:(unsigned)index
|
|
|
|
|
{
|
|
|
|
|
if (self.isCartContainer) {
|
|
|
|
|
return [self.fileName stringByAppendingPathComponent:[NSString stringWithFormat:@"state.s%u", index]];
|
|
|
|
|
}
|
|
|
|
|
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%u", index]].path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)romPath
|
|
|
|
|
{
|
|
|
|
|
NSString *fileName = self.fileName;
|
|
|
|
|
if (self.isCartContainer) {
|
|
|
|
|
NSArray *paths = [[NSString stringWithContentsOfFile:[fileName stringByAppendingPathComponent:@"rom.gbl"]
|
|
|
|
|
encoding:NSUTF8StringEncoding
|
|
|
|
|
error:nil] componentsSeparatedByString:@"\n"];
|
|
|
|
|
fileName = nil;
|
|
|
|
|
bool needsRebuild = false;
|
|
|
|
|
for (NSString *path in paths) {
|
|
|
|
|
NSURL *url = [NSURL URLWithString:path relativeToURL:self.fileURL];
|
|
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
|
|
|
|
|
if (fileName && ![fileName isEqualToString:url.path]) {
|
|
|
|
|
needsRebuild = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
fileName = url.path;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
needsRebuild = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (fileName && needsRebuild) {
|
|
|
|
|
[[NSString stringWithFormat:@"%@\n%@\n%@",
|
|
|
|
|
[fileName pathRelativeToDirectory:self.fileName],
|
|
|
|
|
fileName,
|
|
|
|
|
[[NSURL fileURLWithPath:fileName].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]]
|
|
|
|
|
writeToFile:[self.fileName stringByAppendingPathComponent:@"rom.gbl"]
|
|
|
|
|
atomically:false
|
|
|
|
|
encoding:NSUTF8StringEncoding
|
|
|
|
|
error:nil];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fileName;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 17:57:28 +00:00
|
|
|
|
- (int) loadROM
|
2017-02-24 13:14:00 +00:00
|
|
|
|
{
|
2021-04-19 17:57:28 +00:00
|
|
|
|
__block int ret = 0;
|
2022-02-25 23:47:47 +00:00
|
|
|
|
NSString *fileName = self.romPath;
|
|
|
|
|
if (!fileName) {
|
|
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
|
|
|
[alert setMessageText:@"Could not locate the ROM referenced by this Game Boy Cartridge"];
|
|
|
|
|
[alert setAlertStyle:NSAlertStyleCritical];
|
|
|
|
|
[alert runModal];
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-17 17:16:17 +00:00
|
|
|
|
NSString *rom_warnings = [self captureOutputForBlock:^{
|
2018-04-02 16:57:39 +00:00
|
|
|
|
GB_debugger_clear_symbols(&gb);
|
2022-02-25 23:47:47 +00:00
|
|
|
|
if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"isx"]) {
|
|
|
|
|
ret = GB_load_isx(&gb, fileName.UTF8String);
|
|
|
|
|
if (!self.isCartContainer) {
|
|
|
|
|
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String);
|
|
|
|
|
}
|
2021-04-02 22:29:43 +00:00
|
|
|
|
}
|
2022-02-25 23:47:47 +00:00
|
|
|
|
else if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"gbs"]) {
|
2021-04-05 20:08:43 +00:00
|
|
|
|
__block GB_gbs_info_t info;
|
2022-02-25 23:47:47 +00:00
|
|
|
|
ret = GB_load_gbs(&gb, fileName.UTF8String, &info);
|
2021-04-02 22:29:43 +00:00
|
|
|
|
[self prepareGBSInterface:&info];
|
2020-04-25 19:48:48 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
2022-02-25 23:47:47 +00:00
|
|
|
|
ret = GB_load_rom(&gb, [fileName UTF8String]);
|
2020-04-25 19:48:48 +00:00
|
|
|
|
}
|
2022-02-25 23:47:47 +00:00
|
|
|
|
GB_load_battery(&gb, self.savPath.UTF8String);
|
|
|
|
|
GB_load_cheats(&gb, self.chtPath.UTF8String);
|
2020-04-11 18:48:47 +00:00
|
|
|
|
[self.cheatWindowController cheatsUpdated];
|
2017-04-17 17:16:17 +00:00
|
|
|
|
GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]);
|
2022-02-25 23:47:47 +00:00
|
|
|
|
GB_debugger_load_symbol_file(&gb, [[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"].UTF8String);
|
2017-04-17 17:16:17 +00:00
|
|
|
|
}];
|
2021-04-19 17:57:28 +00:00
|
|
|
|
if (ret) {
|
|
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
|
|
|
[alert setMessageText:rom_warnings?: @"Could not load ROM"];
|
|
|
|
|
[alert setAlertStyle:NSAlertStyleCritical];
|
|
|
|
|
[alert runModal];
|
|
|
|
|
}
|
|
|
|
|
else if (rom_warnings && !rom_warning_issued) {
|
2017-04-17 17:16:17 +00:00
|
|
|
|
rom_warning_issued = true;
|
|
|
|
|
[GBWarningPopover popoverWithContents:rom_warnings onWindow:self.mainWindow];
|
|
|
|
|
}
|
2021-04-19 17:57:28 +00:00
|
|
|
|
return ret;
|
2017-02-24 13:14:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
- (void)close
|
|
|
|
|
{
|
2020-11-13 21:07:35 +00:00
|
|
|
|
[self disconnectLinkCable];
|
2021-04-02 22:29:43 +00:00
|
|
|
|
if (!self.gbsPlayerView) {
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"];
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"];
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[self stop];
|
|
|
|
|
[self.consoleWindow close];
|
2021-03-01 20:58:52 +00:00
|
|
|
|
[self.memoryWindow close];
|
|
|
|
|
[self.vramWindow close];
|
|
|
|
|
[self.printerFeedWindow close];
|
|
|
|
|
[self.cheatsWindow close];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[super close];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction) interrupt:(id)sender
|
|
|
|
|
{
|
|
|
|
|
[self log:"^C\n"];
|
2017-04-17 17:16:17 +00:00
|
|
|
|
GB_debugger_break(&gb);
|
2020-11-13 21:07:35 +00:00
|
|
|
|
[self start];
|
2017-05-27 14:16:20 +00:00
|
|
|
|
[self.consoleWindow makeKeyAndOrderFront:nil];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[self.consoleInput becomeFirstResponder];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)mute:(id)sender
|
|
|
|
|
{
|
2021-12-29 15:21:06 +00:00
|
|
|
|
if (_audioClient.isPlaying) {
|
|
|
|
|
[_audioClient stop];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
2021-12-29 15:21:06 +00:00
|
|
|
|
[_audioClient start];
|
|
|
|
|
if (_volume == 0) {
|
2021-05-21 15:12:29 +00:00
|
|
|
|
[GBWarningPopover popoverWithContents:@"Warning: Volume is set to to zero in the preferences panel" onWindow:self.mainWindow];
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2021-12-29 15:21:06 +00:00
|
|
|
|
[[NSUserDefaults standardUserDefaults] setBool:!_audioClient.isPlaying forKey:@"Mute"];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
|
|
|
|
|
{
|
2020-04-24 17:37:57 +00:00
|
|
|
|
if ([anItem action] == @selector(mute:)) {
|
2021-12-29 15:21:06 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:!_audioClient.isPlaying];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2016-04-08 10:54:34 +00:00
|
|
|
|
else if ([anItem action] == @selector(togglePause:)) {
|
2020-11-13 21:07:35 +00:00
|
|
|
|
if (master) {
|
|
|
|
|
[(NSMenuItem *)anItem setState:(!master->running) || (GB_debugger_is_stopped(&gb)) || (GB_debugger_is_stopped(&gb))];
|
|
|
|
|
}
|
|
|
|
|
[(NSMenuItem *)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))];
|
2017-04-17 17:16:17 +00:00
|
|
|
|
return !GB_debugger_is_stopped(&gb);
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2018-01-13 11:16:33 +00:00
|
|
|
|
else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) {
|
2022-05-20 23:17:59 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:anItem.tag == current_model];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2016-04-08 10:54:34 +00:00
|
|
|
|
else if ([anItem action] == @selector(interrupt:)) {
|
|
|
|
|
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-13 20:26:44 +00:00
|
|
|
|
else if ([anItem action] == @selector(disconnectAllAccessories:)) {
|
2022-05-20 23:17:59 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:accessory == GBAccessoryNone];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
}
|
|
|
|
|
else if ([anItem action] == @selector(connectPrinter:)) {
|
2022-05-20 23:17:59 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:accessory == GBAccessoryPrinter];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
}
|
2020-09-19 16:31:24 +00:00
|
|
|
|
else if ([anItem action] == @selector(connectWorkboy:)) {
|
2022-05-20 23:17:59 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:accessory == GBAccessoryWorkboy];
|
2020-09-19 16:31:24 +00:00
|
|
|
|
}
|
2020-11-13 21:07:35 +00:00
|
|
|
|
else if ([anItem action] == @selector(connectLinkCable:)) {
|
2022-05-20 23:17:59 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:[(NSMenuItem *)anItem representedObject] == master ||
|
2020-11-13 21:07:35 +00:00
|
|
|
|
[(NSMenuItem *)anItem representedObject] == slave];
|
|
|
|
|
}
|
2020-04-11 15:03:10 +00:00
|
|
|
|
else if ([anItem action] == @selector(toggleCheats:)) {
|
2022-05-20 23:17:59 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:GB_cheats_enabled(&gb)];
|
2020-04-11 15:03:10 +00:00
|
|
|
|
}
|
2021-12-17 19:12:26 +00:00
|
|
|
|
else if ([anItem action] == @selector(toggleDisplayBackground:)) {
|
2022-05-20 23:17:59 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:!GB_is_background_rendering_disabled(&gb)];
|
2021-12-17 19:12:26 +00:00
|
|
|
|
}
|
|
|
|
|
else if ([anItem action] == @selector(toggleDisplayObjects:)) {
|
2022-05-20 23:17:59 +00:00
|
|
|
|
[(NSMenuItem *)anItem setState:!GB_is_object_rendering_disabled(&gb)];
|
|
|
|
|
}
|
|
|
|
|
else if ([anItem action] == @selector(toggleAudioRecording:)) {
|
|
|
|
|
[(NSMenuItem *)anItem setTitle:_isRecordingAudio? @"Stop Audio Recording" : @"Start Audio Recording…"];
|
2021-12-17 19:12:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
return [super validateUserInterfaceItem:anItem];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-07-03 18:32:58 +00:00
|
|
|
|
- (void) windowWillEnterFullScreen:(NSNotification *)notification
|
|
|
|
|
{
|
|
|
|
|
fullScreen = true;
|
2016-08-20 21:38:26 +00:00
|
|
|
|
self.view.mouseHidingEnabled = running;
|
2016-07-03 18:32:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) windowWillExitFullScreen:(NSNotification *)notification
|
|
|
|
|
{
|
|
|
|
|
fullScreen = false;
|
2021-10-23 10:36:58 +00:00
|
|
|
|
self.view.mouseHidingEnabled = false;
|
2016-07-03 18:32:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame
|
|
|
|
|
{
|
2016-07-03 18:32:58 +00:00
|
|
|
|
if (fullScreen) {
|
|
|
|
|
return newFrame;
|
|
|
|
|
}
|
2018-11-14 22:21:21 +00:00
|
|
|
|
size_t width = GB_get_screen_width(&gb),
|
|
|
|
|
height = GB_get_screen_height(&gb);
|
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
NSRect rect = window.contentView.frame;
|
|
|
|
|
|
2020-04-09 11:32:52 +00:00
|
|
|
|
unsigned titlebarSize = window.contentView.superview.frame.size.height - rect.size.height;
|
|
|
|
|
unsigned step = width / [[window screen] backingScaleFactor];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
|
|
|
|
|
rect.size.width = floor(rect.size.width / step) * step + step;
|
2018-11-14 22:21:21 +00:00
|
|
|
|
rect.size.height = rect.size.width * height / width + titlebarSize;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
|
|
|
|
|
if (rect.size.width > newFrame.size.width) {
|
2018-11-14 22:21:21 +00:00
|
|
|
|
rect.size.width = width;
|
|
|
|
|
rect.size.height = height + titlebarSize;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
else if (rect.size.height > newFrame.size.height) {
|
2018-11-14 22:21:21 +00:00
|
|
|
|
rect.size.width = width;
|
|
|
|
|
rect.size.height = height + titlebarSize;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rect.origin = window.frame.origin;
|
|
|
|
|
rect.origin.y -= rect.size.height - window.frame.size.height;
|
|
|
|
|
|
|
|
|
|
return rect;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-21 18:23:57 +00:00
|
|
|
|
- (void) appendPendingOutput
|
|
|
|
|
{
|
|
|
|
|
[console_output_lock lock];
|
|
|
|
|
if (shouldClearSideView) {
|
|
|
|
|
shouldClearSideView = false;
|
|
|
|
|
[self.debuggerSideView setString:@""];
|
|
|
|
|
}
|
|
|
|
|
if (pending_console_output) {
|
|
|
|
|
NSTextView *textView = logToSideView? self.debuggerSideView : self.consoleOutput;
|
|
|
|
|
|
|
|
|
|
[hex_controller reloadData];
|
|
|
|
|
[self reloadVRAMData: nil];
|
|
|
|
|
|
|
|
|
|
[textView.textStorage appendAttributedString:pending_console_output];
|
|
|
|
|
[textView scrollToEndOfDocument:nil];
|
|
|
|
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) {
|
2018-09-15 14:57:59 +00:00
|
|
|
|
[self.consoleWindow orderFront:nil];
|
2018-06-21 18:23:57 +00:00
|
|
|
|
}
|
|
|
|
|
pending_console_output = nil;
|
2022-01-21 23:11:23 +00:00
|
|
|
|
}
|
2018-06-21 18:23:57 +00:00
|
|
|
|
[console_output_lock unlock];
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
|
- (void) log: (const char *) string withAttributes: (GB_log_attributes) attributes
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2017-02-24 16:15:31 +00:00
|
|
|
|
NSString *nsstring = @(string); // For ref-counting
|
|
|
|
|
if (capturedOutput) {
|
|
|
|
|
[capturedOutput appendString:nsstring];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-26 17:16:19 +00:00
|
|
|
|
|
2018-06-21 18:23:57 +00:00
|
|
|
|
NSFont *font = [NSFont userFixedPitchFontOfSize:12];
|
|
|
|
|
NSUnderlineStyle underline = NSUnderlineStyleNone;
|
|
|
|
|
if (attributes & GB_LOG_BOLD) {
|
|
|
|
|
font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attributes & GB_LOG_UNDERLINE_MASK) {
|
|
|
|
|
underline = (attributes & GB_LOG_UNDERLINE_MASK) == GB_LOG_DASHED_UNDERLINE? NSUnderlinePatternDot | NSUnderlineStyleSingle : NSUnderlineStyleSingle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSMutableParagraphStyle *paragraph_style = [[NSMutableParagraphStyle alloc] init];
|
|
|
|
|
[paragraph_style setLineSpacing:2];
|
|
|
|
|
NSMutableAttributedString *attributed =
|
|
|
|
|
[[NSMutableAttributedString alloc] initWithString:nsstring
|
|
|
|
|
attributes:@{NSFontAttributeName: font,
|
|
|
|
|
NSForegroundColorAttributeName: [NSColor whiteColor],
|
|
|
|
|
NSUnderlineStyleAttributeName: @(underline),
|
|
|
|
|
NSParagraphStyleAttributeName: paragraph_style}];
|
|
|
|
|
|
|
|
|
|
[console_output_lock lock];
|
|
|
|
|
if (!pending_console_output) {
|
|
|
|
|
pending_console_output = attributed;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[pending_console_output appendAttributedString:attributed];
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-15 14:57:59 +00:00
|
|
|
|
if (![console_output_timer isValid]) {
|
2021-10-23 10:36:58 +00:00
|
|
|
|
console_output_timer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 target:self selector:@selector(appendPendingOutput) userInfo:nil repeats:false];
|
2018-06-21 18:23:57 +00:00
|
|
|
|
[[NSRunLoop mainRunLoop] addTimer:console_output_timer forMode:NSDefaultRunLoopMode];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2018-06-21 18:23:57 +00:00
|
|
|
|
|
|
|
|
|
[console_output_lock unlock];
|
2016-07-05 20:34:33 +00:00
|
|
|
|
|
|
|
|
|
/* Make sure mouse is not hidden while debugging */
|
2021-10-23 10:36:58 +00:00
|
|
|
|
self.view.mouseHidingEnabled = false;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)showConsoleWindow:(id)sender
|
|
|
|
|
{
|
|
|
|
|
[self.consoleWindow orderBack:nil];
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 17:37:57 +00:00
|
|
|
|
- (IBAction)consoleInput:(NSTextField *)sender
|
|
|
|
|
{
|
2016-03-30 20:07:55 +00:00
|
|
|
|
NSString *line = [sender stringValue];
|
2016-04-07 22:53:21 +00:00
|
|
|
|
if ([line isEqualToString:@""] && lastConsoleInput) {
|
2016-03-30 20:31:34 +00:00
|
|
|
|
line = lastConsoleInput;
|
|
|
|
|
}
|
|
|
|
|
else if (line) {
|
|
|
|
|
lastConsoleInput = line;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2016-03-30 20:07:55 +00:00
|
|
|
|
line = @"";
|
|
|
|
|
}
|
2016-07-17 21:39:43 +00:00
|
|
|
|
|
|
|
|
|
if (!in_sync_input) {
|
|
|
|
|
[self log:">"];
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[self log:[line UTF8String]];
|
|
|
|
|
[self log:"\n"];
|
|
|
|
|
[has_debugger_input lock];
|
|
|
|
|
[debugger_input_queue addObject:line];
|
|
|
|
|
[has_debugger_input unlockWithCondition:1];
|
|
|
|
|
|
|
|
|
|
[sender setStringValue:@""];
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-19 20:26:39 +00:00
|
|
|
|
- (void) interruptDebugInputRead
|
|
|
|
|
{
|
|
|
|
|
[has_debugger_input lock];
|
|
|
|
|
[debugger_input_queue addObject:[NSNull null]];
|
|
|
|
|
[has_debugger_input unlockWithCondition:1];
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-26 17:16:19 +00:00
|
|
|
|
- (void) updateSideView
|
|
|
|
|
{
|
|
|
|
|
if (!GB_debugger_is_stopped(&gb)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-06-21 18:23:57 +00:00
|
|
|
|
|
|
|
|
|
if (![NSThread isMainThread]) {
|
|
|
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
|
|
|
[self updateSideView];
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[console_output_lock lock];
|
2017-05-26 17:16:19 +00:00
|
|
|
|
shouldClearSideView = true;
|
2018-06-21 18:23:57 +00:00
|
|
|
|
[self appendPendingOutput];
|
2017-05-26 17:16:19 +00:00
|
|
|
|
logToSideView = true;
|
2018-06-21 18:23:57 +00:00
|
|
|
|
[console_output_lock unlock];
|
|
|
|
|
|
2017-05-26 17:16:19 +00:00
|
|
|
|
for (NSString *line in [self.debuggerSideViewInput.string componentsSeparatedByString:@"\n"]) {
|
|
|
|
|
NSString *stripped = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
|
|
|
if ([stripped length]) {
|
|
|
|
|
char *dupped = strdup([stripped UTF8String]);
|
|
|
|
|
GB_attributed_log(&gb, GB_LOG_BOLD, "%s:\n", dupped);
|
|
|
|
|
GB_debugger_execute_command(&gb, dupped);
|
|
|
|
|
GB_log(&gb, "\n");
|
|
|
|
|
free(dupped);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-21 18:23:57 +00:00
|
|
|
|
|
|
|
|
|
[console_output_lock lock];
|
|
|
|
|
[self appendPendingOutput];
|
2017-05-26 17:16:19 +00:00
|
|
|
|
logToSideView = false;
|
2018-06-21 18:23:57 +00:00
|
|
|
|
[console_output_lock unlock];
|
2017-05-26 17:16:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-19 20:26:39 +00:00
|
|
|
|
- (char *) getDebuggerInput
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2022-06-25 17:09:23 +00:00
|
|
|
|
bool isPlaying = _audioClient.isPlaying;
|
|
|
|
|
if (isPlaying) {
|
|
|
|
|
[_audioClient stop];
|
|
|
|
|
}
|
2021-02-25 13:43:52 +00:00
|
|
|
|
[audioLock lock];
|
2021-02-01 21:11:42 +00:00
|
|
|
|
[audioLock signal];
|
2021-02-25 13:43:52 +00:00
|
|
|
|
[audioLock unlock];
|
2017-05-26 17:16:19 +00:00
|
|
|
|
[self updateSideView];
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[self log:">"];
|
2016-07-17 21:39:43 +00:00
|
|
|
|
in_sync_input = true;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
[has_debugger_input lockWhenCondition:1];
|
|
|
|
|
NSString *input = [debugger_input_queue firstObject];
|
|
|
|
|
[debugger_input_queue removeObjectAtIndex:0];
|
|
|
|
|
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
|
2016-07-17 21:39:43 +00:00
|
|
|
|
in_sync_input = false;
|
2017-05-26 17:16:19 +00:00
|
|
|
|
shouldClearSideView = true;
|
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC / 10)), dispatch_get_main_queue(), ^{
|
|
|
|
|
if (shouldClearSideView) {
|
|
|
|
|
shouldClearSideView = false;
|
|
|
|
|
[self.debuggerSideView setString:@""];
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-06-25 17:09:23 +00:00
|
|
|
|
if (isPlaying) {
|
|
|
|
|
[_audioClient start];
|
|
|
|
|
}
|
2017-04-19 20:26:39 +00:00
|
|
|
|
if ((id) input == [NSNull null]) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return strdup([input UTF8String]);
|
2016-07-17 21:39:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-19 20:26:39 +00:00
|
|
|
|
- (char *) getAsyncDebuggerInput
|
2016-07-17 21:39:43 +00:00
|
|
|
|
{
|
|
|
|
|
[has_debugger_input lock];
|
|
|
|
|
NSString *input = [debugger_input_queue firstObject];
|
|
|
|
|
if (input) {
|
|
|
|
|
[debugger_input_queue removeObjectAtIndex:0];
|
|
|
|
|
}
|
|
|
|
|
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
|
2020-06-04 23:10:05 +00:00
|
|
|
|
if ((id)input == [NSNull null]) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2017-04-19 20:26:39 +00:00
|
|
|
|
return input? strdup([input UTF8String]): NULL;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)saveState:(id)sender
|
|
|
|
|
{
|
2017-02-24 16:15:31 +00:00
|
|
|
|
bool __block success = false;
|
|
|
|
|
[self performAtomicBlock:^{
|
2022-02-25 23:47:47 +00:00
|
|
|
|
success = GB_save_state(&gb, [self saveStatePath:[sender tag]].UTF8String) == 0;
|
2017-02-24 16:15:31 +00:00
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
[GBWarningPopover popoverWithContents:@"Failed to write save state." onWindow:self.mainWindow];
|
|
|
|
|
NSBeep();
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2021-05-30 17:55:04 +00:00
|
|
|
|
else {
|
|
|
|
|
[self.osdView displayText:@"State saved"];
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 21:33:04 +00:00
|
|
|
|
- (int)loadStateFile:(const char *)path noErrorOnNotFound:(bool)noErrorOnFileNotFound;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2021-05-06 21:33:04 +00:00
|
|
|
|
int __block result = false;
|
2017-02-24 16:15:31 +00:00
|
|
|
|
NSString *error =
|
|
|
|
|
[self captureOutputForBlock:^{
|
2021-05-06 21:33:04 +00:00
|
|
|
|
result = GB_load_state(&gb, path);
|
2017-02-24 16:15:31 +00:00
|
|
|
|
}];
|
|
|
|
|
|
2021-05-06 21:33:04 +00:00
|
|
|
|
if (result == ENOENT && noErrorOnFileNotFound) {
|
|
|
|
|
return ENOENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result) {
|
2017-02-24 16:15:31 +00:00
|
|
|
|
NSBeep();
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
2021-05-30 17:55:04 +00:00
|
|
|
|
else {
|
|
|
|
|
[self.osdView displayText:@"State loaded"];
|
|
|
|
|
}
|
2021-03-23 21:33:16 +00:00
|
|
|
|
if (error) {
|
|
|
|
|
[GBWarningPopover popoverWithContents:error onWindow:self.mainWindow];
|
|
|
|
|
}
|
2021-05-06 21:33:04 +00:00
|
|
|
|
return result;
|
2021-04-14 12:20:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)loadState:(id)sender
|
|
|
|
|
{
|
2022-02-25 23:47:47 +00:00
|
|
|
|
int ret = [self loadStateFile:[self saveStatePath:[sender tag]].UTF8String noErrorOnNotFound:true];
|
|
|
|
|
if (ret == ENOENT && !self.isCartContainer) {
|
2021-05-06 21:33:04 +00:00
|
|
|
|
[self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"sn%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:false];
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)clearConsole:(id)sender
|
|
|
|
|
{
|
|
|
|
|
[self.consoleOutput setString:@""];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)log:(const char *)log
|
|
|
|
|
{
|
|
|
|
|
[self log:log withAttributes:0];
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 19:49:17 +00:00
|
|
|
|
- (uint8_t) readMemory:(uint16_t)addr
|
|
|
|
|
{
|
2017-02-24 13:14:00 +00:00
|
|
|
|
while (!GB_is_inited(&gb));
|
2021-11-26 11:54:28 +00:00
|
|
|
|
return GB_safe_read_memory(&gb, addr);
|
2016-08-12 19:49:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) writeMemory:(uint16_t)addr value:(uint8_t)value
|
|
|
|
|
{
|
2017-02-24 13:14:00 +00:00
|
|
|
|
while (!GB_is_inited(&gb));
|
2016-08-12 19:49:17 +00:00
|
|
|
|
GB_write_memory(&gb, addr, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) performAtomicBlock: (void (^)())block
|
|
|
|
|
{
|
2017-02-24 13:14:00 +00:00
|
|
|
|
while (!GB_is_inited(&gb));
|
2017-04-17 17:16:17 +00:00
|
|
|
|
bool was_running = running && !GB_debugger_is_stopped(&gb);
|
2020-11-13 21:07:35 +00:00
|
|
|
|
if (master) {
|
|
|
|
|
was_running |= master->running;
|
|
|
|
|
}
|
2016-08-12 19:49:17 +00:00
|
|
|
|
if (was_running) {
|
|
|
|
|
[self stop];
|
|
|
|
|
}
|
|
|
|
|
block();
|
|
|
|
|
if (was_running) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-24 16:15:31 +00:00
|
|
|
|
- (NSString *) captureOutputForBlock: (void (^)())block
|
|
|
|
|
{
|
|
|
|
|
capturedOutput = [[NSMutableString alloc] init];
|
|
|
|
|
[self performAtomicBlock:block];
|
|
|
|
|
NSString *ret = [capturedOutput stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
|
|
|
|
capturedOutput = nil;
|
|
|
|
|
return [ret length]? ret : nil;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 21:14:02 +00:00
|
|
|
|
+ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale
|
|
|
|
|
{
|
2017-09-27 19:09:26 +00:00
|
|
|
|
|
2022-02-19 20:13:07 +00:00
|
|
|
|
NSImage *ret = [[NSImage alloc] initWithSize:NSMakeSize(width * scale, height * scale)];
|
|
|
|
|
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
|
|
|
|
pixelsWide:width
|
|
|
|
|
pixelsHigh:height
|
|
|
|
|
bitsPerSample:8
|
|
|
|
|
samplesPerPixel:3
|
|
|
|
|
hasAlpha:false
|
|
|
|
|
isPlanar:false
|
|
|
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
|
|
|
|
bitmapFormat:0
|
|
|
|
|
bytesPerRow:4 * width
|
|
|
|
|
bitsPerPixel:32];
|
|
|
|
|
memcpy(rep.bitmapData, data.bytes, data.length);
|
|
|
|
|
[ret addRepresentation:rep];
|
2017-09-27 19:09:26 +00:00
|
|
|
|
return ret;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 19:49:17 +00:00
|
|
|
|
- (void) reloadMemoryView
|
|
|
|
|
{
|
|
|
|
|
if (self.memoryWindow.isVisible) {
|
|
|
|
|
[hex_controller reloadData];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 21:14:02 +00:00
|
|
|
|
- (IBAction) reloadVRAMData: (id) sender
|
|
|
|
|
{
|
|
|
|
|
if (self.vramWindow.isVisible) {
|
2021-12-25 23:47:59 +00:00
|
|
|
|
uint8_t *io_regs = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IO, NULL, NULL);
|
2016-10-26 21:14:02 +00:00
|
|
|
|
switch ([self.vramTabView.tabViewItems indexOfObject:self.vramTabView.selectedTabViewItem]) {
|
|
|
|
|
case 0:
|
|
|
|
|
/* Tileset */
|
|
|
|
|
{
|
|
|
|
|
GB_palette_type_t palette_type = GB_PALETTE_NONE;
|
|
|
|
|
NSUInteger palette_menu_index = self.tilesetPaletteButton.indexOfSelectedItem;
|
|
|
|
|
if (palette_menu_index) {
|
|
|
|
|
palette_type = palette_menu_index > 8? GB_PALETTE_OAM : GB_PALETTE_BACKGROUND;
|
|
|
|
|
}
|
|
|
|
|
size_t bufferLength = 256 * 192 * 4;
|
|
|
|
|
NSMutableData *data = [NSMutableData dataWithCapacity:bufferLength];
|
|
|
|
|
data.length = bufferLength;
|
|
|
|
|
GB_draw_tileset(&gb, (uint32_t *)data.mutableBytes, palette_type, (palette_menu_index - 1) & 7);
|
|
|
|
|
|
|
|
|
|
self.tilesetImageView.image = [Document imageFromData:data width:256 height:192 scale:1.0];
|
|
|
|
|
self.tilesetImageView.layer.magnificationFilter = kCAFilterNearest;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
/* Tilemap */
|
|
|
|
|
{
|
|
|
|
|
GB_palette_type_t palette_type = GB_PALETTE_NONE;
|
|
|
|
|
NSUInteger palette_menu_index = self.tilemapPaletteButton.indexOfSelectedItem;
|
|
|
|
|
if (palette_menu_index > 1) {
|
|
|
|
|
palette_type = palette_menu_index > 9? GB_PALETTE_OAM : GB_PALETTE_BACKGROUND;
|
|
|
|
|
}
|
|
|
|
|
else if (palette_menu_index == 1) {
|
|
|
|
|
palette_type = GB_PALETTE_AUTO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t bufferLength = 256 * 256 * 4;
|
|
|
|
|
NSMutableData *data = [NSMutableData dataWithCapacity:bufferLength];
|
|
|
|
|
data.length = bufferLength;
|
|
|
|
|
GB_draw_tilemap(&gb, (uint32_t *)data.mutableBytes, palette_type, (palette_menu_index - 2) & 7,
|
|
|
|
|
(GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem,
|
|
|
|
|
(GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem);
|
|
|
|
|
|
2021-12-25 23:47:59 +00:00
|
|
|
|
self.tilemapImageView.scrollRect = NSMakeRect(io_regs[GB_IO_SCX],
|
|
|
|
|
io_regs[GB_IO_SCY],
|
2019-06-07 21:04:58 +00:00
|
|
|
|
160, 144);
|
2016-10-26 21:14:02 +00:00
|
|
|
|
self.tilemapImageView.image = [Document imageFromData:data width:256 height:256 scale:1.0];
|
|
|
|
|
self.tilemapImageView.layer.magnificationFilter = kCAFilterNearest;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
|
/* OAM */
|
|
|
|
|
{
|
2022-02-19 23:58:59 +00:00
|
|
|
|
_oamCount = GB_get_oam_info(&gb, _oamInfo, &_oamHeight);
|
2016-10-26 21:14:02 +00:00
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2022-02-19 23:58:59 +00:00
|
|
|
|
[self.objectView reloadData:self];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
|
/* Palettes */
|
|
|
|
|
{
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2022-02-20 12:23:49 +00:00
|
|
|
|
[self.paletteView reloadData:self];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 19:49:17 +00:00
|
|
|
|
- (IBAction) showMemory:(id)sender
|
|
|
|
|
{
|
|
|
|
|
if (!hex_controller) {
|
|
|
|
|
[self initMemoryView];
|
|
|
|
|
}
|
|
|
|
|
[self.memoryWindow makeKeyAndOrderFront:sender];
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-19 11:54:54 +00:00
|
|
|
|
- (IBAction)hexGoTo:(id)sender
|
|
|
|
|
{
|
2017-02-24 16:15:31 +00:00
|
|
|
|
NSString *error = [self captureOutputForBlock:^{
|
2016-08-19 11:54:54 +00:00
|
|
|
|
uint16_t addr;
|
|
|
|
|
if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, NULL)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
addr -= lineRep.valueOffset;
|
|
|
|
|
if (addr >= hex_controller.byteArray.length) {
|
2017-02-24 16:15:31 +00:00
|
|
|
|
GB_log(&gb, "Value $%04x is out of range.\n", addr);
|
2016-08-19 11:54:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
[hex_controller setSelectedContentsRanges:@[[HFRangeWrapper withRange:HFRangeMake(addr, 0)]]];
|
|
|
|
|
[hex_controller _ensureVisibilityOfLocation:addr];
|
|
|
|
|
[self.memoryWindow makeFirstResponder:self.memoryView.subviews[0].subviews[0]];
|
|
|
|
|
}];
|
2017-02-24 16:15:31 +00:00
|
|
|
|
if (error) {
|
|
|
|
|
NSBeep();
|
|
|
|
|
[GBWarningPopover popoverWithContents:error onView:sender];
|
|
|
|
|
}
|
2016-08-19 11:54:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-16 11:46:16 +00:00
|
|
|
|
- (void)hexUpdateBank:(NSControl *)sender ignoreErrors: (bool)ignore_errors
|
2016-08-19 11:54:54 +00:00
|
|
|
|
{
|
2017-02-24 16:15:31 +00:00
|
|
|
|
NSString *error = [self captureOutputForBlock:^{
|
2016-08-19 11:54:54 +00:00
|
|
|
|
uint16_t addr, bank;
|
|
|
|
|
if (GB_debugger_evaluate(&gb, [[sender stringValue] UTF8String], &addr, &bank)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bank == (uint16_t) -1) {
|
|
|
|
|
bank = addr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint16_t n_banks = 1;
|
|
|
|
|
switch ([(GBMemoryByteArray *)(hex_controller.byteArray) mode]) {
|
2017-04-19 20:26:39 +00:00
|
|
|
|
case GBMemoryROM: {
|
|
|
|
|
size_t rom_size;
|
|
|
|
|
GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, &rom_size, NULL);
|
|
|
|
|
n_banks = rom_size / 0x4000;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
break;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
}
|
2016-08-19 11:54:54 +00:00
|
|
|
|
case GBMemoryVRAM:
|
2017-04-19 20:26:39 +00:00
|
|
|
|
n_banks = GB_is_cgb(&gb) ? 2 : 1;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
break;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
case GBMemoryExternalRAM: {
|
|
|
|
|
size_t ram_size;
|
|
|
|
|
GB_get_direct_access(&gb, GB_DIRECT_ACCESS_CART_RAM, &ram_size, NULL);
|
|
|
|
|
n_banks = (ram_size + 0x1FFF) / 0x2000;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
break;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
}
|
2016-08-19 11:54:54 +00:00
|
|
|
|
case GBMemoryRAM:
|
2017-04-19 20:26:39 +00:00
|
|
|
|
n_banks = GB_is_cgb(&gb) ? 8 : 1;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
break;
|
|
|
|
|
case GBMemoryEntireSpace:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bank %= n_banks;
|
|
|
|
|
|
|
|
|
|
[sender setStringValue:[NSString stringWithFormat:@"$%x", bank]];
|
|
|
|
|
[(GBMemoryByteArray *)(hex_controller.byteArray) setSelectedBank:bank];
|
|
|
|
|
[hex_controller reloadData];
|
|
|
|
|
}];
|
2017-02-24 16:15:31 +00:00
|
|
|
|
|
2018-06-16 11:46:16 +00:00
|
|
|
|
if (error && !ignore_errors) {
|
2017-02-24 16:15:31 +00:00
|
|
|
|
NSBeep();
|
|
|
|
|
[GBWarningPopover popoverWithContents:error onView:sender];
|
|
|
|
|
}
|
2016-08-19 11:54:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-16 11:46:16 +00:00
|
|
|
|
- (IBAction)hexUpdateBank:(NSControl *)sender
|
|
|
|
|
{
|
|
|
|
|
[self hexUpdateBank:sender ignoreErrors:false];
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-19 11:54:54 +00:00
|
|
|
|
- (IBAction)hexUpdateSpace:(NSPopUpButtonCell *)sender
|
|
|
|
|
{
|
|
|
|
|
self.memoryBankItem.enabled = [sender indexOfSelectedItem] != GBMemoryEntireSpace;
|
|
|
|
|
GBMemoryByteArray *byteArray = (GBMemoryByteArray *)(hex_controller.byteArray);
|
|
|
|
|
[byteArray setMode:(GB_memory_mode_t)[sender indexOfSelectedItem]];
|
2017-04-19 20:26:39 +00:00
|
|
|
|
uint16_t bank;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
switch ((GB_memory_mode_t)[sender indexOfSelectedItem]) {
|
|
|
|
|
case GBMemoryEntireSpace:
|
|
|
|
|
case GBMemoryROM:
|
|
|
|
|
lineRep.valueOffset = 0;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, NULL, &bank);
|
|
|
|
|
byteArray.selectedBank = bank;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
break;
|
|
|
|
|
case GBMemoryVRAM:
|
|
|
|
|
lineRep.valueOffset = 0x8000;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, NULL, &bank);
|
|
|
|
|
byteArray.selectedBank = bank;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
break;
|
|
|
|
|
case GBMemoryExternalRAM:
|
|
|
|
|
lineRep.valueOffset = 0xA000;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
GB_get_direct_access(&gb, GB_DIRECT_ACCESS_CART_RAM, NULL, &bank);
|
|
|
|
|
byteArray.selectedBank = bank;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
break;
|
|
|
|
|
case GBMemoryRAM:
|
|
|
|
|
lineRep.valueOffset = 0xC000;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
GB_get_direct_access(&gb, GB_DIRECT_ACCESS_RAM, NULL, &bank);
|
|
|
|
|
byteArray.selectedBank = bank;
|
2016-08-19 11:54:54 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
[self.memoryBankInput setStringValue:[NSString stringWithFormat:@"$%x", byteArray.selectedBank]];
|
|
|
|
|
[hex_controller reloadData];
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[self.memoryView setNeedsDisplay:true];
|
2016-08-19 11:54:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (GB_gameboy_t *) gameboy
|
|
|
|
|
{
|
|
|
|
|
return &gb;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-01 21:10:31 +00:00
|
|
|
|
+ (BOOL)canConcurrentlyReadDocumentsOfType:(NSString *)typeName
|
|
|
|
|
{
|
2021-10-23 10:36:58 +00:00
|
|
|
|
return true;
|
2016-10-01 21:10:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-02 21:26:12 +00:00
|
|
|
|
- (void)cameraRequestUpdate
|
|
|
|
|
{
|
2021-04-25 19:28:24 +00:00
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
2016-10-02 21:26:12 +00:00
|
|
|
|
@try {
|
|
|
|
|
if (!cameraSession) {
|
2019-12-29 16:34:43 +00:00
|
|
|
|
if (@available(macOS 10.14, *)) {
|
|
|
|
|
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {
|
|
|
|
|
case AVAuthorizationStatusAuthorized:
|
|
|
|
|
break;
|
|
|
|
|
case AVAuthorizationStatusNotDetermined: {
|
|
|
|
|
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
|
|
|
|
|
[self cameraRequestUpdate];
|
|
|
|
|
}];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case AVAuthorizationStatusDenied:
|
|
|
|
|
case AVAuthorizationStatusRestricted:
|
|
|
|
|
GB_camera_updated(&gb);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-02 21:26:12 +00:00
|
|
|
|
NSError *error;
|
|
|
|
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
|
|
|
|
|
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice: device error: &error];
|
|
|
|
|
CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions([[[device formats] firstObject] formatDescription]);
|
|
|
|
|
|
|
|
|
|
if (!input) {
|
|
|
|
|
GB_camera_updated(&gb);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cameraOutput = [[AVCaptureStillImageOutput alloc] init];
|
|
|
|
|
/* Greyscale is not widely supported, so we use YUV, whose first element is the brightness. */
|
|
|
|
|
[cameraOutput setOutputSettings: @{(id)kCVPixelBufferPixelFormatTypeKey: @(kYUVSPixelFormat),
|
|
|
|
|
(id)kCVPixelBufferWidthKey: @(MAX(128, 112 * dimensions.width / dimensions.height)),
|
|
|
|
|
(id)kCVPixelBufferHeightKey: @(MAX(112, 128 * dimensions.height / dimensions.width)),}];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cameraSession = [AVCaptureSession new];
|
|
|
|
|
cameraSession.sessionPreset = AVCaptureSessionPresetPhoto;
|
|
|
|
|
|
|
|
|
|
[cameraSession addInput: input];
|
|
|
|
|
[cameraSession addOutput: cameraOutput];
|
|
|
|
|
[cameraSession startRunning];
|
|
|
|
|
cameraConnection = [cameraOutput connectionWithMediaType: AVMediaTypeVideo];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[cameraOutput captureStillImageAsynchronouslyFromConnection: cameraConnection completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError *error) {
|
|
|
|
|
if (error) {
|
|
|
|
|
GB_camera_updated(&gb);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (cameraImage) {
|
|
|
|
|
CVBufferRelease(cameraImage);
|
|
|
|
|
cameraImage = NULL;
|
|
|
|
|
}
|
|
|
|
|
cameraImage = CVBufferRetain(CMSampleBufferGetImageBuffer(sampleBuffer));
|
|
|
|
|
/* We only need the actual buffer, no need to ever unlock it. */
|
|
|
|
|
CVPixelBufferLockBaseAddress(cameraImage, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GB_camera_updated(&gb);
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
@catch (NSException *exception) {
|
|
|
|
|
/* I have not tested camera support on many devices, so we catch exceptions just in case. */
|
|
|
|
|
GB_camera_updated(&gb);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (uint8_t)cameraGetPixelAtX:(uint8_t)x andY:(uint8_t) y
|
|
|
|
|
{
|
|
|
|
|
if (!cameraImage) {
|
2016-10-03 20:05:47 +00:00
|
|
|
|
return 0;
|
2016-10-02 21:26:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(cameraImage);
|
|
|
|
|
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(cameraImage);
|
|
|
|
|
uint8_t offsetX = (CVPixelBufferGetWidth(cameraImage) - 128) / 2;
|
|
|
|
|
uint8_t offsetY = (CVPixelBufferGetHeight(cameraImage) - 112) / 2;
|
|
|
|
|
uint8_t ret = baseAddress[(x + offsetX) * 2 + (y + offsetY) * bytesPerRow];
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
2016-10-26 21:14:02 +00:00
|
|
|
|
|
|
|
|
|
- (IBAction)toggleTilesetGrid:(NSButton *)sender
|
|
|
|
|
{
|
|
|
|
|
if (sender.state) {
|
|
|
|
|
self.tilesetImageView.horizontalGrids = @[
|
|
|
|
|
[[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8],
|
|
|
|
|
[[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.5] size:128],
|
|
|
|
|
|
|
|
|
|
];
|
|
|
|
|
self.tilesetImageView.verticalGrids = @[
|
|
|
|
|
[[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8],
|
|
|
|
|
[[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.5] size:64],
|
|
|
|
|
];
|
|
|
|
|
self.tilemapImageView.horizontalGrids = @[
|
|
|
|
|
[[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8],
|
|
|
|
|
];
|
|
|
|
|
self.tilemapImageView.verticalGrids = @[
|
|
|
|
|
[[GBImageViewGridConfiguration alloc] initWithColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.25] size:8],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
self.tilesetImageView.horizontalGrids = nil;
|
|
|
|
|
self.tilesetImageView.verticalGrids = nil;
|
|
|
|
|
self.tilemapImageView.horizontalGrids = nil;
|
|
|
|
|
self.tilemapImageView.verticalGrids = nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-07 20:38:34 +00:00
|
|
|
|
- (IBAction)toggleScrollingDisplay:(NSButton *)sender
|
|
|
|
|
{
|
|
|
|
|
self.tilemapImageView.displayScrollRect = sender.state;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 21:14:02 +00:00
|
|
|
|
- (IBAction)vramTabChanged:(NSSegmentedControl *)sender
|
|
|
|
|
{
|
|
|
|
|
[self.vramTabView selectTabViewItemAtIndex:[sender selectedSegment]];
|
|
|
|
|
[self reloadVRAMData:sender];
|
|
|
|
|
[self.vramTabView.selectedTabViewItem.view addSubview:self.gridButton];
|
|
|
|
|
self.gridButton.hidden = [sender selectedSegment] >= 2;
|
|
|
|
|
|
|
|
|
|
NSUInteger height_diff = self.vramWindow.frame.size.height - self.vramWindow.contentView.frame.size.height;
|
|
|
|
|
CGRect window_rect = self.vramWindow.frame;
|
|
|
|
|
window_rect.origin.y += window_rect.size.height;
|
|
|
|
|
switch ([sender selectedSegment]) {
|
|
|
|
|
case 0:
|
2022-02-19 23:58:59 +00:00
|
|
|
|
case 2:
|
2016-10-26 21:14:02 +00:00
|
|
|
|
window_rect.size.height = 384 + height_diff + 48;
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
window_rect.size.height = 512 + height_diff + 48;
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
2022-02-20 12:23:49 +00:00
|
|
|
|
window_rect.size.height = 24 * 16 + height_diff;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
window_rect.origin.y -= window_rect.size.height;
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[self.vramWindow setFrame:window_rect display:true animate:true];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)mouseDidLeaveImageView:(GBImageView *)view
|
|
|
|
|
{
|
|
|
|
|
self.vramStatusLabel.stringValue = @"";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)imageView:(GBImageView *)view mouseMovedToX:(NSUInteger)x Y:(NSUInteger)y
|
|
|
|
|
{
|
|
|
|
|
if (view == self.tilesetImageView) {
|
|
|
|
|
uint8_t bank = x >= 128? 1 : 0;
|
|
|
|
|
x &= 127;
|
|
|
|
|
uint16_t tile = x / 8 + y / 8 * 16;
|
|
|
|
|
self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x at %d:$%04x", tile & 0xFF, bank, 0x8000 + tile * 0x10];
|
|
|
|
|
}
|
|
|
|
|
else if (view == self.tilemapImageView) {
|
|
|
|
|
uint16_t map_offset = x / 8 + y / 8 * 32;
|
|
|
|
|
uint16_t map_base = 0x1800;
|
|
|
|
|
GB_map_type_t map_type = (GB_map_type_t) self.tilemapMapButton.indexOfSelectedItem;
|
|
|
|
|
GB_tileset_type_t tileset_type = (GB_tileset_type_t) self.TilemapSetButton.indexOfSelectedItem;
|
2017-04-19 20:26:39 +00:00
|
|
|
|
uint8_t lcdc = ((uint8_t *)GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IO, NULL, NULL))[GB_IO_LCDC];
|
|
|
|
|
uint8_t *vram = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, NULL, NULL);
|
2016-10-26 21:14:02 +00:00
|
|
|
|
|
2017-04-19 20:26:39 +00:00
|
|
|
|
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && lcdc & 0x08)) {
|
2022-02-13 14:58:44 +00:00
|
|
|
|
map_base = 0x1C00;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tileset_type == GB_TILESET_AUTO) {
|
2017-04-19 20:26:39 +00:00
|
|
|
|
tileset_type = (lcdc & 0x10)? GB_TILESET_8800 : GB_TILESET_8000;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-19 20:26:39 +00:00
|
|
|
|
uint8_t tile = vram[map_base + map_offset];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
uint16_t tile_address = 0;
|
|
|
|
|
if (tileset_type == GB_TILESET_8000) {
|
|
|
|
|
tile_address = 0x8000 + tile * 0x10;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
tile_address = 0x9000 + (int8_t)tile * 0x10;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-19 20:26:39 +00:00
|
|
|
|
if (GB_is_cgb(&gb)) {
|
|
|
|
|
uint8_t attributes = vram[map_base + map_offset + 0x2000];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x (%d:$%04x) at map address $%04x (Attributes: %c%c%c%d%d)",
|
|
|
|
|
tile,
|
|
|
|
|
attributes & 0x8? 1 : 0,
|
|
|
|
|
tile_address,
|
|
|
|
|
0x8000 + map_base + map_offset,
|
|
|
|
|
(attributes & 0x80) ? 'P' : '-',
|
|
|
|
|
(attributes & 0x40) ? 'V' : '-',
|
|
|
|
|
(attributes & 0x20) ? 'H' : '-',
|
|
|
|
|
attributes & 0x8? 1 : 0,
|
|
|
|
|
attributes & 0x7
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
self.vramStatusLabel.stringValue = [NSString stringWithFormat:@"Tile number $%02x ($%04x) at map address $%04x",
|
|
|
|
|
tile,
|
|
|
|
|
tile_address,
|
|
|
|
|
0x8000 + map_base + map_offset
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-19 23:58:59 +00:00
|
|
|
|
- (GB_oam_info_t *)oamInfo
|
|
|
|
|
{
|
|
|
|
|
return _oamInfo;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 21:14:02 +00:00
|
|
|
|
- (IBAction)showVRAMViewer:(id)sender
|
|
|
|
|
{
|
|
|
|
|
[self.vramWindow makeKeyAndOrderFront:sender];
|
2017-05-27 09:52:31 +00:00
|
|
|
|
[self reloadVRAMData: nil];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
}
|
2017-01-13 20:26:44 +00:00
|
|
|
|
|
|
|
|
|
- (void) printImage:(uint32_t *)imageBytes height:(unsigned) height
|
|
|
|
|
topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin
|
|
|
|
|
exposure:(unsigned) exposure
|
|
|
|
|
{
|
|
|
|
|
uint32_t paddedImage[160 * (topMargin + height + bottomMargin)];
|
|
|
|
|
memset(paddedImage, 0xFF, sizeof(paddedImage));
|
|
|
|
|
memcpy(paddedImage + (160 * topMargin), imageBytes, 160 * height * sizeof(imageBytes[0]));
|
|
|
|
|
if (!self.printerFeedWindow.isVisible) {
|
|
|
|
|
currentPrinterImageData = [[NSMutableData alloc] init];
|
|
|
|
|
}
|
|
|
|
|
[currentPrinterImageData appendBytes:paddedImage length:sizeof(paddedImage)];
|
|
|
|
|
/* UI related code must run on main thread. */
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2019-03-15 12:36:10 +00:00
|
|
|
|
self.feedImageView.image = [Document imageFromData:currentPrinterImageData
|
|
|
|
|
width:160
|
|
|
|
|
height:currentPrinterImageData.length / 160 / sizeof(imageBytes[0])
|
|
|
|
|
scale:2.0];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
NSRect frame = self.printerFeedWindow.frame;
|
|
|
|
|
frame.size = self.feedImageView.image.size;
|
2020-06-10 20:28:33 +00:00
|
|
|
|
[self.printerFeedWindow setContentMaxSize:frame.size];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
frame.size.height += self.printerFeedWindow.frame.size.height - self.printerFeedWindow.contentView.frame.size.height;
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[self.printerFeedWindow setFrame:frame display:false animate: self.printerFeedWindow.isVisible];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
[self.printerFeedWindow orderFront:NULL];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|
2020-06-10 20:28:33 +00:00
|
|
|
|
|
|
|
|
|
- (void)printDocument:(id)sender
|
|
|
|
|
{
|
|
|
|
|
if (self.feedImageView.image.size.height == 0) {
|
|
|
|
|
NSBeep(); return;
|
|
|
|
|
}
|
|
|
|
|
NSImageView *view = [[NSImageView alloc] initWithFrame:(NSRect){{0,0}, self.feedImageView.image.size}];
|
|
|
|
|
view.image = self.feedImageView.image;
|
|
|
|
|
[[NSPrintOperation printOperationWithView:view] runOperationModalForWindow:self.printerFeedWindow delegate:nil didRunSelector:NULL contextInfo:NULL];
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-13 20:26:44 +00:00
|
|
|
|
- (IBAction)savePrinterFeed:(id)sender
|
|
|
|
|
{
|
|
|
|
|
bool shouldResume = running;
|
|
|
|
|
[self stop];
|
2021-11-05 17:07:27 +00:00
|
|
|
|
NSSavePanel *savePanel = [NSSavePanel savePanel];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
[savePanel setAllowedFileTypes:@[@"png"]];
|
2020-04-24 17:37:57 +00:00
|
|
|
|
[savePanel beginSheetModalForWindow:self.printerFeedWindow completionHandler:^(NSInteger result) {
|
2021-01-13 19:52:18 +00:00
|
|
|
|
if (result == NSModalResponseOK) {
|
2017-01-13 20:26:44 +00:00
|
|
|
|
[savePanel orderOut:self];
|
|
|
|
|
CGImageRef cgRef = [self.feedImageView.image CGImageForProposedRect:NULL
|
|
|
|
|
context:nil
|
|
|
|
|
hints:nil];
|
|
|
|
|
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
|
|
|
|
|
[imageRep setSize:(NSSize){160, self.feedImageView.image.size.height / 2}];
|
2021-01-13 19:52:18 +00:00
|
|
|
|
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[data writeToURL:savePanel.URL atomically:false];
|
|
|
|
|
[self.printerFeedWindow setIsVisible:false];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
}
|
|
|
|
|
if (shouldResume) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)disconnectAllAccessories:(id)sender
|
|
|
|
|
{
|
2020-11-13 21:07:35 +00:00
|
|
|
|
[self disconnectLinkCable];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
[self performAtomicBlock:^{
|
|
|
|
|
accessory = GBAccessoryNone;
|
|
|
|
|
GB_disconnect_serial(&gb);
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)connectPrinter:(id)sender
|
|
|
|
|
{
|
2020-11-13 21:07:35 +00:00
|
|
|
|
[self disconnectLinkCable];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
[self performAtomicBlock:^{
|
2020-06-10 20:28:33 +00:00
|
|
|
|
accessory = GBAccessoryPrinter;
|
2017-01-13 20:26:44 +00:00
|
|
|
|
GB_connect_printer(&gb, printImage);
|
|
|
|
|
}];
|
2020-09-19 16:31:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)connectWorkboy:(id)sender
|
|
|
|
|
{
|
2020-11-13 21:07:35 +00:00
|
|
|
|
[self disconnectLinkCable];
|
2020-09-19 16:31:24 +00:00
|
|
|
|
[self performAtomicBlock:^{
|
|
|
|
|
accessory = GBAccessoryWorkboy;
|
|
|
|
|
GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime);
|
|
|
|
|
}];
|
2017-01-13 20:26:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 15:21:06 +00:00
|
|
|
|
- (void) updateVolume
|
|
|
|
|
{
|
|
|
|
|
_volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"];
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-15 18:59:53 +00:00
|
|
|
|
- (void) updateHighpassFilter
|
|
|
|
|
{
|
|
|
|
|
if (GB_is_inited(&gb)) {
|
|
|
|
|
GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-12 14:22:22 +00:00
|
|
|
|
- (void) updateColorCorrectionMode
|
|
|
|
|
{
|
|
|
|
|
if (GB_is_inited(&gb)) {
|
|
|
|
|
GB_set_color_correction_mode(&gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-25 12:14:17 +00:00
|
|
|
|
- (void) updateLightTemperature
|
|
|
|
|
{
|
|
|
|
|
if (GB_is_inited(&gb)) {
|
|
|
|
|
GB_set_light_temperature(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-30 22:06:36 +00:00
|
|
|
|
- (void) updateInterferenceVolume
|
|
|
|
|
{
|
|
|
|
|
if (GB_is_inited(&gb)) {
|
|
|
|
|
GB_set_interference_volume(&gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-25 12:14:17 +00:00
|
|
|
|
|
2020-03-26 18:54:18 +00:00
|
|
|
|
- (void) updateFrameBlendingMode
|
|
|
|
|
{
|
|
|
|
|
self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"];
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-10 12:42:14 +00:00
|
|
|
|
- (void) updateRewindLength
|
|
|
|
|
{
|
2018-06-23 08:50:08 +00:00
|
|
|
|
[self performAtomicBlock:^{
|
|
|
|
|
if (GB_is_inited(&gb)) {
|
|
|
|
|
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
|
|
|
|
|
}
|
|
|
|
|
}];
|
2018-02-10 12:42:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 20:12:14 +00:00
|
|
|
|
- (void) updateRTCMode
|
|
|
|
|
{
|
|
|
|
|
if (GB_is_inited(&gb)) {
|
|
|
|
|
GB_set_rtc_mode(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-01 15:16:50 +00:00
|
|
|
|
- (void)dmgModelChanged
|
|
|
|
|
{
|
|
|
|
|
modelsChanging = true;
|
|
|
|
|
if (current_model == MODEL_DMG) {
|
|
|
|
|
[self reset:nil];
|
|
|
|
|
}
|
|
|
|
|
modelsChanging = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)sgbModelChanged
|
|
|
|
|
{
|
|
|
|
|
modelsChanging = true;
|
|
|
|
|
if (current_model == MODEL_SGB) {
|
|
|
|
|
[self reset:nil];
|
|
|
|
|
}
|
|
|
|
|
modelsChanging = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)cgbModelChanged
|
|
|
|
|
{
|
|
|
|
|
modelsChanging = true;
|
|
|
|
|
if (current_model == MODEL_CGB) {
|
|
|
|
|
[self reset:nil];
|
|
|
|
|
}
|
|
|
|
|
modelsChanging = false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-15 14:57:59 +00:00
|
|
|
|
- (void)setFileURL:(NSURL *)fileURL
|
|
|
|
|
{
|
|
|
|
|
[super setFileURL:fileURL];
|
|
|
|
|
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[fileURL path] lastPathComponent]];
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 15:19:15 +00:00
|
|
|
|
- (BOOL)splitView:(GBSplitView *)splitView canCollapseSubview:(NSView *)subview;
|
2020-04-09 12:29:49 +00:00
|
|
|
|
{
|
|
|
|
|
if ([[splitView arrangedSubviews] lastObject] == subview) {
|
2021-10-23 10:36:58 +00:00
|
|
|
|
return true;
|
2020-04-09 12:29:49 +00:00
|
|
|
|
}
|
2021-10-23 10:36:58 +00:00
|
|
|
|
return false;
|
2020-04-09 12:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 15:19:15 +00:00
|
|
|
|
- (CGFloat)splitView:(GBSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex
|
2020-04-09 12:29:49 +00:00
|
|
|
|
{
|
|
|
|
|
return 600;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 17:37:57 +00:00
|
|
|
|
- (CGFloat)splitView:(GBSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex
|
|
|
|
|
{
|
2020-04-09 12:29:49 +00:00
|
|
|
|
return splitView.frame.size.width - 321;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-24 17:37:57 +00:00
|
|
|
|
- (BOOL)splitView:(GBSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view
|
|
|
|
|
{
|
2020-04-09 12:29:49 +00:00
|
|
|
|
if ([[splitView arrangedSubviews] lastObject] == view) {
|
2021-10-23 10:36:58 +00:00
|
|
|
|
return false;
|
2020-04-09 12:29:49 +00:00
|
|
|
|
}
|
2021-10-23 10:36:58 +00:00
|
|
|
|
return true;
|
2020-04-09 12:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)splitViewDidResizeSubviews:(NSNotification *)notification
|
|
|
|
|
{
|
2020-04-11 15:19:15 +00:00
|
|
|
|
GBSplitView *splitview = notification.object;
|
2020-04-09 12:29:49 +00:00
|
|
|
|
if ([[[splitview arrangedSubviews] firstObject] frame].size.width < 600) {
|
|
|
|
|
[splitview setPosition:600 ofDividerAtIndex:0];
|
|
|
|
|
}
|
|
|
|
|
/* NSSplitView renders its separator without the proper vibrancy, so we made it transparent and move an
|
|
|
|
|
NSBox-based separator that renders properly so it acts like the split view's separator. */
|
|
|
|
|
NSRect rect = self.debuggerVerticalLine.frame;
|
|
|
|
|
rect.origin.x = [[[splitview arrangedSubviews] firstObject] frame].size.width - 1;
|
|
|
|
|
self.debuggerVerticalLine.frame = rect;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 15:03:10 +00:00
|
|
|
|
- (IBAction)showCheats:(id)sender
|
|
|
|
|
{
|
|
|
|
|
[self.cheatsWindow makeKeyAndOrderFront:nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)toggleCheats:(id)sender
|
|
|
|
|
{
|
|
|
|
|
GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb));
|
|
|
|
|
}
|
2020-11-13 21:07:35 +00:00
|
|
|
|
|
|
|
|
|
- (void)disconnectLinkCable
|
|
|
|
|
{
|
|
|
|
|
bool wasRunning = self->running;
|
|
|
|
|
Document *partner = master ?: slave;
|
|
|
|
|
if (partner) {
|
2022-02-25 12:29:21 +00:00
|
|
|
|
wasRunning |= partner->running;
|
2020-11-13 21:07:35 +00:00
|
|
|
|
[self stop];
|
|
|
|
|
partner->master = nil;
|
|
|
|
|
partner->slave = nil;
|
|
|
|
|
master = nil;
|
|
|
|
|
slave = nil;
|
|
|
|
|
if (wasRunning) {
|
|
|
|
|
[partner start];
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
GB_set_turbo_mode(&gb, false, false);
|
|
|
|
|
GB_set_turbo_mode(&partner->gb, false, false);
|
|
|
|
|
partner->accessory = GBAccessoryNone;
|
|
|
|
|
accessory = GBAccessoryNone;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)connectLinkCable:(NSMenuItem *)sender
|
|
|
|
|
{
|
|
|
|
|
[self disconnectAllAccessories:sender];
|
|
|
|
|
Document *partner = [sender representedObject];
|
|
|
|
|
[partner disconnectAllAccessories:sender];
|
|
|
|
|
|
|
|
|
|
bool wasRunning = self->running;
|
|
|
|
|
[self stop];
|
|
|
|
|
[partner stop];
|
|
|
|
|
GB_set_turbo_mode(&partner->gb, true, true);
|
|
|
|
|
slave = partner;
|
|
|
|
|
partner->master = self;
|
|
|
|
|
linkOffset = 0;
|
|
|
|
|
partner->accessory = GBAccessoryLinkCable;
|
|
|
|
|
accessory = GBAccessoryLinkCable;
|
|
|
|
|
GB_set_serial_transfer_bit_start_callback(&gb, linkCableBitStart);
|
|
|
|
|
GB_set_serial_transfer_bit_start_callback(&partner->gb, linkCableBitStart);
|
|
|
|
|
GB_set_serial_transfer_bit_end_callback(&gb, linkCableBitEnd);
|
|
|
|
|
GB_set_serial_transfer_bit_end_callback(&partner->gb, linkCableBitEnd);
|
|
|
|
|
if (wasRunning) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)linkCableBitStart:(bool)bit
|
|
|
|
|
{
|
|
|
|
|
linkCableBit = bit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-(bool)linkCableBitEnd
|
|
|
|
|
{
|
|
|
|
|
bool ret = GB_serial_get_data_bit(&self.partner->gb);
|
|
|
|
|
GB_serial_set_data_bit(&self.partner->gb, linkCableBit);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)infraredStateChanged:(bool)state
|
|
|
|
|
{
|
|
|
|
|
if (self.partner) {
|
|
|
|
|
GB_set_infrared_input(&self.partner->gb, state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-(Document *)partner
|
|
|
|
|
{
|
|
|
|
|
return slave ?: master;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (bool)isSlave
|
|
|
|
|
{
|
|
|
|
|
return master;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (GB_gameboy_t *)gb
|
|
|
|
|
{
|
|
|
|
|
return &gb;
|
|
|
|
|
}
|
2021-10-23 10:26:44 +00:00
|
|
|
|
|
|
|
|
|
- (NSImage *)takeScreenshot
|
|
|
|
|
{
|
|
|
|
|
NSImage *ret = nil;
|
|
|
|
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBFilterScreenshots"]) {
|
|
|
|
|
ret = [_view renderToImage];
|
2021-12-26 16:38:08 +00:00
|
|
|
|
[ret lockFocus];
|
|
|
|
|
NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0,
|
|
|
|
|
ret.size.width, ret.size.height)];
|
|
|
|
|
[ret unlockFocus];
|
|
|
|
|
ret = [[NSImage alloc] initWithSize:ret.size];
|
|
|
|
|
[ret addRepresentation:bitmapRep];
|
2021-10-23 10:26:44 +00:00
|
|
|
|
}
|
|
|
|
|
if (!ret) {
|
|
|
|
|
ret = [Document imageFromData:[NSData dataWithBytesNoCopy:_view.currentBuffer
|
|
|
|
|
length:GB_get_screen_width(&gb) * GB_get_screen_height(&gb) * 4
|
|
|
|
|
freeWhenDone:false]
|
|
|
|
|
width:GB_get_screen_width(&gb)
|
|
|
|
|
height:GB_get_screen_height(&gb)
|
|
|
|
|
scale:1.0];
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)screenshotFilename
|
|
|
|
|
{
|
|
|
|
|
NSDate *date = [NSDate date];
|
|
|
|
|
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
|
|
|
|
dateFormatter.dateStyle = NSDateFormatterLongStyle;
|
|
|
|
|
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
|
|
|
|
|
return [[NSString stringWithFormat:@"%@ – %@.png",
|
|
|
|
|
self.fileURL.lastPathComponent.stringByDeletingPathExtension,
|
|
|
|
|
[dateFormatter stringFromDate:date]] stringByReplacingOccurrencesOfString:@":" withString:@"."]; // Gotta love Mac OS Classic
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)saveScreenshot:(id)sender
|
|
|
|
|
{
|
|
|
|
|
NSString *folder = [[NSUserDefaults standardUserDefaults] stringForKey:@"GBScreenshotFolder"];
|
|
|
|
|
BOOL isDirectory = false;
|
|
|
|
|
if (folder) {
|
|
|
|
|
[[NSFileManager defaultManager] fileExistsAtPath:folder isDirectory:&isDirectory];
|
|
|
|
|
}
|
|
|
|
|
if (!folder) {
|
|
|
|
|
bool shouldResume = running;
|
|
|
|
|
[self stop];
|
|
|
|
|
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
|
|
|
|
openPanel.canChooseFiles = false;
|
|
|
|
|
openPanel.canChooseDirectories = true;
|
|
|
|
|
openPanel.message = @"Choose a folder for screenshots";
|
|
|
|
|
[openPanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
|
|
|
|
|
if (result == NSModalResponseOK) {
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:openPanel.URL.path
|
|
|
|
|
forKey:@"GBScreenshotFolder"];
|
|
|
|
|
[self saveScreenshot:sender];
|
|
|
|
|
}
|
|
|
|
|
if (shouldResume) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
NSImage *image = [self takeScreenshot];
|
|
|
|
|
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
|
|
|
|
dateFormatter.dateStyle = NSDateFormatterLongStyle;
|
|
|
|
|
dateFormatter.timeStyle = NSDateFormatterMediumStyle;
|
|
|
|
|
NSString *filename = [self screenshotFilename];
|
|
|
|
|
filename = [folder stringByAppendingPathComponent:filename];
|
|
|
|
|
unsigned i = 2;
|
|
|
|
|
while ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
|
|
|
|
|
filename = [[filename stringByDeletingPathExtension] stringByAppendingFormat:@" %d.png", i++];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject;
|
|
|
|
|
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[data writeToFile:filename atomically:false];
|
2021-10-23 10:26:44 +00:00
|
|
|
|
[self.osdView displayText:@"Screenshot saved"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)saveScreenshotAs:(id)sender
|
|
|
|
|
{
|
|
|
|
|
bool shouldResume = running;
|
|
|
|
|
[self stop];
|
|
|
|
|
NSImage *image = [self takeScreenshot];
|
|
|
|
|
NSSavePanel *savePanel = [NSSavePanel savePanel];
|
|
|
|
|
[savePanel setNameFieldStringValue:[self screenshotFilename]];
|
|
|
|
|
[savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
|
|
|
|
|
if (result == NSModalResponseOK) {
|
|
|
|
|
[savePanel orderOut:self];
|
|
|
|
|
NSBitmapImageRep *imageRep = (NSBitmapImageRep *)image.representations.firstObject;
|
|
|
|
|
NSData *data = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
|
2021-10-23 10:36:58 +00:00
|
|
|
|
[data writeToURL:savePanel.URL atomically:false];
|
2021-10-23 10:26:44 +00:00
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:savePanel.URL.path.stringByDeletingLastPathComponent
|
|
|
|
|
forKey:@"GBScreenshotFolder"];
|
|
|
|
|
}
|
|
|
|
|
if (shouldResume) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
[self.osdView displayText:@"Screenshot saved"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)copyScreenshot:(id)sender
|
|
|
|
|
{
|
|
|
|
|
NSImage *image = [self takeScreenshot];
|
|
|
|
|
[[NSPasteboard generalPasteboard] clearContents];
|
|
|
|
|
[[NSPasteboard generalPasteboard] writeObjects:@[image]];
|
|
|
|
|
[self.osdView displayText:@"Screenshot copied"];
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-17 19:12:26 +00:00
|
|
|
|
- (IBAction)toggleDisplayBackground:(id)sender
|
|
|
|
|
{
|
|
|
|
|
GB_set_background_rendering_disabled(&gb, !GB_is_background_rendering_disabled(&gb));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)toggleDisplayObjects:(id)sender
|
|
|
|
|
{
|
|
|
|
|
GB_set_object_rendering_disabled(&gb, !GB_is_object_rendering_disabled(&gb));
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-25 23:47:47 +00:00
|
|
|
|
- (IBAction)newCartridgeInstance:(id)sender
|
|
|
|
|
{
|
|
|
|
|
bool shouldResume = running;
|
|
|
|
|
[self stop];
|
|
|
|
|
NSSavePanel *savePanel = [NSSavePanel savePanel];
|
|
|
|
|
[savePanel setAllowedFileTypes:@[@"gbcart"]];
|
|
|
|
|
[savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
|
|
|
|
|
if (result == NSModalResponseOK) {
|
|
|
|
|
[savePanel orderOut:self];
|
|
|
|
|
NSString *romPath = self.romPath;
|
|
|
|
|
[[NSFileManager defaultManager] trashItemAtURL:savePanel.URL resultingItemURL:nil error:nil];
|
|
|
|
|
[[NSFileManager defaultManager] createDirectoryAtURL:savePanel.URL withIntermediateDirectories:false attributes:nil error:nil];
|
|
|
|
|
[[NSString stringWithFormat:@"%@\n%@\n%@",
|
|
|
|
|
[romPath pathRelativeToDirectory:savePanel.URL.path],
|
|
|
|
|
romPath,
|
|
|
|
|
[[NSURL fileURLWithPath:romPath].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]
|
|
|
|
|
] writeToURL:[savePanel.URL URLByAppendingPathComponent:@"rom.gbl"] atomically:false encoding:NSUTF8StringEncoding error:nil];
|
|
|
|
|
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:savePanel.URL display:true completionHandler:nil];
|
|
|
|
|
}
|
|
|
|
|
if (shouldResume) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 23:17:59 +00:00
|
|
|
|
- (IBAction)toggleAudioRecording:(id)sender
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
bool shouldResume = running;
|
|
|
|
|
[self stop];
|
|
|
|
|
if (_isRecordingAudio) {
|
|
|
|
|
_isRecordingAudio = false;
|
|
|
|
|
int error = GB_stop_audio_recording(&gb);
|
|
|
|
|
if (error) {
|
|
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
|
|
|
[alert setMessageText:[NSString stringWithFormat:@"Could not finalize recording: %s", strerror(error)]];
|
|
|
|
|
[alert setAlertStyle:NSAlertStyleCritical];
|
|
|
|
|
[alert runModal];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[self.osdView displayText:@"Audio recording ended"];
|
|
|
|
|
}
|
|
|
|
|
if (shouldResume) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_audioSavePanel = [NSSavePanel savePanel];
|
|
|
|
|
if (!self.audioRecordingAccessoryView) {
|
|
|
|
|
[[NSBundle mainBundle] loadNibNamed:@"AudioRecordingAccessoryView" owner:self topLevelObjects:nil];
|
|
|
|
|
}
|
|
|
|
|
_audioSavePanel.accessoryView = self.audioRecordingAccessoryView;
|
|
|
|
|
[self audioFormatChanged:self.audioFormatButton];
|
|
|
|
|
|
|
|
|
|
[_audioSavePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
|
|
|
|
|
if (result == NSModalResponseOK) {
|
|
|
|
|
[_audioSavePanel orderOut:self];
|
|
|
|
|
int error = GB_start_audio_recording(&gb, _audioSavePanel.URL.fileSystemRepresentation, self.audioFormatButton.selectedTag);
|
|
|
|
|
if (error) {
|
|
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
|
|
|
[alert setMessageText:[NSString stringWithFormat:@"Could not start recording: %s", strerror(error)]];
|
|
|
|
|
[alert setAlertStyle:NSAlertStyleCritical];
|
|
|
|
|
[alert runModal];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
[self.osdView displayText:@"Audio recording started"];
|
|
|
|
|
_isRecordingAudio = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (shouldResume) {
|
|
|
|
|
[self start];
|
|
|
|
|
}
|
|
|
|
|
_audioSavePanel = nil;
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)audioFormatChanged:(NSPopUpButton *)sender
|
|
|
|
|
{
|
|
|
|
|
switch ((GB_audio_format_t)sender.selectedTag) {
|
|
|
|
|
case GB_AUDIO_FORMAT_RAW:
|
|
|
|
|
_audioSavePanel.allowedFileTypes = @[@"raw", @"pcm"];
|
|
|
|
|
break;
|
|
|
|
|
case GB_AUDIO_FORMAT_AIFF:
|
|
|
|
|
_audioSavePanel.allowedFileTypes = @[@"aiff", @"aif", @"aifc"];
|
|
|
|
|
break;
|
|
|
|
|
case GB_AUDIO_FORMAT_WAV:
|
|
|
|
|
_audioSavePanel.allowedFileTypes = @[@"wav"];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-25 23:47:47 +00:00
|
|
|
|
|
2017-02-20 12:37:15 +00:00
|
|
|
|
@end
|