/* * cfsdl12.c - see cfsdl12.h. * * Written for fredygames.com to build Crimson Fields for the browser with Emscripten's SDL2 port. * Copyright (C) 2026 FredyGames. Same license as Crimson Fields: GNU GPL version 2 or later. */ #include "cfsdl12.h" #undef SDL_PollEvent #undef SDL_WaitEvent /* SDL2 key codes; the SDLK_ names now hold SDL 1.2 numbers. */ #define KEY2(scancode) SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_##scancode) static SDL_Window *window = NULL; static SDL_Renderer *renderer = NULL; static SDL_Texture *texture = NULL; static SDL_Surface *screen = NULL; static int key_repeat = 0; /* * The screen surface has two references: SDL 1.2 programs free the video surface before asking for * a new mode (a no-op in SDL 1.2), which only drops one of them; the shim frees it for good. */ SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) { Uint32 format = bpp == 16 ? SDL_PIXELFORMAT_RGB565 : SDL_PIXELFORMAT_RGB888; (void)flags; if(texture) SDL_DestroyTexture(texture); if(screen) SDL_FreeSurface(screen); texture = NULL; screen = NULL; if(!window) { window = SDL_CreateWindow("Crimson Fields", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0); if(!window) return NULL; renderer = SDL_CreateRenderer(window, -1, 0); if(!renderer) return NULL; } else SDL_SetWindowSize(window, width, height); texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height); screen = SDL_CreateRGBSurfaceWithFormat(0, width, height, SDL_BITSPERPIXEL(format), format); if(!texture || !screen) return NULL; screen->refcount = 2; return screen; } void SDL_UpdateRect(SDL_Surface *s, Sint32 x, Sint32 y, Uint32 w, Uint32 h) { SDL_Rect r; if(!screen || s != screen) return; if(w == 0 && h == 0) { r.x = r.y = 0; r.w = screen->w; r.h = screen->h; } else { r.x = x; r.y = y; r.w = w; r.h = h; } SDL_UpdateTexture(texture, &r, (Uint8 *)screen->pixels + r.y * screen->pitch + r.x * screen->format->BytesPerPixel, screen->pitch); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); } /* "Any size is fine": the game then offers its list of standard sizes. */ SDL_Rect **SDL_ListModes(void *format, Uint32 flags) { (void)format; (void)flags; return (SDL_Rect **)-1; } /* The page keeps its own title and icon, and has its own full screen button. */ void SDL_WM_SetCaption(const char *title, const char *icon) { (void)title; (void)icon; } void SDL_WM_SetIcon(SDL_Surface *icon, Uint8 *mask) { (void)icon; (void)mask; } int SDL_WM_IconifyWindow(void) { return 0; } int SDL_WM_ToggleFullScreen(SDL_Surface *surface) { (void)surface; return 0; } int SDL_EnableUNICODE(int enable) { (void)enable; return 1; } int SDL_EnableKeyRepeat(int delay, int interval) { (void)interval; key_repeat = delay > 0; return 0; } /* * 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; } /* The screen format; keeps the colour key, the blend mode and the surface alpha. */ SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface) { 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, screen ? screen->format->format : SDL_PIXELFORMAT_RGB565, 0); if(!s) return NULL; 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; } /* SDL2 key code -> SDL 1.2 key number. */ static SDL_Keycode key12(SDL_Keycode key) { if(key >= 0 && key < 128) return key; if(key >= KEY2(KP_1) && key <= KEY2(KP_9)) return SDLK_KP1 + (key - KEY2(KP_1)); if(key >= KEY2(F1) && key <= KEY2(F12)) return SDLK_F1 + (key - KEY2(F1)); if(key >= KEY2(F13) && key <= KEY2(F15)) return SDLK_F13 + (key - KEY2(F13)); if(key == KEY2(KP_0)) return SDLK_KP0; if(key == KEY2(KP_PERIOD)) return SDLK_KP_PERIOD; if(key == KEY2(KP_ENTER)) return SDLK_KP_ENTER; if(key == KEY2(UP)) return SDLK_UP; if(key == KEY2(DOWN)) return SDLK_DOWN; if(key == KEY2(RIGHT)) return SDLK_RIGHT; if(key == KEY2(LEFT)) return SDLK_LEFT; if(key == KEY2(INSERT)) return SDLK_INSERT; if(key == KEY2(HOME)) return SDLK_HOME; if(key == KEY2(END)) return SDLK_END; if(key == KEY2(PAGEUP)) return SDLK_PAGEUP; if(key == KEY2(PAGEDOWN)) return SDLK_PAGEDOWN; return SDLK_UNKNOWN; } /* First character of a UTF-8 string, as SDL 1.2's 16-bit keysym.unicode. */ static Uint32 utf8_first(const char *s) { const unsigned char *p = (const unsigned char *)s; if(p[0] < 0x80) return p[0]; if((p[0] & 0xE0) == 0xC0 && p[1]) return (p[0] & 0x1F) << 6 | (p[1] & 0x3F); if((p[0] & 0xF0) == 0xE0 && p[1] && p[2]) return (p[0] & 0x0F) << 12 | (p[1] & 0x3F) << 6 | (p[2] & 0x3F); return 0; } /* Turns an SDL2 event into what SDL 1.2 would have sent; returns 0 to drop it. */ static int translate(SDL_Event *ev) { switch(ev->type) { case SDL_KEYDOWN: case SDL_KEYUP: { SDL_Keycode sym = ev->key.keysym.sym; if(ev->type == SDL_KEYDOWN && ev->key.repeat && !key_repeat) return 0; ev->key.keysym.sym = key12(sym); ev->key.keysym.unused = 0; if(ev->type == SDL_KEYDOWN) { /* The browser sends the typed character right after the key press. */ SDL_Event text; SDL_PumpEvents(); if(SDL_PeepEvents(&text, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT) == 1) ev->key.keysym.unused = utf8_first(text.text.text); else if(sym == '\r' || sym == '\b' || sym == '\t' || sym == 27 || sym == 127) ev->key.keysym.unused = sym; } return 1; } case SDL_MOUSEWHEEL: { /* SDL 1.2 reported the wheel as a press and a release of buttons 4 and 5. */ int x, y; Uint8 button; if(ev->wheel.y == 0) return 0; button = ev->wheel.y > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN; SDL_GetMouseState(&x, &y); SDL_zerop(ev); ev->type = SDL_MOUSEBUTTONDOWN; ev->button.button = button; ev->button.state = SDL_PRESSED; ev->button.x = x; ev->button.y = y; { SDL_Event up = *ev; up.type = SDL_MOUSEBUTTONUP; up.button.state = SDL_RELEASED; SDL_PushEvent(&up); } return 1; } case SDL_KEYMAPCHANGED: case SDL_TEXTINPUT: case SDL_TEXTEDITING: case SDL_WINDOWEVENT: case SDL_AUDIODEVICEADDED: case SDL_AUDIODEVICEREMOVED: case SDL_RENDER_TARGETS_RESET: return 0; default: return 1; } } int CF_PollEvent(SDL_Event *event) { while(SDL_PollEvent(event)) if(translate(event)) return 1; return 0; } /* SDL_WaitEvent sleeps with SDL_Delay, which hands control back to the browser (Asyncify). */ int CF_WaitEvent(SDL_Event *event) { for(;;) { if(!SDL_WaitEvent(event)) return 0; if(translate(event)) return 1; } }