From 8a84a5897e5cd44b6fd0c1baa92cc48d7eddbe00 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 14 Apr 2021 15:20:01 +0300 Subject: [PATCH] Allow drag&drop of state files --- Cocoa/Document.h | 1 + Cocoa/Document.m | 10 ++++++++-- Cocoa/GBView.m | 27 +++++++++++++++++++++++++++ Core/gb.c | 6 ------ Core/save_state.c | 33 +++++++++++++++++++++++++++++++++ Core/save_state.h | 8 +++++++- SDL/gui.c | 13 ++++++++++--- SDL/gui.h | 2 ++ SDL/main.c | 17 +++++++++++++++-- 9 files changed, 103 insertions(+), 14 deletions(-) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index b651646..6effe48 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -44,5 +44,6 @@ -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) performAtomicBlock: (void (^)())block; -(void) connectLinkCable:(NSMenuItem *)sender; +- (bool)loadStateFile:(const char *)path; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index e7812d4..1d9072b 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1172,12 +1172,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } } -- (IBAction)loadState:(id)sender +- (bool)loadStateFile:(const char *)path { bool __block success = false; NSString *error = [self captureOutputForBlock:^{ - success = GB_load_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0; + success = GB_load_state(&gb, path) == 0; }]; if (!success) { @@ -1186,6 +1186,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) if (error) { [GBWarningPopover popoverWithContents:error onWindow:self.mainWindow]; } + return success; +} + +- (IBAction)loadState:(id)sender +{ + [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String]; } - (IBAction)clearConsole:(id)sender diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 0d834c0..645544f 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -142,6 +142,8 @@ static const uint8_t workboy_vk_to_key[] = { - (void) _init { + [self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeFileURL, nil]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect @@ -626,4 +628,29 @@ static const uint8_t workboy_vk_to_key[] = { return image_buffers[(current_buffer + 2) % self.numberOfBuffers]; } +-(NSDragOperation)draggingEntered:(id)sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + + if ( [[pboard types] containsObject:NSURLPboardType] ) { + NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; + if (GB_is_stave_state(fileURL.fileSystemRepresentation)) { + return NSDragOperationGeneric; + } + } + return NSDragOperationNone; +} + +-(BOOL)performDragOperation:(id)sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + + if ( [[pboard types] containsObject:NSURLPboardType] ) { + NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; + return [_document loadStateFile:fileURL.fileSystemRepresentation]; + } + + return false; +} + @end diff --git a/Core/gb.c b/Core/gb.c index 8816552..39a265c 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -19,12 +19,6 @@ #endif -static inline uint32_t state_magic(void) -{ - if (sizeof(bool) == 1) return 'SAME'; - return 'S4ME'; -} - void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { char *string = NULL; diff --git a/Core/save_state.c b/Core/save_state.c index 6ef62c1..17932ee 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -1170,3 +1170,36 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return load_state_internal(gb, &file); } + + +bool GB_is_stave_state(const char *path) +{ + bool ret = false; + FILE *f = fopen(path, "rb"); + if (!f) return false; + uint32_t magic = 0; + fread(&magic, sizeof(magic), 1, f); + if (magic == state_magic()) { + ret = true; + goto exit; + } + + // Legacy corrupted Windows save state + if (magic == 0) { + fread(&magic, sizeof(magic), 1, f); + if (magic == state_magic()) { + ret = true; + goto exit; + } + } + + fseek(f, -sizeof(magic), SEEK_END); + fread(&magic, sizeof(magic), 1, f); + if (magic == BE32('BESS')) { + ret = true; + } + +exit: + fclose(f); + return ret; +} diff --git a/Core/save_state.h b/Core/save_state.h index 0c447d9..79e8c06 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -27,8 +27,14 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); - +bool GB_is_stave_state(const char *path); #ifdef GB_INTERNAL +static inline uint32_t state_magic(void) +{ + if (sizeof(bool) == 1) return 'SAME'; + return 'S4ME'; +} + /* For internal in-memory save states (rewind, debugger) that do not need BESS */ size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb); void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); diff --git a/SDL/gui.c b/SDL/gui.c index 580a3f6..f872af5 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -18,6 +18,7 @@ SDL_Texture *texture = NULL; SDL_PixelFormat *pixel_format = NULL; enum pending_command pending_command; unsigned command_parameter; +char *dropped_state_file = NULL; #ifdef __APPLE__ #define MODIFIER_NAME " " CMD_STRING @@ -1300,9 +1301,15 @@ void run_gui(bool is_running) break; } case SDL_DROPFILE: { - set_filename(event.drop.file, SDL_free); - pending_command = GB_SDL_NEW_FILE_COMMAND; - return; + if (GB_is_stave_state(event.drop.file)) { + dropped_state_file = event.drop.file; + pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; + } + else { + set_filename(event.drop.file, SDL_free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + return; + } } case SDL_JOYBUTTONDOWN: { diff --git a/SDL/gui.h b/SDL/gui.h index 5db7aff..baa6789 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -39,12 +39,14 @@ enum pending_command { GB_SDL_RESET_COMMAND, GB_SDL_NEW_FILE_COMMAND, GB_SDL_QUIT_COMMAND, + GB_SDL_LOAD_STATE_FROM_FILE_COMMAND, }; #define GB_SDL_DEFAULT_SCALE_MAX 8 extern enum pending_command pending_command; extern unsigned command_parameter; +extern char *dropped_state_file; typedef enum { JOYPAD_BUTTON_LEFT, diff --git a/SDL/main.c b/SDL/main.c index d10590d..fa74a6a 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -142,8 +142,14 @@ static void handle_events(GB_gameboy_t *gb) break; case SDL_DROPFILE: { - set_filename(event.drop.file, SDL_free); - pending_command = GB_SDL_NEW_FILE_COMMAND; + if (GB_is_stave_state(event.drop.file)) { + dropped_state_file = event.drop.file; + pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; + } + else { + set_filename(event.drop.file, SDL_free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + } break; } @@ -433,6 +439,13 @@ static bool handle_pending_command(void) end_capturing_logs(true, false); return false; } + + case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND: + start_capturing_logs(); + GB_load_state(&gb, dropped_state_file); + end_capturing_logs(true, false); + SDL_free(dropped_state_file); + return false; case GB_SDL_NO_COMMAND: return false;