/* * sdl12shim.c - see sdl12shim.h. * * Written for fredygames.com to build Kobo Deluxe for the browser with Emscripten's SDL2 port. * Copyright (C) 2026 FredyGames. Same license as Kobo Deluxe: GNU GPL version 2 or later. */ #include "sdl12shim.h" /* The game sees SDL 1.2's SDL_SetColorKey and SDL_OpenAudio; here they are SDL2's again. */ #undef SDL_SetColorKey #undef SDL_OpenAudio #ifdef __EMSCRIPTEN__ #include #endif static SDL_Window *window = NULL; static SDL_Renderer *renderer = NULL; static SDL_Texture *texture = NULL; static SDL_Surface *screen = NULL; static SDL_GrabMode grab = SDL_GRAB_OFF; static int key_repeat = 1; /* SDL 1.2 only repeats keys when the game asks for it. */ static int SDLCALL filter_events(void *userdata, SDL_Event *ev) { (void)userdata; if(ev->type == SDL_KEYDOWN && ev->key.repeat && !key_repeat) return 0; return 1; } SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) { (void)bpp; (void)flags; if(screen && screen->w == width && screen->h == height) return screen; if(texture) SDL_DestroyTexture(texture); if(screen) SDL_FreeSurface(screen); texture = NULL; screen = NULL; if(!window) { window = SDL_CreateWindow("Kobo Deluxe", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0); if(!window) return NULL; renderer = SDL_CreateRenderer(window, -1, 0); if(!renderer) return NULL; SDL_SetEventFilter(filter_events, NULL); } else SDL_SetWindowSize(window, width, height); SDL_RenderSetLogicalSize(renderer, width, height); texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, width, height); screen = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_RGB888); if(!texture || !screen) return NULL; return screen; } SDL_Surface *SDL_GetVideoSurface(void) { return screen; } static void present(void) { if(!screen) return; SDL_UpdateTexture(texture, NULL, screen->pixels, screen->pitch); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); #ifdef __EMSCRIPTEN__ /* Hand the frame to the browser: the game runs its own loop (built with Asyncify). */ emscripten_sleep(0); #endif } int SDL_Flip(SDL_Surface *s) { (void)s; present(); return 0; } void SDL_UpdateRects(SDL_Surface *s, int numrects, SDL_Rect *rects) { (void)s; (void)numrects; (void)rects; present(); } void SDL_UpdateRect(SDL_Surface *s, Sint32 x, Sint32 y, Uint32 w, Uint32 h) { (void)s; (void)x; (void)y; (void)w; (void)h; present(); } /* The page keeps its own title. */ void SDL_WM_SetCaption(const char *title, const char *icon) { (void)title; (void)icon; } /* No pointer lock in the browser: only remember what the game asked for. */ SDL_GrabMode SDL_WM_GrabInput(SDL_GrabMode mode) { if(mode != SDL_GRAB_QUERY) grab = mode; return grab; } int SDL_EnableUNICODE(int enable) { (void)enable; return 1; } int SDL_EnableKeyRepeat(int delay, int interval) { (void)interval; key_repeat = delay > 0; return 0; } int SDL_JoystickOpened(int device_index) { return SDL_JoystickFromInstanceID(SDL_JoystickGetDeviceInstanceID(device_index)) != NULL; } /* * SDL 1.2: SDL_SRCALPHA turns blending on; the surface alpha only counts when the surface has no * alpha channel of its own. */ int SDL_SetAlpha(SDL_Surface *surface, Uint32 flags, Uint8 alpha) { if(flags & SDL_SRCALPHA) { SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND); SDL_SetSurfaceAlphaMod(surface, surface->format->Amask ? 255 : alpha); } else { SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE); SDL_SetSurfaceAlphaMod(surface, 255); } return 0; } int SDL12_SetColorKey(SDL_Surface *surface, Uint32 flags, Uint32 key) { return SDL_SetColorKey(surface, (flags & SDL_SRCCOLORKEY) ? SDL_TRUE : SDL_FALSE, key); } int SDL_SetColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) { if(!surface->format->palette) return 0; return SDL_SetPaletteColors(surface->format->palette, colors, firstcolor, ncolors) == 0; } static SDL_Surface *convert(SDL_Surface *surface, Uint32 format, int blend) { SDL_BlendMode mode; Uint8 alpha; Uint32 key; int has_key = SDL_GetColorKey(surface, &key) == 0; SDL_Surface *s; SDL_GetSurfaceBlendMode(surface, &mode); SDL_GetSurfaceAlphaMod(surface, &alpha); s = SDL_ConvertSurfaceFormat(surface, format, 0); if(!s) return NULL; if(blend) { SDL_SetSurfaceBlendMode(s, SDL_BLENDMODE_BLEND); SDL_SetSurfaceAlphaMod(s, 255); } else { SDL_SetSurfaceBlendMode(s, mode); SDL_SetSurfaceAlphaMod(s, alpha); if(has_key) { Uint8 r, g, b, a; SDL_GetRGBA(key, surface->format, &r, &g, &b, &a); SDL_SetColorKey(s, SDL_TRUE, SDL_MapRGB(s->format, r, g, b)); } } return s; } /* The display format: 32-bit, no alpha channel; keeps the colour key and the surface alpha. */ SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface) { return convert(surface, SDL_PIXELFORMAT_RGB888, 0); } SDL_Surface *SDL_DisplayFormatAlpha(SDL_Surface *surface) { return convert(surface, SDL_PIXELFORMAT_ARGB8888, 1); } Uint32 SDL12_SurfaceFlags(SDL_Surface *surface) { Uint32 flags = 0; SDL_BlendMode mode; if(SDL_GetSurfaceBlendMode(surface, &mode) == 0 && mode == SDL_BLENDMODE_BLEND) flags |= SDL_SRCALPHA; if(SDL_HasColorKey(surface)) flags |= SDL_SRCCOLORKEY; return flags; } Uint8 SDL12_SurfaceAlpha(SDL_Surface *surface) { Uint8 alpha = 255; SDL_GetSurfaceAlphaMod(surface, &alpha); return alpha; } Uint32 SDL12_SurfaceColorKey(SDL_Surface *surface) { Uint32 key = 0; SDL_GetColorKey(surface, &key); return key; } /* SDL 1.2 gave the typed character with each key press; the game only uses it for names. */ int SDL12_KeyUnicode(const SDL_Keysym *keysym) { SDL_Keycode sym = keysym->sym; if(sym >= 'a' && sym <= 'z') { int upper = !!(keysym->mod & KMOD_SHIFT) != !!(keysym->mod & KMOD_CAPS); return upper ? sym - 'a' + 'A' : sym; } if(sym >= 32 && sym < 127) return sym; return 0; } /* * Without "obtained", SDL2 converts to the format the game asked for (16-bit stereo; the browser * plays 32-bit float), as SDL 1.2 did; "obtained" then gets the requested spec. */ int SDL12_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained) { if(SDL_OpenAudio(desired, NULL) < 0) return -1; if(obtained) *obtained = *desired; return 0; }