SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 7cae71d09814348337a504541862ba349bd2bf6c parent 18afec6c918dc62d6b66d4b5e891fb27ea19a193 Author: Lior Halphon <LIJI32@gmail.com> Date: Sat, 28 Feb 2026 00:12:06 +0200 Add a screenshot hotkey to the SDL port, closes #364 Diffstat:
| M | Makefile | 22 | ++++++++++++++++++++-- |
| M | SDL/gui.c | 11 | ++++++++--- |
| M | SDL/main.c | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
| A | SDL/save_png/appkit.m | 29 | +++++++++++++++++++++++++++++ |
| A | SDL/save_png/libpng.c | 54 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | SDL/save_png/save_png.h | 5 | +++++ |
| A | SDL/save_png/windows.c | 88 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | Windows/stdio.h | 1 | + |
8 files changed, 259 insertions(+), 11 deletions(-)
diff --git a/Makefile b/Makefile @@ -180,13 +180,19 @@ RGBGFX_FLAGS := $(if $(filter $(shell echo 'println __RGBDS_MAJOR__ || (!__RGBDS # Set compilation and linkage flags based on target, platform and configuration OPEN_DIALOG = SDL/open_dialog/gtk.c +SAVE_PNG = SDL/save_png/libpng.c +NEED_LIBPNG = 1 ifeq ($(PLATFORM),windows32) OPEN_DIALOG = SDL/open_dialog/windows.c +SAVE_PNG = SDL/save_png/windows.c +NEED_LIBPNG = 0 endif ifeq ($(PLATFORM),Darwin) OPEN_DIALOG = SDL/open_dialog/cocoa.m +SAVE_PNG = SDL/save_png/appkit.m +NEED_LIBPNG = 0 endif # These must come before the -Wno- flags @@ -271,15 +277,22 @@ endif ifeq (,$(PKG_CONFIG)) GL_LDFLAGS := -lGL +ifeq ($(NEED_LIBPNG),1) +SDL_LDFLAGS += -lpng +endif else GL_CFLAGS := $(shell $(PKG_CONFIG) --cflags gl) GL_LDFLAGS := $(shell $(PKG_CONFIG) --libs gl || echo -lGL) +ifeq ($(NEED_LIBPNG),1) +SDL_LDFLAGS += $(shell $(PKG_CONFIG) --cflags libpng) +SDL_CFLAGS += $(shell $(PKG_CONFIG) --libs libpng || echo -lpng) +endif endif ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows -Drandom=rand --target=x86_64-pc-windows LDFLAGS += -lmsvcrt -lkernel32 -Wl,/MANIFESTFILE:NUL --target=x86_64-pc-windows -SDL_LDFLAGS := -lSDL2 -lcomdlg32 -luser32 -lshell32 -lole32 -ladvapi32 -ldwmapi -lSDL2main +SDL_LDFLAGS := -lSDL2 -lcomdlg32 -luser32 -lshell32 -lole32 -ladvapi32 -ldwmapi -lwindowscodecs -lSDL2main GL_LDFLAGS := -lopengl32 ifneq ($(REDIST_XAUDIO),) CFLAGS += -DREDIST_XAUDIO @@ -417,7 +430,7 @@ endif CORE_SOURCES := $(filter-out $(CORE_FILTER),$(shell ls Core/*.c)) CORE_HEADERS := $(shell ls Core/*.h) -SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) $(patsubst %,SDL/audio/%.c,$(SDL_AUDIO_DRIVERS)) +SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) $(SAVE_PNG) $(patsubst %,SDL/audio/%.c,$(SDL_AUDIO_DRIVERS)) TESTER_SOURCES := $(shell ls Tester/*.c) IOS_SOURCES := $(filter-out iOS/installer.m, $(shell ls iOS/*.m)) $(shell ls AppleCommon/*.m) COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) $(shell ls JoyKit/*.m) $(shell ls AppleCommon/*.m) @@ -510,6 +523,11 @@ $(OBJ)/HexFiend/%.m.o: HexFiend/%.m -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch +# Apple-specific code in SDL +$(OBJ)/SDL/%.m.o: SDL/%.m + -@$(MKDIR) -p $(dir $@) + $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(OCFLAGS) $(SDL_CFLAGS) -c $< -o $@ + $(OBJ)/%.m.o: %.m -@$(MKDIR) -p $(dir $@) $(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ diff --git a/SDL/gui.c b/SDL/gui.c @@ -81,21 +81,26 @@ void render_texture(void *pixels, void *previous) } static const char *help[] = { +// Shortcuts "Keyboard Shortcuts:\n" -" Open Menu: Escape\n" +" Open menu: Escape\n" " Open ROM: " MODIFIER_NAME "+O\n" " Reset: " MODIFIER_NAME "+R\n" " Pause: " MODIFIER_NAME "+P\n" " Save state: " MODIFIER_NAME "+(0-9)\n" " Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n" -" Toggle Fullscreen " MODIFIER_NAME "+F\n" +" Toggle fullscreen " MODIFIER_NAME "+F\n" #ifdef __APPLE__ " Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n" +" Save screenshot: " MODIFIER_NAME "+S\n" #else " Mute/Unmute: " MODIFIER_NAME "+M\n" +" Save screenshot: F12\n" #endif " Toggle channel: " ALT_STRING "+(1-4)\n" -" Break Debugger: " CTRL_STRING "+C", +" Break debugger: " CTRL_STRING "+C", + +// About "\n" "SameBoy\n" "Version " GB_VERSION "\n\n" diff --git a/SDL/main.c b/SDL/main.c @@ -14,6 +14,7 @@ #include "shader.h" #include "audio/audio.h" #include "console.h" +#include "save_png/save_png.h" #ifdef _WIN32 #include <Windows.h> @@ -29,6 +30,7 @@ static bool underclock_down = false, rewind_down = false, do_rewind = false, rew static bool rapid_a = false, rapid_b = false; static uint8_t rapid_a_count = 0, rapid_b_count = 0; static double clock_mutliplier = 1.0; +static bool pending_screenshot = false; char *filename = NULL; static typeof(free) *free_function = NULL; @@ -305,6 +307,32 @@ static void configure_console(void) GB_set_async_input_callback(&gb, async_input_callback); } +static void save_screenshot(void) +{ + static char png_path[PATH_MAX] = {0,}; + time_t now = time(NULL); + struct tm *local = localtime(&now); + char timestring[20]; + strftime(timestring, sizeof(timestring), "%Y-%m-%d %H.%M.%S", local); + + replace_extension(filename, strlen(filename), png_path, ""); + snprintf(png_path + strlen(png_path), sizeof(png_path) - 1 - strlen(png_path), " %s.png", timestring); + + unsigned i = 2; + while (access(png_path, F_OK) == 0) { + replace_extension(filename, strlen(filename), png_path, ""); + snprintf(png_path + strlen(png_path), sizeof(png_path) - 1 - strlen(png_path), " %s %u.png", timestring, i); + i++; + } + + if (save_png(png_path, GB_get_screen_width(&gb), GB_get_screen_height(&gb), active_pixel_buffer, pixel_format)) { + show_osd_text("Screenshot saved"); + } + else { + show_osd_text("Failed to save screenshot"); + } +} + static void handle_events(GB_gameboy_t *gb) { SDL_Event event; @@ -567,6 +595,19 @@ static void handle_events(GB_gameboy_t *gb) GB_audio_set_paused(GB_audio_is_playing()); } break; +#ifdef __APPLE__ + case SDL_SCANCODE_S: + if (!(event.key.keysym.mod & MODIFIER)) break; +#else + case SDL_SCANCODE_F12: +#endif + if (osd_countdown && configuration.osd) { + pending_screenshot = true; + break; + } + + save_screenshot(); + break; case SDL_SCANCODE_F: if (event.key.keysym.mod & MODIFIER) { @@ -688,13 +729,20 @@ static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) show_osd_text("Rewinding..."); } + if (pending_screenshot) { + pending_screenshot = false; + save_screenshot(); + } + if (osd_countdown && configuration.osd) { - unsigned width = GB_get_screen_width(gb); - unsigned height = GB_get_screen_height(gb); - draw_text(active_pixel_buffer, - width, height, 8, height - 8 - osd_text_lines * 12, osd_text, - rgb_encode(gb, 255, 255, 255), rgb_encode(gb, 0, 0, 0), - true); + if (osd_countdown != 1) { + unsigned width = GB_get_screen_width(gb); + unsigned height = GB_get_screen_height(gb); + draw_text(active_pixel_buffer, + width, height, 8, height - 8 - osd_text_lines * 12, osd_text, + rgb_encode(gb, 255, 255, 255), rgb_encode(gb, 0, 0, 0), + true); + } osd_countdown--; } if (type != GB_VBLANK_TYPE_REPEAT) { diff --git a/SDL/save_png/appkit.m b/SDL/save_png/appkit.m @@ -0,0 +1,29 @@ +#import <AppKit/AppKit.h> +#import <SDL.h> +#import "save_png.h" + +bool save_png(const char *filename, uint32_t width, uint32_t height, const void *pixels, SDL_PixelFormat *pixel_format) +{ + if (pixel_format->format != SDL_PIXELFORMAT_ABGR8888 && + pixel_format->format != SDL_PIXELFORMAT_XBGR8888) { + return false; + } + size_t stride = width * 4; + + @autoreleasepool { + 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, pixels, stride * height); + NSData *data = [rep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; + return [data writeToFile:@(filename) atomically:true]; + } +} diff --git a/SDL/save_png/libpng.c b/SDL/save_png/libpng.c @@ -0,0 +1,54 @@ +#include <png.h> +#include <SDL.h> +#include <stdbool.h> +#include "save_png.h" + +bool save_png(const char *filename, uint32_t width, uint32_t height, const void *pixels, SDL_PixelFormat *pixel_format) +{ + FILE *f = fopen(filename, "wb"); + if (!f) return false; + + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_infop info = png_create_info_struct(png); + uint8_t *row = malloc(width * 3); + + if (setjmp(png_jmpbuf(png))) { + png_destroy_write_struct(&png, &info); + fclose(f); + free(row); + return false; + } + png_init_io(png, f); + + png_set_IHDR(png, + info, + width, + height, + 8, + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + png_write_info(png, info); + + for (uint32_t y = 0; y < height; y++) { + const uint32_t *src = (uint32_t *)pixels + y * width; + uint8_t *dest = row; + + for (uint32_t x = 0; x < width; x++) { + uint8_t dummy; + SDL_GetRGBA(*(src++), pixel_format, &dest[0], &dest[1], &dest[2], &dummy); + dest += 3; + } + + png_write_row(png, row); + } + + png_write_end(png, NULL); + png_destroy_write_struct(&png, &info); + fclose(f); + + return true; + +} diff --git a/SDL/save_png/save_png.h b/SDL/save_png/save_png.h @@ -0,0 +1,5 @@ +#include <stdint.h> +#include <stdbool.h> +#include <SDL.h> + +bool save_png(const char *filename, uint32_t width, uint32_t height, const void *pixels, SDL_PixelFormat *pixel_format); diff --git a/SDL/save_png/windows.c b/SDL/save_png/windows.c @@ -0,0 +1,88 @@ +#define COBJMACROS +#include "save_png.h" +#include <windows.h> +#include <wincodec.h> + +static wchar_t *utf8_to_wide(const char *utf8) +{ + int length = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); + if (length <= 0) { + return NULL; + } + + wchar_t *ret = malloc(length * sizeof(*ret)); + if (!ret) { + return NULL; + } + + if (!MultiByteToWideChar(CP_UTF8, 0, utf8, -1, ret, length)) { + free(ret); + return NULL; + } + + return ret; +} + +bool save_png(const char *filename, uint32_t width, uint32_t height, const void *pixels, SDL_PixelFormat *pixel_format) +{ + bool success = false; + wchar_t *wfilename = utf8_to_wide(filename); + + IWICImagingFactory *factory = NULL; + IWICStream *stream = NULL; + IWICBitmapEncoder *encoder = NULL; + IWICBitmapFrameEncode *frame = NULL; + uint8_t *row = malloc(width * 3); + + CoInitialize(NULL); + + + if (CoCreateInstance(&CLSID_WICImagingFactory, + NULL, + CLSCTX_INPROC_SERVER, + &IID_IWICImagingFactory, + (void **)&factory)) { + goto done; + } + + if (IWICImagingFactory_CreateStream(factory, &stream)) goto done; + if (IWICStream_InitializeFromFilename(stream, wfilename, GENERIC_WRITE)) goto done; + if (IWICImagingFactory_CreateEncoder(factory, &GUID_ContainerFormatPng, NULL, &encoder)) goto done; + if (IWICBitmapEncoder_Initialize(encoder, (IStream *)stream, WICBitmapEncoderNoCache)) goto done; + if (IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL)) goto done; + if (IWICBitmapFrameEncode_Initialize(frame, NULL)) goto done; + if (IWICBitmapFrameEncode_SetSize(frame, width, height)) goto done; + + WICPixelFormatGUID pixel_format_guid = GUID_WICPixelFormat24bppRGB; + if (IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format_guid)) goto done; + + for (uint32_t y = 0; y < height; y++) { + const uint32_t *src = (uint32_t *)pixels + y * width; + uint8_t *dest = row; + + for (uint32_t x = 0; x < width; x++) { + uint8_t dummy; + SDL_GetRGBA(*(src++), pixel_format, &dest[2], &dest[1], &dest[0], &dummy); + dest += 3; + } + + if (IWICBitmapFrameEncode_WritePixels(frame, 1, width * 3, width * 3, (void *)row)) goto done; + } + + if (IWICBitmapFrameEncode_Commit(frame)) goto done; + if (IWICBitmapEncoder_Commit(encoder)) goto done; + + success = true; + +done: + if (frame) IWICBitmapFrameEncode_Release(frame); + if (encoder) IWICBitmapEncoder_Release(encoder); + if (stream) IWICStream_Release(stream); + if (factory) IWICImagingFactory_Release(factory); + + CoUninitialize(); + free(wfilename); + free(row); + + return success; +} diff --git a/Windows/stdio.h b/Windows/stdio.h @@ -18,6 +18,7 @@ int access(const char *filename, int mode); #define R_OK 4 #define W_OK 2 +#define F_OK 0 #ifndef __MINGW32__ #ifndef __LIBRETRO__
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.