diff --git a/Makefile b/Makefile
index 60d5cb5..dbb0b08 100644
--- a/Makefile
+++ b/Makefile
@@ -32,8 +32,8 @@ DXE3GEN := dxe3gen
EXE2COFF := exe2coff
# Compiler Settings
-CFLAGS := -DPLATFORM_DOS -Wall -MD
-LDFLAGS :=
+override CFLAGS += -Wall -MD
+override LDFLAGS +=
# Output Directories
OBJ := obj
@@ -47,30 +47,40 @@ FIN := font/in
# Include Paths
INC := include 3rdparty 3rdparty/pthreads/include roo_e
+# Use MEMWATCH?
+ifneq (,$(findstring MEMWATCH,$(CFLAGS)))
+ MEMWATCH_SRC := 3rdparty/memwatch/memwatch.c
+ MEMWATCH_OBJ := $(OBJ)/$(MEMWATCH_SRC).o
+ INC += 3rdparty/memwatch
+else
+ MEMWATCH_SRC :=
+ MEMWATCH_OBJ :=
+endif
+
# Font Compiler Source and Target - NOTE: This is a Linux binary!
FONT_ELF := font
-FONT_SRC := $(shell find font/src -name '*.c')
+FONT_SRC := $(shell find font/src -name '*.c') $(MEMWATCH_SRC)
FONT_OBJ := $(FONT_SRC:%=$(OBJ)/%.o)
FONT_LIB := -lm
# Roo/E Source and Target
ROOE_EXE := roo_e.exe
-ROOE_SRC := $(shell find roo_e -name '*.c')
+ROOE_SRC := $(shell find roo_e -name '*.c') $(MEMWATCH_SRC)
ROOE_OBJ := $(ROOE_SRC:%=$(OBJ)/%.o)
ROOE_LIB := 3rdparty/pthreads/lib/libgthreads.a -lgcc -lm
# MultiPlayer Game Client
MPGC_EXE := kpsmpgc.app
-MPGC_SRC := $(shell find kpsmpgc -name '*.c')
+MPGC_SRC := $(shell find kpsmpgc -name '*.c') $(MEMWATCH_SRC)
MPGC_OBJ := $(MPGC_SRC:%=$(OBJ)/%.o)
MPGC_LIB :=
-DYNS := kpsvideo stbimage
+DYNS := kpssurf kpsplatf stbimage
FONTS := vga4x8 vga8x8 vga8x14 vga8x16
# Wiring
-INC_FLAGS := $(addprefix -I,$(INC))
-CFLAGS += $(INC_FLAGS)
+override CFLAGS += $(INC_FLAGS)
+INC_FLAGS := $(addprefix -I,$(INC)) $(foreach DIR,$(DYNS),-Idyn/$(DIR))
APP_OBJS := $(ROOE_OBJ) $(MPGC_OBJ)
FONTS := $(addprefix $(FNT)/,$(addsuffix .fnt,$(FONTS)))
FONT_PNGS := $(subst .fnt,.png,$(subst $(FNT),$(FIN),$(FONTS)))
@@ -96,6 +106,7 @@ clean:
# Font Compiler Target
$(BIN)/$(FONT_ELF): $(FONT_OBJ) Makefile
+ @echo "\n---------- $@"
mkdir -p $(dir $@) $(BIN)/fonts
$(CC) $(FONT_OBJ) -o $@ $(LDFLAGS) $(FONT_LIB)
@@ -104,6 +115,7 @@ $(FONTS) &: $(FONT_PNGS) $(BIN)/$(FONT_ELF)
# Roo/E Target
$(BIN)/$(ROOE_EXE): $(ROOE_OBJ) Makefile
+ @echo "\n---------- $@"
mkdir -p $(dir $@)
$(CC) $(ROOE_OBJ) -o $@ $(LDFLAGS) $(ROOE_LIB)
# Embed the DPMI Server
@@ -113,16 +125,17 @@ $(BIN)/$(ROOE_EXE): $(ROOE_OBJ) Makefile
# MPGC Target
$(APP)/$(MPGC_EXE): $(MPGC_OBJ) Makefile
+ @echo "\n---------- $@"
mkdir -p $(dir $@)
$(DXE3GEN) -o $@ $(MPGC_OBJ) -U $(LDFLAGS) $(MPGC_LIB)
# DYN Targets - This is NOT how make should be used.
.PHONY: dyns
$(DYN_FILES): $(DYN_SOURCE) Makefile
- echo $(DYN_SOURCE)
+ @echo "\n---------- $@"
mkdir -p $(DYN) $(OBJ)/dyn/$(basename $(notdir $@)) $(SDK)/lib $(SDK)/include
$(foreach CFILE,$(shell find dyn/$(basename $(notdir $@)) -name '*.c'),$(CC) $(CFLAGS) -o $(OBJ)/dyn/$(basename $(notdir $@))/$(subst dyn/$(basename $(notdir $@))/,,$(CFILE).o) -c $(CFILE) && ) true
- $(AR) rcs $(OBJ)/dyn/$(basename $(notdir $@)).a $(OBJ)/dyn/$(basename $(notdir $@))/*.o
+ $(AR) rcs $(OBJ)/dyn/$(basename $(notdir $@)).a $(OBJ)/dyn/$(basename $(notdir $@))/*.o $(MEMWATCH_OBJ)
$(DXE3GEN) -o $@ -Y $@.a --whole-archive -U $(OBJ)/dyn/$(basename $(notdir $@)).a $(LDFLAGS)
mv $@.a $(SDK)/lib/.
if [ ! -z "$(shell find dyn/$(basename $(notdir $@)) -name '*.h')" ]; then cp $(shell find dyn/$(basename $(notdir $@)) -name '*.h') $(SDK)/include/.; fi
diff --git a/build.sh b/build.sh
index dda3402..f1a4f7f 100755
--- a/build.sh
+++ b/build.sh
@@ -24,9 +24,15 @@
#
+# Delete things that get built for Linux and also need built for DOS.
+rm -rf obj/3rdparty/memwatch
+
# Linux side first.
-make linux
+CFLAGS="-DMEMWATCH" make linux
+
+# Delete things that get built for Linux and also need built for DOS.
+rm -rf obj/3rdparty/memwatch
# Now do the DOS part.
eval "$(../toolchains/toolchains.sh use x86 dos)"
-make dos
+CFLAGS="-DMEMWATCH" make dos
diff --git a/dyn/kpsplatf/djgpp.c b/dyn/kpsplatf/djgpp.c
new file mode 100644
index 0000000..94ad6ef
--- /dev/null
+++ b/dyn/kpsplatf/djgpp.c
@@ -0,0 +1,785 @@
+/*
+ * Roo/E, the Kangaroo Punch Portable GUI Toolkit
+ * Copyright (C) 2026 Scott Duensing
+ *
+ * http://kangaroopunch.com
+ *
+ *
+ * This file is part of Roo/E.
+ *
+ * Roo/E is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Roo/E. If not, see .
+ *
+ */
+
+
+#ifdef __DJGPP__
+
+
+#include "kpsplatf.h"
+
+
+// These are all we support
+#define VBE_MM_PACKED 4
+#define VBE_MM_DCOLOR 6
+
+
+// Based on http://www.brackeen.com/vga/source/djgpp20/mouse.c.html
+#define MOUSE_INT 0x33
+
+#define MOUSE_RESET 0x00
+#define MOUSE_STATUS 0x03
+#define MOUSE_GETMOTION 0x0B
+
+#define MOUSE_LEFT_BUTTON 0x01
+#define MOUSE_RIGHT_BUTTON 0x02
+#define MOUSE_MIDDLE_BUTTON 0x04
+
+
+#define KEYBOARD_READ_EXTENDED 0x10
+#define KEYBOARD_CHECK_EXTENDED 0x11
+#define KEYBOARD_META_EXTENDED 0x12
+
+
+enum MetaBitsE {
+ KEY_META_SHIFT_RIGHT = 0,
+ KEY_META_SHIFT_LEFT,
+ KEY_META_CONTROL,
+ KEY_META_ALT,
+ KEY_META_SCROLL_LOCKED,
+ KEY_META_NUM_LOCKED,
+ KEY_META_CAPS_LOCKED,
+ KEY_META_INSERT_LOCKED,
+ KEY_META_CONTROL_LEFT,
+ KEY_META_ALT_LEFT,
+ KEY_META_CONTROL_RIGHT,
+ KEY_META_ALT_RIGHT,
+ KEY_META_SCROLL_LOCK,
+ KEY_META_NUM_LOCK,
+ KEY_META_CAPS_LOCK,
+ KEY_META_SYSREQ
+};
+
+
+typedef struct VBEInfoS {
+ char vbeSignature[4]; // 'VESA' 4 byte signature
+ int16_t vbeVersion; // VBE version number
+ char *oemStringPtr; // Pointer to OEM string
+ uint32_t capabilities; // Capabilities of video card
+ uint16_t *videoModePtr; // Pointer to supported modes
+ uint16_t totalMemory; // Number of 64kb memory blocks
+ // added for VBE 2.0
+ uint16_t oemSoftwareRev; // OEM Software revision number
+ char *oemVendorNamePtr; // Pointer to Vendor Name string
+ char *oemProductNamePtr; // Pointer to Product Name string
+ char *oemProductRevPtr; // Pointer to Product Revision str
+ char reserved[222]; // Pad to 256 byte block size
+ char oemData[256]; // Scratch pad for OEM data
+} __attribute__ ((packed)) VBEInfoT;
+
+
+typedef struct VBEModeInfoS {
+ // Mandatory information for all VBE revisions:
+ uint16_t modeAttributes; // mode attributes
+ uint8_t winAAttributes; // window A attributes
+ uint8_t winBAttributes; // window B attributes
+ uint16_t winGranularity; // window granularity
+ uint16_t winSize; // window size
+ uint16_t winASegment; // window A start segment
+ uint16_t winBSegment; // window B start segment
+ uint32_t winFuncPtr; // pointer to window function
+ uint16_t bytesPerScanLine; // bytes per scan line
+ // Mandatory information for VBE 1.2 and above:
+ uint16_t xResolution; // horizontal resolution in pixels or chars
+ uint16_t yResolution; // vertical resolution in pixels or chars
+ uint8_t xCharSize; // character cell width in pixels
+ uint8_t yCharSize; // character cell height in pixels
+ uint8_t numberOfPlanes; // number of memory planes
+ uint8_t bitsPerPixel; // bits per pixel
+ uint8_t numberOfBanks; // number of banks
+ uint8_t memoryModel; // memory model type
+ uint8_t bankSize; // bank size in KB
+ uint8_t numberOfImagePages; // number of images
+ uint8_t reserved; // reserved for page function
+ // Direct Color fields (required for direct/6 and YUV/7 memory models)
+ uint8_t redMaskSize; // size of direct color red mask in bits
+ uint8_t redFieldPosition; // bit position of lsb of red mask
+ uint8_t greenMaskSize; // size of direct color green mask in bits
+ uint8_t greenFieldPosition; // bit position of lsb of green mask
+ uint8_t blueMaskSize; // size of direct color blue mask in bits
+ uint8_t blueFieldPosition; // bit position of lsb of blue mask
+ uint8_t rsvdMaskSize; // size of direct color reserved mask in bits
+ uint8_t rsvdFieldPosition; // bit position of lsb of reserved mask
+ uint8_t directColorModeInfo; // direct color mode attributes
+ // Mandatory information for VBE 2.0 and above:
+ uint32_t physBasePtr; // physical address for flat frame buffer
+ uint32_t offScreenMemOffset; // pointer to start of off screen memory
+ uint16_t offScreenMemSize; // amount of off screen memory in 1k units
+ char reservedBuf[206];
+} __attribute__ ((packed)) VBEModeInfoT;
+
+
+typedef struct PModeInterfaceS {
+ int16_t setWindow;
+ int16_t setDisplayStart;
+ int16_t setPalette;
+ int16_t ioInfo;
+} __attribute__ ((packed)) PModeInterfaceT;
+
+
+typedef struct VBESurfaceS {
+ uint32_t lfbLinearAddress;
+ uint16_t lfbSelector;
+ void *lfbNearPtr;
+ uint32_t lfbMemSize;
+ uint16_t xResolution;
+ uint16_t yResolution;
+ uint32_t bitsPerPixel;
+ uint16_t virtualXResolution;
+ uint32_t bytesPerPixel;
+ uint32_t screenBytes;
+ uint32_t screenDWords;
+ uint32_t centerX;
+ uint32_t centerY;
+ uint16_t numberOfOffscreens;
+ uint8_t vbeBoolean;
+ uint8_t vbeInitBoolean;
+ int32_t ioSegment;
+ uint32_t ioLinear;
+ uint32_t rMask;
+ uint32_t gMask;
+ uint32_t bMask;
+ uint32_t aMask;
+ uint8_t rShift;
+ uint8_t gShift;
+ uint8_t bShift;
+ uint8_t aShift;
+ uint8_t rPos;
+ uint8_t gPos;
+ uint8_t bPos;
+ uint8_t aPos;
+} VBESurfaceT;
+
+
+static VBESurfaceT _vbeSurface;
+static VBEInfoT _vbeInfo;
+static VBEModeInfoT _vbeModeInfo;
+static PModeInterfaceT *_pmodeInterfacePtr;
+static uint32_t *_yTable;
+
+
+static void vbeCreatePalette(void);
+static VBEInfoT *vbeGetInfo(void);
+static VBEModeInfoT *vbeGetModeInfo(uint16_t vbeModeNumber);
+static void *vbeGetPmodeInterface(void);
+static uint8_t vbeIsDesiredMode(void);
+static uint16_t vbeSelectModeNumber(uint16_t xRes, uint16_t yRes, uint8_t bpp);
+static VBESurfaceT *vbeSetMode(uint16_t vbeModeNumber);
+static uint16_t vbeSetScanlineLength(uint16_t pixelLength);
+
+
+static void (*pmVBESetDisplayStart)(void);
+
+
+void platformEventGet(PlatformEventT *event) {
+ int32_t x;
+ int32_t y;
+ int16_t dx;
+ int16_t dy;
+ int16_t h = videoDisplayHeightGet();
+ int16_t w = videoDisplayWidthGet();
+ int16_t ext = bioskey(KEYBOARD_CHECK_EXTENDED);
+ int16_t meta = bioskey(KEYBOARD_META_EXTENDED);
+ int16_t key = 0;
+ union REGS regs;
+ static int32_t lastX = 0;
+ static int32_t lastY = 0;
+ static int32_t lastButtons = 0;
+
+ event->buttons = lastButtons;
+
+ // Read mouse motion.
+ regs.x.ax = MOUSE_GETMOTION;
+ int86(MOUSE_INT, ®s, ®s);
+ dx = regs.x.cx; // Temporary assignment changes values to signed.
+ dy = regs.x.dx; // Don't skip this step. :-)
+ x = lastX + dx;
+ y = lastY + dy;
+ if (x < 0) x = 0;
+ if (x > w - 1) x = w - 1;
+ if (y < 0) y = 0;
+ if (y > h - 1) y = h - 1;
+ lastX = x;
+ lastY = y;
+ event->x = x;
+ event->y = y;
+
+ // Read mouse buttons.
+ regs.x.ax = MOUSE_STATUS;
+ int86(MOUSE_INT, ®s, ®s);
+
+ // Was the left button down?
+ if (event->buttons & BUTTON_LEFT) {
+ // Yes. Is it still down?
+ if ((regs.x.bx & MOUSE_LEFT_BUTTON) > 0) {
+ // Yes. Do nothing.
+ } else {
+ // No! Clear it and set UP event.
+ event->buttons &= ~BUTTON_LEFT;
+ event->flags |= EVENT_FLAG_LEFT_UP;
+ }
+ } else {
+ // No. Is it down now?
+ if ((regs.x.bx & MOUSE_LEFT_BUTTON) > 0) {
+ // Yes! Set bit and DOWN event.
+ event->buttons |= BUTTON_LEFT;
+ event->flags |= EVENT_FLAG_LEFT_DOWN;
+ } else {
+ // No. Do nothing.
+ }
+ }
+
+ // Was the right button down?
+ if (event->buttons & BUTTON_RIGHT) {
+ // Yes. Is it still down?
+ if ((regs.x.bx & MOUSE_RIGHT_BUTTON) > 0) {
+ // Yes. Do nothing.
+ } else {
+ // No! Clear it and set UP event.
+ event->buttons &= ~BUTTON_RIGHT;
+ event->flags |= EVENT_FLAG_RIGHT_UP;
+ }
+ } else {
+ // No. Is it down now?
+ if ((regs.x.bx & MOUSE_RIGHT_BUTTON) > 0) {
+ // Yes! Set bit and DOWN event.
+ event->buttons |= BUTTON_RIGHT;
+ event->flags |= EVENT_FLAG_RIGHT_DOWN;
+ } else {
+ // No. Do nothing.
+ }
+ }
+
+ // Read keyboard.
+ event->key = 0;
+ event->kbstat = 0;
+ if (ext > 0) {
+ key = bioskey(KEYBOARD_READ_EXTENDED);
+ // The value returned is a combination of the key's scan code in the high 8 bits
+ // and its ASCII code in the low 8 bits. For non-alphanumeric keys, such as the
+ // arrow keys, the low 8 bits are zeroed.
+ // Extended keys have the E0h prefix in the low 8 bits.
+ event->flags |= EVENT_FLAG_KEYPRESS;
+ if (LOW_BYTE(key) == 0xE0) {
+ //_extended = 1;
+ event->key = HIGH_BYTE(key);
+ } else {
+ //_extended = 0;
+ event->key = LOW_BYTE(key);
+ }
+ if ((meta & (1 << KEY_META_ALT)) + (meta & (1 << KEY_META_ALT_LEFT)) + (meta & (1 << KEY_META_ALT_RIGHT))) event->kbstat |= META_ALT;
+ if ((meta & (1 << KEY_META_CONTROL)) + (meta & (1 << KEY_META_CONTROL_LEFT)) + (meta & (1 << KEY_META_CONTROL_RIGHT))) event->kbstat |= META_CTRL;
+ if ((meta & (1 << KEY_META_SHIFT_LEFT)) + (meta & (1 << KEY_META_SHIFT_RIGHT))) event->kbstat |= META_SHIFT;
+ }
+
+ lastButtons = event->buttons;
+}
+
+
+void platformShutdown(void) {
+ __dpmi_regs r;
+ __dpmi_meminfo m;
+
+ surfaceShutdown();
+
+ if (_vbeSurface.vbeInitBoolean == 0) {
+ r.x.ax = 0x03; // make sure we're in 3h
+ __dpmi_int(0x10, &r); // for buggy vesa implementations
+ //return(1);
+ }
+
+ if (_yTable) free(_yTable);
+
+ // free mapping etc.
+ m.size = (_vbeInfo.totalMemory * 64 * 1024);
+ m.address = _vbeSurface.lfbLinearAddress;
+ __dpmi_unlock_linear_region(&m);
+ __dpmi_free_physical_address_mapping(&m);
+ __dpmi_free_ldt_descriptor(_vbeSurface.lfbSelector);
+
+ // get rid of PMI interface
+ if (_pmodeInterfacePtr) free(_pmodeInterfacePtr);
+ if (_vbeSurface.ioSegment) {
+ m.address = _vbeSurface.ioLinear;
+ __dpmi_free_physical_address_mapping(&m);
+ __dpmi_free_ldt_descriptor(_vbeSurface.ioSegment);
+ }
+
+ // return do DOS
+ r.x.ax = 0x03;
+ __dpmi_int(0x10, &r);
+
+ // deinit VBE surface
+ _vbeSurface.vbeInitBoolean = 0;
+ _vbeSurface.vbeBoolean = 0;
+
+ // dealloc mode list
+ free(_vbeInfo.videoModePtr);
+}
+
+
+uint8_t platformStartup(int16_t width, int16_t height, int16_t depth) {
+ uint16_t vbeModeNumber;
+
+ if (vbeGetInfo() == NULL) {
+ logWrite("No VESA BIOS Extensions found.\n");
+ platformShutdown();
+ return FAIL;
+ }
+
+ if (_vbeInfo.vbeVersion < 0x0200) {
+ logWrite("VBE Version 2.0 or better required.\n");
+ platformShutdown();
+ return FAIL;
+ }
+
+ vbeGetPmodeInterface();
+
+ if ((vbeModeNumber = vbeSelectModeNumber(width, height, depth)) == 0) {
+ logWrite("No appropriate video mode available.\n");
+ platformShutdown();
+ return FAIL;
+ }
+
+ if (vbeSetMode(vbeModeNumber) == NULL) {
+ platformShutdown();
+ return FAIL;
+ }
+
+ surfaceStartup(_vbeSurface.bitsPerPixel);
+
+ return SUCCESS;
+}
+
+
+static void vbeCreatePalette(void) {
+ uint8_t color = 0;
+ uint8_t red;
+ uint8_t green;
+ uint8_t blue;
+
+ // Create palette for 3:3:2 true color emulation
+ for (red = 4; red < 68; red += 8) {
+ for (green = 4; green < 68; green += 8) {
+ for (blue = 4; blue < 68; blue += 16) {
+ outportb(0x3C8, color);
+ outportb(0x3C9, red);
+ outportb(0x3C9, green);
+ outportb(0x3C9, blue);
+ color++;
+ }
+ }
+ }
+}
+
+
+static VBEInfoT *vbeGetInfo(void) {
+ uint16_t counter = 0;
+ uint16_t offset = 0;
+ uint16_t vbeMode = 0xFFFF;
+ uint16_t *vbeModeList = 0;
+ __dpmi_regs r;
+
+ // we want VBE 2.0+ info
+ memcpy(_vbeInfo.vbeSignature, "VBE2", 4);
+
+ // request info
+ r.x.ax = 0x4F00;
+ r.x.di = __tb & 0x0F;
+ r.x.es = (__tb >> 4) & 0xFFFF;
+ dosmemput(&_vbeInfo, sizeof(VBEInfoT), __tb);
+ __dpmi_int(0x10, &r);
+ if (r.x.ax != 0x004F) return NULL;
+ dosmemget(__tb, sizeof(VBEInfoT), &_vbeInfo);
+
+ if (strncmp(_vbeInfo.vbeSignature, "VESA", 4) != 0) return NULL; // VESA ?
+
+ // get the videomode list
+ do {
+ dosmemget(((((long)_vbeInfo.videoModePtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.videoModePtr & 0xFFFF) + (counter * sizeof(short)), sizeof(short), &vbeMode);
+ counter++;
+ } while (vbeMode != 0xFFFF);
+ vbeModeList = malloc((counter + 1) * sizeof(short));
+
+ counter = 0;
+ do {
+ dosmemget(((((long)_vbeInfo.videoModePtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.videoModePtr & 0xFFFF) + (counter * sizeof(short)), sizeof(short), &vbeModeList[counter]);
+ counter++;
+ } while (vbeModeList[counter - 1] != 0xFFFF);
+ _vbeInfo.videoModePtr = vbeModeList;
+
+ // get the OEM string
+ counter = 0;
+ do {
+ dosmemget(((((long)_vbeInfo.oemStringPtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.oemStringPtr & 0xFFFF) + counter, sizeof(char), &_vbeInfo.oemData[counter]);
+ counter++;
+ } while (_vbeInfo.oemData[counter - 1] != 0);
+ _vbeInfo.oemStringPtr = &_vbeInfo.oemData[0];
+ offset = counter;
+
+ if (_vbeInfo.vbeVersion >= 0x0200) {
+ // get the vendor name
+ counter = 0;
+ do {
+ dosmemget(((((long)_vbeInfo.oemVendorNamePtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.oemVendorNamePtr & 0xFFFF) + counter, sizeof(char), &_vbeInfo.oemData[counter + offset]);
+ counter++;
+ } while (_vbeInfo.oemData[counter + offset - 1] != 0);
+ _vbeInfo.oemVendorNamePtr = &_vbeInfo.oemData[offset];
+ offset = offset + counter;
+
+ // get the product name
+ counter = 0;
+ do {
+ dosmemget(((((long)_vbeInfo.oemProductNamePtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.oemProductNamePtr & 0xFFFF) + counter, sizeof(char), &_vbeInfo.oemData[counter + offset]);
+ counter++;
+ } while (_vbeInfo.oemData[counter + offset - 1] != 0);
+ _vbeInfo.oemProductNamePtr = &_vbeInfo.oemData[offset];
+ offset = offset + counter;
+
+ //get the product revision
+ counter = 0;
+ do {
+ dosmemget(((((long)_vbeInfo.oemProductRevPtr >> 16) & 0xFFFF) << 4) + ((long)_vbeInfo.oemProductRevPtr & 0xFFFF) + counter, sizeof(char), &_vbeInfo.oemData[counter + offset]);
+ counter++;
+ } while (_vbeInfo.oemData[counter + offset - 1] != 0);
+ _vbeInfo.oemProductRevPtr = &_vbeInfo.oemData[offset];
+ }
+
+ _vbeSurface.vbeBoolean = 1;
+
+ return(&_vbeInfo);
+}
+
+
+static VBEModeInfoT *vbeGetModeInfo(uint16_t vbeModeNumber) {
+ __dpmi_regs r;
+
+ if (_vbeSurface.vbeBoolean == 0) return NULL;
+
+ r.x.ax = 0x4F01;
+ r.x.cx = vbeModeNumber;
+ r.x.di = __tb & 0x0F;
+ r.x.es = (__tb >> 4) & 0xFFFF;
+ __dpmi_int(0x10, &r);
+
+ if (r.x.ax != 0x004F) return NULL;
+
+ dosmemget(__tb, sizeof(VBEModeInfoT), &_vbeModeInfo);
+
+ // Fake 3:3:2 true color for packed modes.
+ if (_vbeModeInfo.bitsPerPixel == 8) {
+ _vbeModeInfo.blueMaskSize = 2;
+ _vbeModeInfo.greenMaskSize = 3;
+ _vbeModeInfo.redMaskSize = 3;
+ }
+
+ if (_vbeModeInfo.bitsPerPixel == 16) {
+ // for buggy VBE implementations
+ _vbeModeInfo.bitsPerPixel = _vbeModeInfo.redMaskSize + _vbeModeInfo.greenMaskSize + _vbeModeInfo.blueMaskSize;
+ }
+
+ return(&_vbeModeInfo);
+}
+
+
+static void *vbeGetPmodeInterface(void) {
+ __dpmi_regs r;
+ __dpmi_meminfo m;
+ uint16_t *ptr;
+
+ // only available in VBE 2.0+
+ if (_vbeInfo.vbeVersion < 0x200) return NULL;
+
+ r.x.ax = 0x4F0A;
+ r.x.bx = 0;
+ __dpmi_int(0x10, &r);
+ if (r.x.ax != 0x004F) return NULL;
+
+ // copy interface
+ _pmodeInterfacePtr = (PModeInterfaceT *)malloc(r.x.cx);
+ dosmemget((r.x.es * 16) + r.x.di, r.x.cx, _pmodeInterfacePtr);
+ _go32_dpmi_lock_data(_pmodeInterfacePtr, r.x.cx);
+
+ // need memory-mapped IO?
+ if (_pmodeInterfacePtr->ioInfo) {
+ ptr = (uint16_t *)((char *)_pmodeInterfacePtr + _pmodeInterfacePtr->ioInfo);
+ // skip the port table...
+ while (*ptr != 0xFFFF)
+ ptr++;
+ ptr++;
+ // ...and get descriptor
+ if (*ptr != 0xFFFF) {
+ m.address = *((uint32_t *)ptr);
+ m.size = *(ptr + 2);
+ if (__dpmi_physical_address_mapping(&m) != 0) return NULL;
+ _vbeSurface.ioLinear = m.address;
+ __dpmi_lock_linear_region(&m);
+ _vbeSurface.ioSegment = __dpmi_allocate_ldt_descriptors(1);
+ if (_vbeSurface.ioSegment < 0) {
+ __dpmi_unlock_linear_region(&m);
+ __dpmi_free_physical_address_mapping(&m);
+ return NULL;
+ }
+ __dpmi_set_segment_base_address(_vbeSurface.ioSegment, _vbeSurface.ioLinear);
+ __dpmi_set_segment_limit(_vbeSurface.ioSegment, m.size - 1);
+ }
+ }
+
+ pmVBESetDisplayStart = (void *)((char *)_pmodeInterfacePtr + _pmodeInterfacePtr->setDisplayStart);
+
+ return pmVBESetDisplayStart;
+}
+
+
+static uint8_t vbeIsDesiredMode(void) {
+ // This is a custom filter to remove modes not supported by the calling application.
+
+ // Must be linear.
+ if (((_vbeModeInfo.modeAttributes) & (1<<7)) >> 7) {
+ // Packed or Direct Color mode.
+ if (_vbeModeInfo.memoryModel == VBE_MM_PACKED || _vbeModeInfo.memoryModel == VBE_MM_DCOLOR) {
+ // We only handle these bit depths.
+ if ((_vbeModeInfo.bitsPerPixel == 8) || (_vbeModeInfo.bitsPerPixel == 16) || (_vbeModeInfo.bitsPerPixel == 32)) {
+ // Resolution minimum of 640x480.
+ if (_vbeModeInfo.xResolution >= 640 && _vbeModeInfo.yResolution >= 480) {
+ // Multiple of 8
+ if (DIVISIBLE_BY_EIGHT(_vbeModeInfo.xResolution) && DIVISIBLE_BY_EIGHT(_vbeModeInfo.yResolution)) {
+ // Valid mode!
+ return 1;
+ }
+ }
+ }
+ }
+ }
+
+ // Rejected!
+ return 0;
+}
+
+
+static uint16_t vbeSelectModeNumber(uint16_t xRes, uint16_t yRes, uint8_t bpp) {
+ uint16_t counter;
+
+ if (_vbeSurface.vbeBoolean == 0) return(0);
+
+ for (counter = 0; ; counter++) {
+ if (_vbeInfo.videoModePtr[counter] == 0xFFFF) return(0);
+ vbeGetModeInfo(_vbeInfo.videoModePtr[counter]);
+ if (vbeIsDesiredMode()) {
+ if (_vbeModeInfo.xResolution == xRes && _vbeModeInfo.yResolution == yRes && _vbeModeInfo.bitsPerPixel == bpp)
+ break;
+ }
+ }
+
+ return(_vbeInfo.videoModePtr[counter]);
+}
+
+
+static VBESurfaceT *vbeSetMode(uint16_t vbeModeNumber) {
+ __dpmi_regs r;
+ __dpmi_meminfo m;
+ uint32_t counter;
+
+ if (_vbeSurface.vbeBoolean == 0) return NULL;
+ if (_vbeSurface.vbeInitBoolean == 1) return NULL;
+ if (vbeGetModeInfo(vbeModeNumber) == 0) return NULL;
+
+ if (_yTable) free(_yTable);
+ if ((_yTable = malloc(4 * (_vbeModeInfo.yResolution + 1))) == 0) return NULL;
+ for (counter = 0; counter <= _vbeModeInfo.yResolution; counter++) {
+ _yTable[counter] = _vbeModeInfo.xResolution * counter;// * ((_vbeModeInfo.bitsPerPixel + 1) / 8);
+ }
+
+ // request frame buffer
+ r.x.ax = 0x4F02;
+ r.x.bx = (vbeModeNumber | 0x4000);
+ __dpmi_int(0x10, &r);
+ if (r.x.ax != 0x004F) return(0);
+
+ m.size = (_vbeInfo.totalMemory * 64 * 1024);
+ m.address = _vbeModeInfo.physBasePtr;
+ __dpmi_physical_address_mapping(&m);
+ __dpmi_lock_linear_region(&m);
+
+ _vbeSurface.lfbLinearAddress = m.address;
+ _vbeSurface.lfbSelector = __dpmi_allocate_ldt_descriptors(1);
+
+ __dpmi_set_segment_base_address(_vbeSurface.lfbSelector, _vbeSurface.lfbLinearAddress);
+ __dpmi_set_segment_limit(_vbeSurface.lfbSelector, (_vbeInfo.totalMemory * 64 * 1024) - 1);
+
+ _vbeSurface.lfbNearPtr = (void *)(_vbeSurface.lfbLinearAddress + __djgpp_conventional_base);
+ _vbeSurface.xResolution = _vbeModeInfo.xResolution;
+ _vbeSurface.yResolution = _vbeModeInfo.yResolution;
+ _vbeSurface.bitsPerPixel = _vbeModeInfo.bitsPerPixel;
+ _vbeSurface.virtualXResolution = _vbeModeInfo.xResolution;
+ _vbeSurface.bytesPerPixel = (_vbeModeInfo.bitsPerPixel + 1) / 8;
+ _vbeSurface.centerX = _vbeSurface.virtualXResolution / 2;
+ _vbeSurface.centerY = _vbeSurface.yResolution / 2;
+ _vbeSurface.numberOfOffscreens = vbeSetScanlineLength(_vbeModeInfo.xResolution) / _vbeModeInfo.yResolution;
+ _vbeSurface.vbeInitBoolean = 1;
+ _vbeSurface.screenBytes = (_vbeSurface.xResolution * _vbeSurface.yResolution * _vbeSurface.bytesPerPixel);
+ _vbeSurface.screenDWords = _vbeSurface.screenBytes / 4;
+ _vbeSurface.lfbMemSize = (_vbeSurface.xResolution * _vbeSurface.yResolution * _vbeSurface.bytesPerPixel) / 1024 + _vbeModeInfo.offScreenMemSize;
+
+ for (counter = 0; counter < (_vbeSurface.lfbMemSize * 1024); counter++) {
+ _farpokeb(_vbeSurface.lfbSelector, counter, 0x0); // clear Lfb
+ }
+
+ if (_vbeModeInfo.memoryModel == VBE_MM_PACKED) {
+ vbeCreatePalette();
+
+ _vbeModeInfo.redFieldPosition = 5;
+ _vbeModeInfo.greenFieldPosition = 2;
+ _vbeModeInfo.blueFieldPosition = 0;
+ _vbeModeInfo.rsvdFieldPosition = 8;
+
+ _vbeModeInfo.redMaskSize = 3;
+ _vbeModeInfo.greenMaskSize = 3;
+ _vbeModeInfo.blueMaskSize = 2;
+ _vbeModeInfo.rsvdMaskSize = 0;
+ }
+
+ _vbeSurface.rMask = ((1UL << _vbeModeInfo.redMaskSize) - 1) << _vbeModeInfo.redFieldPosition;
+ _vbeSurface.gMask = ((1UL << _vbeModeInfo.greenMaskSize) - 1) << _vbeModeInfo.greenFieldPosition;
+ _vbeSurface.bMask = ((1UL << _vbeModeInfo.blueMaskSize) - 1) << _vbeModeInfo.blueFieldPosition;
+ _vbeSurface.aMask = ((1UL << _vbeModeInfo.rsvdMaskSize) - 1) << _vbeModeInfo.rsvdFieldPosition;
+
+ _vbeSurface.rShift = 8 - _vbeModeInfo.redMaskSize;
+ _vbeSurface.gShift = 8 - _vbeModeInfo.greenMaskSize;
+ _vbeSurface.bShift = 8 - _vbeModeInfo.blueMaskSize;
+ _vbeSurface.aShift = 8 - _vbeModeInfo.rsvdMaskSize;
+
+ _vbeSurface.rPos = _vbeModeInfo.redFieldPosition;
+ _vbeSurface.gPos = _vbeModeInfo.greenFieldPosition;
+ _vbeSurface.bPos = _vbeModeInfo.blueFieldPosition;
+ _vbeSurface.aPos = _vbeModeInfo.rsvdFieldPosition;
+
+ /*
+ logWrite("VESA Red Mask %u Shift %u Loss %u\n", _vbeSurface.rMask, _vbeSurface.rPos, _vbeSurface.rShift);
+ logWrite("VESA Green Mask %u Shift %u Loss %u\n", _vbeSurface.gMask, _vbeSurface.gPos, _vbeSurface.gShift);
+ logWrite("VESA Blue Mask %u Shift %u Loss %u\n", _vbeSurface.bMask, _vbeSurface.bPos, _vbeSurface.bShift);
+ logWrite("VESA Alpha Mask %u Shift %u Loss %u\n\n", _vbeSurface.aMask, _vbeSurface.aPos, _vbeSurface.aShift);
+ */
+
+ return(&_vbeSurface);
+}
+
+
+static uint16_t vbeSetScanlineLength(uint16_t pixelLength) {
+ __dpmi_regs r;
+
+ if (_vbeSurface.vbeBoolean == 0) return(0);
+
+ r.x.ax = 0x4F06;
+ r.x.bx = 0x0000;
+ r.x.cx = pixelLength;
+ __dpmi_int(0x10, &r);
+ if (r.h.ah != 0) return(0);
+ if (r.x.cx != pixelLength) return(0);
+
+ _vbeSurface.virtualXResolution = pixelLength;
+
+ return(r.x.dx);
+}
+
+
+void videoBlit(int16_t targetX, int16_t targetY, SurfaceT *source) {
+
+ //***TODO*** Does not handle partial blits at this time.
+ (void)targetX;
+ (void)targetY;
+
+ _movedatal(_my_ds(), (int32_t)source->buffer.bits32, _vbeSurface.lfbSelector, 0x0, _vbeSurface.screenDWords);
+}
+
+
+uint16_t videoDisplayHeightGet(void) {
+ return _vbeSurface.yResolution;
+}
+
+
+uint16_t videoDisplayWidthGet(void) {
+ return _vbeSurface.xResolution;
+}
+
+
+void videoModesShow(void) {
+ int8_t counter;
+
+ // 0 1 2 3 4 5 6 7 8
+ // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
+ //printf("VBE 2.0 driver v1.0 (c) 2021, Scott Duensing \n");
+ //printf("Based on: VBE 2.0 driver v1.0 (c) 1999, Tobias Koch \n\n");
+
+ if (vbeGetInfo() == NULL) {
+ logWrite("No VESA BIOS Extensions found.\n");
+ platformShutdown();
+ return;
+ }
+ logWrite(
+ "Video Memory - %d KB\n"
+ "VBE Version - %d.%d detected\n"
+ "OEM Specification - %s\n",
+ _vbeInfo.totalMemory * 64,
+ _vbeInfo.vbeVersion >> 8, _vbeInfo.vbeVersion & 0x00FF,
+ _vbeInfo.oemStringPtr);
+
+ if (_vbeInfo.vbeVersion >= 0x0200) {
+ logWrite(
+ "OEM Software Revision - %d.%d\n"
+ "OEM Vendor Name - %s\n"
+ "OEM Product Name - %s\n"
+ "OEM Product Revision - %s\n"
+ "Protected Mode Interface - %s\n\n",
+ _vbeInfo.oemSoftwareRev >> 8, _vbeInfo.oemSoftwareRev & 0x00FF,
+ _vbeInfo.oemVendorNamePtr,
+ _vbeInfo.oemProductNamePtr,
+ _vbeInfo.oemProductRevPtr,
+ vbeGetPmodeInterface() ? "Found" : "Missing");
+ } else {
+ logWrite("VESA BIOS Extension 2.0 or better required!\n");
+ platformShutdown();
+ return;
+ }
+
+ for (counter=0; ; counter++) {
+ if (_vbeInfo.videoModePtr[counter] == 0xFFFF) break;
+ vbeGetModeInfo(_vbeInfo.videoModePtr[counter]);
+ if (vbeIsDesiredMode()) {
+ logWrite("Mode %Xh - %4d x %4d x %2d - RGB %d:%d:%d - %s\n",
+ _vbeInfo.videoModePtr[counter],
+ _vbeModeInfo.xResolution,
+ _vbeModeInfo.yResolution,
+ _vbeModeInfo.bitsPerPixel,
+ _vbeModeInfo.redMaskSize,
+ _vbeModeInfo.greenMaskSize,
+ _vbeModeInfo.blueMaskSize,
+ ((_vbeModeInfo.memoryModel / 2) - 2) ? "DCOLOR" : "PACKED");
+ }
+ }
+
+ platformShutdown();
+}
+
+
+#endif // __DJGPP__
diff --git a/dyn/kpsplatf/kpsplatf.h b/dyn/kpsplatf/kpsplatf.h
new file mode 100644
index 0000000..c2ddcad
--- /dev/null
+++ b/dyn/kpsplatf/kpsplatf.h
@@ -0,0 +1,71 @@
+/*
+ * Roo/E, the Kangaroo Punch Portable GUI Toolkit
+ * Copyright (C) 2026 Scott Duensing
+ *
+ * http://kangaroopunch.com
+ *
+ *
+ * This file is part of Roo/E.
+ *
+ * Roo/E is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Roo/E. If not, see .
+ *
+ */
+
+
+#ifndef KPSPLATF_H
+#define KPSPLATF_H
+
+
+#include "roo_e.h"
+#include "kpssurf.h"
+
+
+#define EVENT_FLAG_KEYPRESS 1
+#define EVENT_FLAG_LEFT_DOWN 2
+#define EVENT_FLAG_LEFT_UP 4
+#define EVENT_FLAG_RIGHT_DOWN 8
+#define EVENT_FLAG_RIGHT_UP 16
+
+#define BUTTON_LEFT 1
+#define BUTTON_RIGHT 2
+
+#define META_ALT 1
+#define META_CTRL 2
+#define META_SHIFT 4
+
+#define KEY_ESC 27
+
+
+typedef struct PlatformEventS {
+ int32_t flags;
+ int32_t x;
+ int32_t y;
+ int32_t buttons;
+ int32_t key;
+ int32_t kbstat;
+ int32_t dtime;
+} PlatformEventT;
+
+
+void platformEventGet(PlatformEventT *event);
+void platformShutdown(void);
+uint8_t platformStartup(int16_t width, int16_t height, int16_t depth);
+
+void videoBlit(int16_t targetX, int16_t targetY, SurfaceT *source);
+uint16_t videoDisplayHeightGet(void);
+uint16_t videoDisplayWidthGet(void);
+void videoModesShow(void);
+
+
+#endif // KPSPLATF_H
diff --git a/dyn/kpsplatf/sdl2.c b/dyn/kpsplatf/sdl2.c
new file mode 100644
index 0000000..52c8c59
--- /dev/null
+++ b/dyn/kpsplatf/sdl2.c
@@ -0,0 +1,230 @@
+/*
+ * Roo/E, the Kangaroo Punch Portable GUI Toolkit
+ * Copyright (C) 2026 Scott Duensing
+ *
+ * http://kangaroopunch.com
+ *
+ *
+ * This file is part of Roo/E.
+ *
+ * Roo/E is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Roo/E. If not, see .
+ *
+ */
+
+
+#ifdef __linux__
+
+
+#include
+#include "kpsplatf.h"
+
+
+static SDL_Window *_window = NULL;
+static SDL_Renderer *_renderer = NULL;
+static SDL_Texture *_texture = NULL;
+static uint16_t _width = 0;
+static uint16_t _height = 0;
+static uint8_t _windowScale = 1;
+
+
+void platformEventGet(PlatformEventT *event) {
+ SDL_Event e;
+ static uint8_t ASCII = 0;
+ static uint8_t debounce = 0;
+ static uint8_t buttons = 0;
+ static int16_t x = 0;
+ static int16_t y = 0;
+
+ memset(event, 0, sizeof(EventT));
+
+ while (SDL_PollEvent(&e)) {
+ switch (e.type) {
+ case SDL_MOUSEMOTION:
+ x = e.motion.x;
+ y = e.motion.y;
+ break;
+
+ case SDL_MOUSEBUTTONUP:
+ if (e.button.button == SDL_BUTTON_LEFT) {
+ buttons &= ~BUTTON_LEFT;
+ event->flags |= EVENT_FLAG_LEFT_UP;
+ }
+ if (e.button.button == SDL_BUTTON_RIGHT) {
+ buttons &= ~BUTTON_RIGHT;
+ event->flags |= EVENT_FLAG_RIGHT_UP;
+ }
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ if (e.button.button == SDL_BUTTON_LEFT) {
+ buttons |= BUTTON_LEFT;
+ event->flags |= EVENT_FLAG_LEFT_DOWN;
+ }
+ if (e.button.button == SDL_BUTTON_RIGHT) {
+ buttons |= BUTTON_RIGHT;
+ event->flags |= EVENT_FLAG_RIGHT_DOWN;
+ }
+ break;
+
+ case SDL_KEYUP:
+ ASCII = 0;
+ debounce = 0;
+ break;
+
+ case SDL_KEYDOWN:
+ if (debounce == 0) {
+ if (e.key.keysym.scancode != 0) {
+ // Not a meta key
+ debounce = 1;
+ ASCII = e.key.keysym.sym;
+ //if (e.key.keysym.scancode == SDL_SCANCODE_ESCAPE) ASCII = 27;
+ event->key = ASCII;
+ event->flags |= EVENT_FLAG_KEYPRESS;
+ }
+ }
+ break;
+ }
+ }
+
+ event->x = x;
+ event->y = y;
+ event->buttons = buttons;
+ if ((SDL_GetModState() & KMOD_ALT) != 0) event->kbstat |= META_ALT;
+ if ((SDL_GetModState() & KMOD_CTRL) != 0) event->kbstat |= META_CTRL;
+ if ((SDL_GetModState() & KMOD_SHIFT) != 0) event->kbstat |= META_SHIFT;
+}
+
+
+void platformShutdown(void) {
+
+ SDL_ShowCursor(SDL_ENABLE);
+
+ surfaceShutdown();
+
+ if (_texture) {
+ SDL_DestroyTexture(_texture);
+ _texture = NULL;
+ }
+
+ if (_renderer) {
+ SDL_DestroyRenderer(_renderer);
+ _renderer = NULL;
+ }
+
+ if (_window) {
+ SDL_DestroyWindow(_window);
+ _window = NULL;
+ }
+
+ SDL_Quit();
+}
+
+
+uint8_t platformStartup(int16_t width, int16_t height, int16_t depth) {
+ SDL_PixelFormatEnum pixelFormat = SDL_PIXELFORMAT_ARGB8888;
+
+ (void)depth;
+
+ /*
+ SDL_Surface *bits8 = SDL_CreateRGBSurfaceWithFormat(0, 320, 200, 8, SDL_PIXELFORMAT_RGB332);
+ SDL_Surface *bits16 = SDL_CreateRGBSurfaceWithFormat(0, 320, 200, 8, SDL_PIXELFORMAT_RGB565);
+ SDL_Surface *bits32 = SDL_CreateRGBSurfaceWithFormat(0, 320, 200, 8, SDL_PIXELFORMAT_ARGB8888);
+
+ logWrite("8 Red Mask %u Shift %u Loss %u\n", bits8->format->Rmask, bits8->format->Rshift, bits8->format->Rloss);
+ logWrite("8 Green Mask %u Shift %u Loss %u\n", bits8->format->Gmask, bits8->format->Gshift, bits8->format->Gloss);
+ logWrite("8 Blue Mask %u Shift %u Loss %u\n", bits8->format->Bmask, bits8->format->Bshift, bits8->format->Bloss);
+ logWrite("8 Alpha Mask %u Shift %u Loss %u\n\n", bits8->format->Amask, bits8->format->Ashift, bits8->format->Aloss);
+
+ logWrite("16 Red Mask %u Shift %u Loss %u\n", bits16->format->Rmask, bits16->format->Rshift, bits16->format->Rloss);
+ logWrite("16 Green Mask %u Shift %u Loss %u\n", bits16->format->Gmask, bits16->format->Gshift, bits16->format->Gloss);
+ logWrite("16 Blue Mask %u Shift %u Loss %u\n", bits16->format->Bmask, bits16->format->Bshift, bits16->format->Bloss);
+ logWrite("16 Alpha Mask %u Shift %u Loss %u\n\n", bits16->format->Amask, bits16->format->Ashift, bits16->format->Aloss);
+
+ logWrite("32 Red Mask %u Shift %u Loss %u\n", bits32->format->Rmask, bits32->format->Rshift, bits32->format->Rloss);
+ logWrite("32 Green Mask %u Shift %u Loss %u\n", bits32->format->Gmask, bits32->format->Gshift, bits32->format->Gloss);
+ logWrite("32 Blue Mask %u Shift %u Loss %u\n", bits32->format->Bmask, bits32->format->Bshift, bits32->format->Bloss);
+ logWrite("32 Alpha Mask %u Shift %u Loss %u\n\n", bits32->format->Amask, bits32->format->Ashift, bits32->format->Aloss);
+ */
+
+ switch (depth) {
+ case 8:
+ pixelFormat = SDL_PIXELFORMAT_RGB332;
+ break;
+
+ case 16:
+ pixelFormat = SDL_PIXELFORMAT_RGB565;
+ break;
+
+ case 32:
+ pixelFormat = SDL_PIXELFORMAT_ARGB8888;
+ break;
+ }
+
+ SDL_Init(SDL_INIT_EVERYTHING);
+
+ _windowScale = 1;
+
+ _window = SDL_CreateWindow("GUI Debug", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_ALLOW_HIGHDPI);
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ _texture = SDL_CreateTexture(_renderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, width, height);
+
+ SDL_RenderSetLogicalSize(_renderer, width, height);
+ SDL_SetWindowSize(_window, width * _windowScale, height * _windowScale);
+
+ SDL_ShowCursor(SDL_DISABLE);
+
+ _width = width;
+ _height = height;
+
+ surfaceStartup(depth);
+
+ return SUCCESS;
+}
+
+
+void videoBlit(int16_t targetX, int16_t targetY, SurfaceT *source) {
+ void *pixels;
+ int temp;
+
+ //***TODO*** Does not handle partial blits at this time.
+ (void)targetX;
+ (void)targetY;
+
+ SDL_LockTexture(_texture, NULL, &pixels, &temp);
+ memcpy(pixels, source->buffer.bits8, source->bytes);
+ SDL_UnlockTexture(_texture);
+ SDL_RenderCopy(_renderer, _texture, NULL, NULL);
+ SDL_RenderPresent(_renderer);
+
+ // Throttle this to some sane frame rate.
+ SDL_Delay(32);
+}
+
+
+uint16_t videoDisplayHeightGet(void) {
+ return _height;
+}
+
+
+uint16_t videoDisplayWidthGet(void) {
+ return _width;
+}
+
+
+void videoModesShow(void) {
+ // Nada
+}
+
+
+#endif // __linux__
diff --git a/dyn/kpssurf/kpssurf.c b/dyn/kpssurf/kpssurf.c
new file mode 100644
index 0000000..dc8caad
--- /dev/null
+++ b/dyn/kpssurf/kpssurf.c
@@ -0,0 +1,570 @@
+/*
+ * Roo/E, the Kangaroo Punch Portable GUI Toolkit
+ * Copyright (C) 2026 Scott Duensing
+ *
+ * http://kangaroopunch.com
+ *
+ *
+ * This file is part of Roo/E.
+ *
+ * Roo/E is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Roo/E. If not, see .
+ *
+ */
+
+
+#include "kpssurf.h"
+
+
+SurfaceT *__surfaceActive = NULL;
+uint8_t __surfaceBitsPerPixel = 0;
+uint8_t __surfaceBytesPerPixel = 0;
+SurfaceFormatT __surfaceFormat = { 0 };
+
+
+void (*surfaceLineH)(int16_t x1, int16_t x2, int16_t y, ColorT c);
+void (*surfaceLineV)(int16_t x, int16_t y1, int16_t y2, ColorT c);
+ColorT (*surfacePixelGet)(SurfaceT *surface, int16_t x, int16_t y);
+void (*surfacePixelSet)(uint16_t x, uint16_t y, ColorT color);
+
+
+static void surfaceLineH8(int16_t x1, int16_t x2, int16_t y, ColorT c);
+static void surfaceLineH16(int16_t x1, int16_t x2, int16_t y, ColorT c);
+static void surfaceLineH32(int16_t x1, int16_t x2, int16_t y, ColorT c);
+
+static void surfaceLineV8(int16_t x, int16_t y1, int16_t y2, ColorT c);
+static void surfaceLineV16(int16_t x, int16_t y1, int16_t y2, ColorT c);
+static void surfaceLineV32(int16_t x, int16_t y1, int16_t y2, ColorT c);
+
+static ColorT surfacePixelGet8(SurfaceT *surface, int16_t x, int16_t y);
+static ColorT surfacePixelGet16(SurfaceT *surface, int16_t x, int16_t y);
+static ColorT surfacePixelGet32(SurfaceT *surface, int16_t x, int16_t y);
+
+static void surfacePixelSet8(uint16_t x, uint16_t y, ColorT color);
+static void surfacePixelSet16(uint16_t x, uint16_t y, ColorT color);
+static void surfacePixelSet32(uint16_t x, uint16_t y, ColorT color);
+
+
+void surfaceBlit(int16_t targetX, int16_t targetY, int16_t offsetX, int16_t offsetY, int16_t width, int16_t height, SurfaceT *source) {
+ int16_t i;
+ int16_t x = 0;
+ int16_t y = 0;
+ size_t bytes;
+ size_t offsetTarget;
+ size_t offsetSource;
+
+ // Did they provide a valid width/height?
+ if (width <= 0 || width > source->width) width = source->width;
+ if (height <= 0 || height > source->height) height = source->height;
+
+ // Clip on top and left. x1 & y1 are pixel locations inside the source bitmap.
+ if (targetX < 0) x = -targetX;
+ if (targetY < 0) y = -targetY;
+
+ // Clip on right and bottom.
+ if (targetX + width - offsetX > __surfaceActive->width) width -= targetX + width - offsetX - __surfaceActive->width;
+ if (targetY + height - offsetY > __surfaceActive->height) height -= targetY + height - offsetY - __surfaceActive->height;
+
+ // Are we still on the screen?
+ if (x < 0 || y < 0 || width < x || height < y) return;
+
+ if (targetX == 0 && targetY == 0 && __surfaceActive->width == source->width && __surfaceActive->height == source->height) {
+ // Direct blit of entire surface.
+ memcpy(__surfaceActive->buffer.bits8, source->buffer.bits8, source->bytes);
+ } else {
+ // Blit into larger surface.
+ offsetTarget = (targetY + y) * __surfaceActive->scanline + (targetX + x) * __surfaceBytesPerPixel;
+ offsetSource = (y + offsetY) * source->scanline + (x + offsetX) * __surfaceBytesPerPixel;
+ bytes = (width - x) * __surfaceBytesPerPixel;
+ for (i=y; ibuffer.bits8[offsetTarget], &source->buffer.bits8[offsetSource], bytes);
+ offsetTarget += __surfaceActive->scanline;
+ offsetSource += source->scanline;
+ }
+ }
+}
+
+
+void surfaceBlitWithTransparency(int16_t targetX, int16_t targetY, SurfaceT *source, ColorT transparent) {
+ int16_t x;
+ int16_t y;
+ int16_t x1 = 0;
+ int16_t y1 = 0;
+ int16_t x2 = source->width;
+ int16_t y2 = source->height;
+ ColorT pixel;
+
+ // Clip on top and left. x1 & y1 are pixel locations inside the source bitmap. ox & oy offset those into screen coordinates.
+ if (targetX < 0) x1 = -targetX;
+ if (targetY < 0) y1 = -targetY;
+
+ // Clip on right and bottom.
+ if (targetX + x2 > __surfaceActive->width) x2 -= targetX + x2 - __surfaceActive->width;
+ if (targetY + y2 > __surfaceActive->height) y2 -= targetY + y2 - __surfaceActive->height;
+
+ // Are we still on the screen?
+ if (x1 < 0 || y1 < 0 || x2 < x1 || y2 < y1) return;
+
+ // Blit.
+ for (y=y1; ywidth - 1, 0, color);
+
+ // Copy it to the other lines.
+ offsetTarget = __surfaceActive->scanline;
+ for (x=1; x<__surfaceActive->height; x++) {
+ memcpy(&__surfaceActive->buffer.bits8[offsetTarget], &__surfaceActive->buffer.bits8[0], __surfaceActive->scanline);
+ offsetTarget += __surfaceActive->scanline;
+ }
+}
+
+
+ColorT surfaceColorMake(uint8_t r, uint8_t g, uint8_t b) {
+ return
+ (r >> __surfaceFormat.rLoss) << __surfaceFormat.rShift |
+ (g >> __surfaceFormat.gLoss) << __surfaceFormat.gShift |
+ (b >> __surfaceFormat.bLoss) << __surfaceFormat.bShift |
+ ((255 >> __surfaceFormat.aLoss) << __surfaceFormat.aShift & __surfaceFormat.aMask);
+}
+
+
+SurfaceT *surfaceCreate(int16_t width, int16_t height) {
+ SurfaceT *surface = NULL;
+
+ NEW(SurfaceT, surface);
+ if (!surface) return NULL;
+
+ surface->width = width;
+ surface->height = height;
+ surface->scanline = width * __surfaceBytesPerPixel;
+ surface->bytes = surface->scanline * height;
+
+ surface->buffer.bits8 = malloc(surface->bytes);
+ if (!surface->buffer.bits8) {
+ free(surface);
+ return NULL;
+ }
+
+ return surface;
+}
+
+
+void surfaceBox(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c) {
+ surfaceLineH(x1, x2, y1, c);
+ surfaceLineH(x1, x2, y2, c);
+ surfaceLineV(x1, y1, y2, c);
+ surfaceLineV(x2, y1, y2, c);
+}
+
+
+void surfaceBoxFilled(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c) {
+ int16_t i;
+ size_t offsetTarget;
+ size_t offsetSource;
+ uint16_t width;
+
+ if (x1 > x2) {
+ i = x1;
+ x1 = x2;
+ x2 = i;
+ }
+
+ if (y1 > y2) {
+ i = y1;
+ y1 = y2;
+ y2 = i;
+ }
+
+ width = (x2 - x1 + 1) * __surfaceBytesPerPixel;
+
+ // Draw the top line.
+ surfaceLineH(x1, x2, y1, c);
+
+ // Copy it to the other lines.
+ offsetTarget = __surfaceActive->scanline * (y1 + 1) + (x1 * __surfaceBytesPerPixel);
+ offsetSource = __surfaceActive->scanline * y1 + (x1 * __surfaceBytesPerPixel);
+ for (i=y1 + 1; i<=y2; i++) {
+ memcpy(&__surfaceActive->buffer.bits8[offsetTarget], &__surfaceActive->buffer.bits8[offsetSource], width);
+ offsetTarget += __surfaceActive->scanline;
+ }
+}
+
+
+void surfaceBoxHighlight(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT highlight, ColorT shadow) {
+ surfaceLineH(x1, x2, y1, highlight);
+ surfaceLineV(x1, y1, y2, highlight);
+ surfaceLineH(x1, x2, y2, shadow);
+ surfaceLineV(x2, y1, y2, shadow);
+}
+
+
+void surfaceDestroy(SurfaceT **surface) {
+ SurfaceT *s = *surface;
+ DEL(s->buffer.bits8);
+ DEL(s);
+ *surface = NULL;
+}
+
+
+SurfaceT *surfaceGet(void) {
+ return __surfaceActive;
+}
+
+
+int16_t surfaceHeightGet(SurfaceT *surface) {
+ return surface->height;
+}
+
+
+void surfaceLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT color) {
+ int16_t x;
+ int16_t y;
+ int16_t dx;
+ int16_t dy;
+ int16_t incX;
+ int16_t incY;
+ int16_t balance;
+
+ if (x2 >= x1) {
+ dx = x2 - x1;
+ incX = 1;
+ } else {
+ dx = x1 - x2;
+ incX = -1;
+ }
+
+ if (y2 >= y1) {
+ dy = y2 - y1;
+ incY = 1;
+ } else {
+ dy = y1 - y2;
+ incY = -1;
+ }
+
+ x = x1;
+ y = y1;
+
+ if (dx >= dy) {
+ dy <<= 1;
+ balance = dy - dx;
+ dx <<= 1;
+ while (x != x2) {
+ surfacePixelSet(x, y, color);
+ if (balance >= 0) {
+ y += incY;
+ balance -= dx;
+ }
+ balance += dy;
+ x += incX;
+ }
+ surfacePixelSet(x, y, color);
+ } else {
+ dx <<= 1;
+ balance = dx - dy;
+ dy <<= 1;
+ while (y != y2) {
+ surfacePixelSet(x, y, color);
+ if (balance >= 0) {
+ x += incX;
+ balance -= dy;
+ }
+ balance += dx;
+ y += incY;
+ }
+ surfacePixelSet(x, y, color);
+ }
+}
+
+
+static void surfaceLineH8(int16_t x1, int16_t x2, int16_t y, ColorT c) {
+ int16_t i;
+ size_t offset;
+
+ if (x1 > x2) {
+ i = x2;
+ x2 = x1;
+ x1 = i;
+ }
+
+ offset = y * __surfaceActive->width + x1;
+ for (i=x1; i<=x2; i++) __surfaceActive->buffer.bits8[offset++] = (uint8_t)c;
+}
+
+
+static void surfaceLineH16(int16_t x1, int16_t x2, int16_t y, ColorT c) {
+ int16_t i;
+ size_t offset;
+
+ if (x1 > x2) {
+ i = x2;
+ x2 = x1;
+ x1 = i;
+ }
+
+ offset = y * __surfaceActive->width + x1;
+ for (i=x1; i<=x2; i++) __surfaceActive->buffer.bits16[offset++] = (uint16_t)c;
+}
+
+
+static void surfaceLineH32(int16_t x1, int16_t x2, int16_t y, ColorT c) {
+ int16_t i;
+ size_t offset;
+
+ if (x1 > x2) {
+ i = x2;
+ x2 = x1;
+ x1 = i;
+ }
+
+ offset = y * __surfaceActive->width + x1;
+ for (i=x1; i<=x2; i++) __surfaceActive->buffer.bits32[offset++] = (uint32_t)c;
+}
+
+
+static void surfaceLineV8(int16_t x, int16_t y1, int16_t y2, ColorT c) {
+ int16_t i;
+ size_t offset;
+
+ if (y1 > y2) {
+ i = y2;
+ y2 = y1;
+ y1 = i;
+ }
+
+ offset = y1 * __surfaceActive->width + x;
+ for (i=y1; i<=y2; i++) {
+ __surfaceActive->buffer.bits8[offset] = (uint8_t)c;
+ offset += __surfaceActive->width;
+ }
+}
+
+
+static void surfaceLineV16(int16_t x, int16_t y1, int16_t y2, ColorT c) {
+ int16_t i;
+ size_t offset;
+
+ if (y1 > y2) {
+ i = y2;
+ y2 = y1;
+ y1 = i;
+ }
+
+ offset = y1 * __surfaceActive->width + x;
+ for (i=y1; i<=y2; i++) {
+ __surfaceActive->buffer.bits16[offset] = (uint16_t)c;
+ offset += __surfaceActive->width;
+ }
+}
+
+
+static void surfaceLineV32(int16_t x, int16_t y1, int16_t y2, ColorT c) {
+ int16_t i;
+ size_t offset;
+
+ if (y1 > y2) {
+ i = y2;
+ y2 = y1;
+ y1 = i;
+ }
+
+ offset = y1 * __surfaceActive->width + x;
+ for (i=y1; i<=y2; i++) {
+ __surfaceActive->buffer.bits32[offset] = (uint32_t)c;
+ offset += __surfaceActive->width;
+ }
+}
+
+
+SurfaceT *surfaceLoad(char *filename) {
+ char ext[5] = { 0 };
+ char *name = NULL;
+ FILE *in = NULL;
+ SurfaceT *i = NULL;
+
+ sprintf(ext, "S%d", __surfaceBitsPerPixel);
+ name = utilFileExtensionChange(filename, ext);
+
+ if (!utilFileExists(name)) {
+ in = fopen(name, "rb");
+ if (in) {
+ NEW(SurfaceT, i);
+ if (!i) return NULL;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
+ fread(&i->width, sizeof(uint16_t), 1, in);
+ fread(&i->height, sizeof(uint16_t), 1, in);
+ fread(&i->scanline, sizeof(size_t), 1, in);
+ fread(&i->bytes, sizeof(size_t), 1, in);
+ i->buffer.bits8 = (uint8_t *)malloc(i->bytes);
+ fread(i->buffer.bits8, i->bytes, 1, in);
+#pragma GCC diagnostic pop
+ fclose(in);
+ }
+ }
+
+ DEL(name);
+
+ return i;
+}
+
+
+static ColorT surfacePixelGet8(SurfaceT *surface, int16_t x, int16_t y) {
+ return surface->buffer.bits8[y * surface->width + x];
+}
+
+
+static ColorT surfacePixelGet16(SurfaceT *surface, int16_t x, int16_t y) {
+ return surface->buffer.bits16[y * surface->width + x];
+}
+
+
+static ColorT surfacePixelGet32(SurfaceT *surface, int16_t x, int16_t y) {
+ return surface->buffer.bits32[y * surface->width + x];
+}
+
+
+static void surfacePixelSet8(uint16_t x, uint16_t y, ColorT color) {
+ __surfaceActive->buffer.bits8[y * __surfaceActive->width + x] = (uint8_t)color;
+}
+
+
+static void surfacePixelSet16(uint16_t x, uint16_t y, ColorT color) {
+ __surfaceActive->buffer.bits16[y * __surfaceActive->width + x] = (uint16_t)color;
+}
+
+
+static void surfacePixelSet32(uint16_t x, uint16_t y, ColorT color) {
+ __surfaceActive->buffer.bits32[y * __surfaceActive->width + x] = color;
+}
+
+
+void surfaceSave(SurfaceT *surface, char *filename) {
+ char ext[5] = { 0 };
+ char *name = NULL;
+ FILE *out = NULL;
+
+ sprintf(ext, "S%d", __surfaceBitsPerPixel);
+ name = utilFileExtensionChange(filename, ext);
+
+ out = fopen(name, "wb");
+ if (out) {
+ fwrite(&surface->width, sizeof(uint16_t), 1, out);
+ fwrite(&surface->height, sizeof(uint16_t), 1, out);
+ fwrite(&surface->scanline, sizeof(size_t), 1, out);
+ fwrite(&surface->bytes, sizeof(size_t), 1, out);
+ fwrite(surface->buffer.bits8, surface->bytes, 1, out);
+ fclose(out);
+ }
+
+ DEL(name);
+}
+
+
+void surfaceSet(SurfaceT *surface) {
+ __surfaceActive = surface;
+}
+
+
+void surfaceShutdown(void) {
+ // Nada
+}
+
+
+void surfaceStartup(uint8_t bits) {
+ uint8_t redMaskSize;
+ uint8_t greenMaskSize;
+ uint8_t blueMaskSize;
+ uint8_t alphaMaskSize;
+
+ __surfaceBitsPerPixel = bits;
+ __surfaceBytesPerPixel = bits >> 3;
+
+ switch (bits) {
+ case 8:
+ // xxx 3:3:2
+ alphaMaskSize = 0;
+ redMaskSize = 3;
+ greenMaskSize = 3;
+ blueMaskSize = 2;
+ surfaceLineH = surfaceLineH8;
+ surfaceLineV = surfaceLineV8;
+ surfacePixelSet = surfacePixelSet8;
+ surfacePixelGet = surfacePixelGet8;
+ break;
+
+ case 16:
+ // xx 5:6:5
+ alphaMaskSize = 0;
+ redMaskSize = 5;
+ greenMaskSize = 6;
+ blueMaskSize = 5;
+ surfaceLineH = surfaceLineH16;
+ surfaceLineV = surfaceLineV16;
+ surfacePixelSet = surfacePixelSet16;
+ surfacePixelGet = surfacePixelGet16;
+ break;
+
+ default:
+ // x 8:8:8
+ alphaMaskSize = 8;
+ redMaskSize = 8;
+ greenMaskSize = 8;
+ blueMaskSize = 8;
+ surfaceLineH = surfaceLineH32;
+ surfaceLineV = surfaceLineV32;
+ surfacePixelSet = surfacePixelSet32;
+ surfacePixelGet = surfacePixelGet32;
+ break;
+ }
+
+ __surfaceFormat.bShift = 0;
+ __surfaceFormat.gShift = __surfaceFormat.bShift + blueMaskSize;
+ __surfaceFormat.rShift = __surfaceFormat.gShift + greenMaskSize;
+ __surfaceFormat.aShift = __surfaceFormat.rShift + redMaskSize;
+
+ __surfaceFormat.rMask = ((1UL << redMaskSize) - 1) << __surfaceFormat.rShift;
+ __surfaceFormat.gMask = ((1UL << greenMaskSize) - 1) << __surfaceFormat.gShift;
+ __surfaceFormat.bMask = ((1UL << blueMaskSize) - 1) << __surfaceFormat.bShift;
+ __surfaceFormat.aMask = ((1UL << alphaMaskSize) - 1) << __surfaceFormat.aShift;
+
+ __surfaceFormat.rLoss = 8 - redMaskSize;
+ __surfaceFormat.gLoss = 8 - greenMaskSize;
+ __surfaceFormat.bLoss = 8 - blueMaskSize;
+ __surfaceFormat.aLoss = 8 - alphaMaskSize;
+
+ /*
+ logWrite("Surface Red Mask %u Shift %u Loss %u\n", __surfaceFormat.rMask, __surfaceFormat.rShift, __surfaceFormat.rLoss);
+ logWrite("Surface Green Mask %u Shift %u Loss %u\n", __surfaceFormat.gMask, __surfaceFormat.gShift, __surfaceFormat.gLoss);
+ logWrite("Surface Blue Mask %u Shift %u Loss %u\n", __surfaceFormat.bMask, __surfaceFormat.bShift, __surfaceFormat.bLoss);
+ logWrite("Surface Alpha Mask %u Shift %u Loss %u\n\n", __surfaceFormat.aMask, __surfaceFormat.aShift, __surfaceFormat.aLoss);
+ */
+}
+
+
+int16_t surfaceWidthGet(SurfaceT *surface) {
+ return surface->width;
+}
diff --git a/dyn/kpssurf/kpssurf.h b/dyn/kpssurf/kpssurf.h
new file mode 100644
index 0000000..328c0f3
--- /dev/null
+++ b/dyn/kpssurf/kpssurf.h
@@ -0,0 +1,95 @@
+/*
+ * Roo/E, the Kangaroo Punch Portable GUI Toolkit
+ * Copyright (C) 2026 Scott Duensing
+ *
+ * http://kangaroopunch.com
+ *
+ *
+ * This file is part of Roo/E.
+ *
+ * Roo/E is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Roo/E. If not, see .
+ *
+ */
+
+
+#ifndef KPSSURF_H
+#define KPSSURF_H
+
+
+#include "roo_e.h"
+
+
+typedef uint32_t ColorT;
+
+typedef struct SurfaceS {
+ uint16_t width;
+ uint16_t height;
+ size_t scanline;
+ size_t bytes;
+ union {
+ uint8_t *bits8;
+ uint16_t *bits16;
+ uint32_t *bits32;
+ } buffer;
+} SurfaceT;
+
+typedef struct SurfaceFormatS {
+ uint32_t rMask;
+ uint32_t gMask;
+ uint32_t bMask;
+ uint32_t aMask;
+ uint8_t rShift;
+ uint8_t gShift;
+ uint8_t bShift;
+ uint8_t aShift;
+ uint8_t rLoss;
+ uint8_t gLoss;
+ uint8_t bLoss;
+ uint8_t aLoss;
+} SurfaceFormatT;
+
+
+extern SurfaceT *__surfaceActive;
+extern uint8_t __surfaceBitsPerPixel;
+extern uint8_t __surfaceBytesPerPixel;
+extern SurfaceFormatT __surfaceFormat;
+
+
+extern void (*surfaceLineH)(int16_t x1, int16_t x2, int16_t y, ColorT c);
+extern void (*surfaceLineV)(int16_t x, int16_t y1, int16_t y2, ColorT c);
+extern ColorT (*surfacePixelGet)(SurfaceT *surface, int16_t x, int16_t y);
+extern void (*surfacePixelSet)(uint16_t x, uint16_t y, ColorT color);
+
+
+void surfaceBlit(int16_t targetX1, int16_t targetY1, int16_t offsetX, int16_t offsetY, int16_t width, int16_t height, SurfaceT *source);
+void surfaceBlitWithTransparency(int16_t targetX, int16_t targetY, SurfaceT *source, ColorT transparent);
+void surfaceClear(ColorT color);
+ColorT surfaceColorMake(uint8_t r, uint8_t g, uint8_t b);
+SurfaceT *surfaceCreate(int16_t width, int16_t height);
+void surfaceBox(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c);
+void surfaceBoxFilled(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c);
+void surfaceBoxHighlight(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT highlight, ColorT shadow);
+void surfaceDestroy(SurfaceT **surface);
+SurfaceT *surfaceGet(void);
+int16_t surfaceHeightGet(SurfaceT *surface);
+void surfaceLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT color);
+SurfaceT *surfaceLoad(char *filename);
+void surfaceSave(SurfaceT *surface, char *filename);
+void surfaceSet(SurfaceT *surface);
+void surfaceShutdown(void);
+void surfaceStartup(uint8_t bits);
+int16_t surfaceWidthGet(SurfaceT *surface);
+
+
+#endif // KPSSURF_H
diff --git a/dyn/kpsvideo/kpsvideo.c b/dyn/kpsvideo/kpsvideo.c
deleted file mode 100644
index d003d00..0000000
--- a/dyn/kpsvideo/kpsvideo.c
+++ /dev/null
@@ -1,37 +0,0 @@
-/* Roo/E, the Kangaroo Punch Portable GUI Toolkit
- * Copyright (C) 2026 Scott Duensing
- *
- * http://kangaroopunch.com
- *
- *
- * This file is part of Roo/E.
- *
- * Roo/E is free software: you can redistribute it and/or modify it under the
- * terms of the GNU Affero General Public License as published by the Free
- * Software Foundation, either version 3 of the License, or (at your option)
- * any later version.
- *
- * Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Roo/E. If not, see .
- */
-
-
-#include "roo_e.h"
-#include "kpsvideo.h"
-
-
-int dynStart(void) {
- printf("kpsvideo starting!\n");
- return 0;
-}
-
-
-int dynStop(void) {
- printf("kpsvideo stopping!\n");
- return 0;
-}
diff --git a/dyn/kpsvideo/kpsvideo.h b/dyn/kpsvideo/kpsvideo.h
deleted file mode 100644
index 9d54738..0000000
--- a/dyn/kpsvideo/kpsvideo.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* Roo/E, the Kangaroo Punch Portable GUI Toolkit
- * Copyright (C) 2026 Scott Duensing
- *
- * http://kangaroopunch.com
- *
- *
- * This file is part of Roo/E.
- *
- * Roo/E is free software: you can redistribute it and/or modify it under the
- * terms of the GNU Affero General Public License as published by the Free
- * Software Foundation, either version 3 of the License, or (at your option)
- * any later version.
- *
- * Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Roo/E. If not, see .
- */
-
-
-#ifndef KPSVIDEO_H
-#define KPSVIDEO_H
-
-#endif // KPSVIDEO_H
diff --git a/env.sh b/env.sh
index c323055..ddae6ba 100755
--- a/env.sh
+++ b/env.sh
@@ -25,4 +25,5 @@
eval "$(../toolchains/toolchains.sh use x86 dos)"
-export C_INCLUDE_PATH=${HOME}/code/toolchains/x-tools/djgpp/i586-pc-msdosdjgpp/sys-include:roo_e
+export CFLAGS="-DMEMWATCH"
+export C_INCLUDE_PATH="${HOME}/code/toolchains/x-tools/djgpp/i586-pc-msdosdjgpp/sys-include:roo_e"
diff --git a/font/src/main.c b/font/src/main.c
index ad10c0f..737c04e 100644
--- a/font/src/main.c
+++ b/font/src/main.c
@@ -23,8 +23,7 @@
#include
-#ifdef PLATFORM_LINUX
-#define MEMWATCH
+#ifdef MEMWATCH
#include "memwatch/memwatch.h"
#endif
@@ -90,7 +89,6 @@ int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
- // Run this in DOS from the BIN folder.
makeFont("../font/in/vga4x8.png", "fonts/vga4x8.fnt", 4, 8, 16, 256);
makeFont("../font/in/vga8x8.png", "fonts/vga8x8.fnt", 8, 8, 16, 256);
makeFont("../font/in/vga8x14.png", "fonts/vga8x14.fnt", 8, 14, 16, 256);
diff --git a/roo_e/main.c b/roo_e/main.c
index 4175daf..465f98f 100644
--- a/roo_e/main.c
+++ b/roo_e/main.c
@@ -27,12 +27,21 @@
static RooAppThreadT **_appThreadList = NULL; // NOLINT
+static FILE *_memoryLog = NULL; // NOLINT
+static FILE *_log = NULL; // NOLINT
+static uint8_t _ourHandle = 0; // NOLINT
+static char *_logBuffer = NULL; // NOLINT
+static uint16_t _logBufferSize = 0; // NOLINT
static void *rooStartAppThread(void *arg);
-#ifdef PLATFORM_DOS
+#ifdef MEMWATCH
+void mwLogW(FILE *p);
+#endif
+
+#ifdef __DJGPP__
#include
void *dxeResolver(const char *symbol);
static int lastResort(void);
@@ -53,15 +62,58 @@ struct __emutls_object // NOLINT
void *__emutls_get_address(struct __emutls_object *); // NOLINT
#pragma GCC diagnostic pop
DXE_EXPORT_TABLE(exports)
+ // bios
+ DXE_EXPORT(bioskey)
+ // ctype
+ DXE_EXPORT(toupper)
+ // dos
+ DXE_EXPORT(int86)
+ // dpmi
+ DXE_EXPORT(__dpmi_allocate_ldt_descriptors)
+ DXE_EXPORT(__dpmi_free_ldt_descriptor)
+ DXE_EXPORT(__dpmi_free_physical_address_mapping)
+ DXE_EXPORT(__dpmi_int)
+ DXE_EXPORT(__dpmi_lock_linear_region)
+ DXE_EXPORT(__dpmi_physical_address_mapping)
+ DXE_EXPORT(__dpmi_set_segment_base_address)
+ DXE_EXPORT(__dpmi_set_segment_limit)
+ DXE_EXPORT(__dpmi_unlock_linear_region)
+ DXE_EXPORT(_go32_dpmi_lock_data)
// dxe
DXE_EXPORT(dlclose)
DXE_EXPORT(dlopen)
DXE_EXPORT(dlregsym)
DXE_EXPORT(dlstatbind)
DXE_EXPORT(dlstatunbind)
+ // farptr
+ DXE_EXPORT(_farpokeb)
+ // go32
+ DXE_EXPORT(_go32_info_block)
// math
DXE_EXPORT(ldexp)
DXE_EXPORT(pow)
+ // movedata
+ DXE_EXPORT(_movedatal)
+ DXE_EXPORT(dosmemget)
+ DXE_EXPORT(dosmemput)
+ // pc
+ DXE_EXPORT(outportb)
+ // Roo/E
+ DXE_EXPORT(logClose)
+ DXE_EXPORT(logOpen)
+ DXE_EXPORT(logOpenByHandle)
+ DXE_EXPORT(logWrite)
+ DXE_EXPORT(logWriteToFileOnly)
+ DXE_EXPORT(utilCreateString)
+ DXE_EXPORT(utilCreateStringVArgs)
+ DXE_EXPORT(utilEndsWith)
+ DXE_EXPORT(utilFileExists)
+ DXE_EXPORT(utilFileExtensionChange)
+ // setjmp
+ DXE_EXPORT(longjmp)
+ DXE_EXPORT(setjmp)
+ // signal
+ DXE_EXPORT(signal)
// stb_ds
DXE_EXPORT(stbds_arrfreef)
DXE_EXPORT(stbds_arrgrowf)
@@ -81,26 +133,47 @@ DXE_EXPORT_TABLE(exports)
DXE_EXPORT(fclose)
DXE_EXPORT(feof)
DXE_EXPORT(ferror)
+ DXE_EXPORT(fflush)
DXE_EXPORT(fgetc)
+ DXE_EXPORT(fgets)
DXE_EXPORT(fopen)
+ DXE_EXPORT(fprintf)
+ DXE_EXPORT(fputc)
DXE_EXPORT(fread)
DXE_EXPORT(fseek)
DXE_EXPORT(ftell)
+ DXE_EXPORT(fwrite)
DXE_EXPORT(printf)
DXE_EXPORT(puts)
+ DXE_EXPORT(sprintf)
DXE_EXPORT(ungetc)
+ DXE_EXPORT(vsprintf)
// stdlib
+ DXE_EXPORT(abort)
+ DXE_EXPORT(atexit)
+ DXE_EXPORT(exit)
DXE_EXPORT(free)
+ DXE_EXPORT(calloc)
DXE_EXPORT(malloc)
DXE_EXPORT(realloc)
// string
DXE_EXPORT(memcpy)
DXE_EXPORT(memset)
+ DXE_EXPORT(strcat)
DXE_EXPORT(strcmp)
+ DXE_EXPORT(strcpy)
+ DXE_EXPORT(strlen)
DXE_EXPORT(strncmp)
+ DXE_EXPORT(strncpy)
DXE_EXPORT(strtol)
+ // time
+ DXE_EXPORT(ctime)
+ DXE_EXPORT(time)
// Compiler stuff.
DXE_EXPORT(__dj_assert)
+ DXE_EXPORT(__dj_ctype_toupper)
+ DXE_EXPORT(__dj_stdin)
+ DXE_EXPORT(__djgpp_base_address)
DXE_EXPORT(__emutls_get_address)
DXE_EXPORT_END
@@ -120,7 +193,101 @@ static int lastResort(void) {
printf("ROO/E: Last resort function called!\n");
return 0;
}
-#endif // PLATFORM_DOS
+#endif // __DJGPP__
+
+
+void logClose(void) {
+ if (_log && _ourHandle) {
+ fclose(_log);
+ _ourHandle = 0;
+ }
+ DEL(_logBuffer);
+}
+
+
+uint8_t logOpen(char *filename, uint8_t append) {
+ _log = fopen(filename, append ? "a" : "w");
+ if (_log) {
+ _ourHandle = 1;
+ return 1;
+ }
+ return 0;
+}
+
+
+void logOpenByHandle(FILE *handle) {
+ _log = handle;
+}
+
+
+void logWrite(char *format, ...) {
+ va_list args = { 0 };
+ va_list args2 = { 0 };
+ uint16_t length = 0;
+
+ if (_log) {
+ va_start(args, format);
+ va_copy(args2, args);
+
+ // Attempt to write into current log buffer.
+ length = vsnprintf(_logBuffer, _logBufferSize, format, args);
+
+ // Did it fit?
+ if (length >= _logBufferSize) {
+ // Nope. Resize buffer to fit plus a bit more.
+ if (_logBuffer) free(_logBuffer);
+ _logBufferSize = length + 32;
+ _logBuffer = (char *)malloc(_logBufferSize);
+ // Do it again.
+ vsnprintf(_logBuffer, _logBufferSize, format, args2);
+ }
+
+#ifdef __linux__
+ // Also output to stdout on Linux.
+ fprintf(stdout, "%s", _logBuffer);
+ fflush(stdout);
+#endif
+
+ fprintf(_log, "%s", _logBuffer);
+ fflush(_log);
+
+ va_end(args2);
+ va_end(args);
+ }
+}
+
+
+void logWriteToFileOnly(char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+
+ if (_log) {
+ vfprintf(_log, format, args);
+ fflush(_log);
+ }
+
+ va_end(args);
+}
+
+
+FILE *memoryLogHandleGet(void) {
+ return _memoryLog;
+}
+
+
+#ifdef MEMWATCH
+void memoryOutput(int c) {
+ fputc(c, _memoryLog);
+ fflush(_memoryLog);
+
+ #ifdef __linux__
+ // Also output to stdout on Linux.
+ fputc(c, stdout);
+ fflush(stdout);
+ #endif
+}
+#endif
RooAppThreadT *rooStartApp(const char *appName, const int argc, char *argv[]) {
@@ -161,9 +328,9 @@ static void *rooStartAppThread(void *arg) {
appThread->running = true;
appPtrCast.from = dlsym(appThread->namedPointer->pointer, "_appMain");
appMain = appPtrCast.to; // NOLINT
- debug("ROO/E: Calling appMain.\n");
+ logWrite("ROO/E: Calling appMain.\n");
appThread->result = appMain(appThread->argc, appThread->argv);
- debug("ROO/E: Back from appMain.\n");
+ logWrite("ROO/E: Back from appMain.\n");
appThread->running = false;
return NULL;
@@ -199,7 +366,8 @@ int main(const int argc, char *argv[]) {
RooAppThreadT *appThread = NULL;;
RooNamedPointerT **dynList = NULL;
RooNamedPointerT *namedPointer = NULL;
- int result;
+ char *logName = utilFileExtensionChange("roo_e.exe", "log");
+ int result = -1;
// RooAbortReasonT abort;
int x;
int (*dynInitStop)(void);
@@ -212,15 +380,26 @@ int main(const int argc, char *argv[]) {
(void)argc;
(void)argv;
-#ifdef PLATFORM_DOS
+ _memoryLog = fopen(logName, "w"); // This used to be encapsulated inside memory.c. Kinda just dangling here now.
+ logOpenByHandle(memoryLogHandleGet());
+
+ #ifdef MEMWATCH
+ mwLogW(_memoryLog);
+ mwSetOutFunc(memoryOutput);
+ mwInit();
+ #endif
+
+ free(logName);
+
+#ifdef __DJGPP__
// Set the error callback function.
_dlsymresolver = dxeResolver;
// Register the symbols exported into dynamic modules.
dlregsym(exports);
-#endif // PLATFORM_DOS
+#endif // __DJGPP__
// Load libraries.
- debug("ROO/E: Loading dynamic libraries.\n");
+ logWrite("ROO/E: Loading dynamic libraries.\n");
if ((dir = opendir("dyn/")) == NULL) {
printf("ROO/E: Unable to open dyn directory!\n");
exit(1);
@@ -236,11 +415,11 @@ int main(const int argc, char *argv[]) {
printf("ROO/E: Unable to load %s! %s\n", namedPointer->name, dlerror());
exit(1);
}
- debug("ROO/E: Loaded %s.\n", namedPointer->name);
+ logWrite("ROO/E: Loaded %s.\n", namedPointer->name);
dynPtrCast.from = dlsym(namedPointer->pointer, "_dynStart");
if (dynPtrCast.from != NULL) {
dynInitStop = dynPtrCast.to; // NOLINT
- debug("ROO/E: Starting %s.\n", namedPointer->name);
+ logWrite("ROO/E: Starting %s.\n", namedPointer->name);
result = dynInitStop();
if (result != 0) {
printf("ROO/E: %s failed to start!\n", namedPointer->name);
@@ -253,7 +432,7 @@ int main(const int argc, char *argv[]) {
}
closedir(dir);
- debug("ROO/E: Loaded %d libraries.\n", arrlen(dynList));
+ logWrite("ROO/E: Loaded %d libraries.\n", arrlen(dynList));
// Load the Shell. This needs to be configurable.
rooStartApp("kpsmpgc", 0, NULL);
@@ -263,13 +442,13 @@ int main(const int argc, char *argv[]) {
// Wait for all apps to exit.
for (x=0; xrunning);
+ logWrite("ROO/E: App %d of %d running == %d\n", x, arrlen(_appThreadList), (int)appThread->running);
if (appThread->running == false) {
- debug("ROO/E: Joining.\n");
+ logWrite("ROO/E: Joining.\n");
pthread_join(appThread->thread, NULL);
- debug("ROO/E: Joined.\n");
+ logWrite("ROO/E: Joined.\n");
dlclose(appThread->namedPointer->pointer);
- debug("ROO/E: %s exited.\n", appThread->namedPointer->name);
+ logWrite("ROO/E: %s exited.\n", appThread->namedPointer->name);
DEL(appThread->namedPointer->name);
DEL(appThread->namedPointer);
//***TODO*** Deal with argv cleanup here?
@@ -297,10 +476,10 @@ int main(const int argc, char *argv[]) {
*/
// Unload DYNs
- debug("ROO/E: Unloading %d libraries.\n", arrlen(dynList));
+ logWrite("ROO/E: Unloading %d libraries.\n", arrlen(dynList));
while (arrlen(dynList) > 0) {
namedPointer = dynList[0]; // NOLINT
- debug("ROO/E: Stopping %s\n", namedPointer->name);
+ logWrite("ROO/E: Stopping %s\n", namedPointer->name);
dynPtrCast.from = dlsym(namedPointer->pointer, "_dynStop");
if (dynPtrCast.from != NULL) {
dynInitStop = dynPtrCast.to; // NOLINT
@@ -316,5 +495,12 @@ int main(const int argc, char *argv[]) {
DEL(namedPointer);
}
+ #ifdef MEMWATCH
+ unlink("memwatch.log"); // It just insists on creating this!
+ mwTerm();
+ #endif
+
+ logClose();
+
return result;
}
diff --git a/roo_e/roo_e.h b/roo_e/roo_e.h
index 8535e29..8c98034 100644
--- a/roo_e/roo_e.h
+++ b/roo_e/roo_e.h
@@ -28,15 +28,35 @@
#include
#include
+#include
+#include
+#include
+#include
#include
#include
#include
+#include
#include "pthread.h"
#include "stbds.h"
#include "util.h"
#include "stddclmr.h"
+// This is weird being here, but we have to expose a lot of it via DXE_EXPORT.
+#ifdef __DJGPP__
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+#ifdef MEMWATCH
+#include "memwatch/memwatch.h"
+#endif
+
#define ROO_CONSTRUCTOR void __attribute__((constructor))
#define ROO_DESTRUCTOR void __attribute__((destructor))
@@ -45,6 +65,23 @@
#define NEW(t,v) (v)=(t*)malloc(sizeof(t))
#define DEL(v) {if(v) {free(v); v=NULL;}}
+// Some helper defines.
+#define DIVISIBLE_BY_EIGHT(x) ((((x) >> 3) << 3) == (x))
+#define HIGH_BYTE(b) ((uint8_t)(((b) & 0xFF00) >> 8))
+#define LOW_BYTE(b) ((uint8_t)((b) & 0x00FF))
+
+// Return codes.
+#define SUCCESS 0
+#define FAIL 1
+
+#ifndef va_copy
+#if defined(__GNUC__) || defined(__clang__)
+#define va_copy(dest, src) __builtin_va_copy(dest, src)
+#else
+#define va_copy(dest, src) (dest = src)
+#endif
+#endif
+
typedef struct RooNamedPointerS {
char *name;
@@ -74,6 +111,12 @@ typedef enum {
} RooAbortReasonT;
+void logClose(void);
+uint8_t logOpen(char *filename, uint8_t append);
+void logOpenByHandle(FILE *handle);
+void logWrite(char *format, ...);
+void logWriteToFileOnly(char *format, ...);
+
RooAppThreadT *rooStartApp(const char *appName, int argc, char *argv[]);
RooAbortReasonT rooStopApp(RooAppThreadT **threadPointer, RooStopReasonT reason);
diff --git a/roo_e/util.c b/roo_e/util.c
index 0605a4f..49b11b3 100644
--- a/roo_e/util.c
+++ b/roo_e/util.c
@@ -21,14 +21,8 @@
*/
-#include
-#include
-#include
-
#include "util.h"
-#include
-
char *utilCreateString(char *format, ...) {
va_list args;
@@ -78,3 +72,46 @@ bool utilEndsWith(const char *haystack, const char *needle, const bool caseSensi
return true;
}
+
+
+uint8_t utilFileExists(char *filename) {
+ FILE *f = fopen(filename, "rb");
+
+ if (f) {
+ fclose(f);
+ return SUCCESS;
+ }
+
+ return FAIL;
+}
+
+
+char *utilFileExtensionChange(char *appName, char *extension) {
+ char *c = NULL;
+ char *newName = NULL;
+ int16_t x = strlen(appName);
+ uint16_t len = 2 + strlen(extension); // 2 = dot in extension and 0 terminator.
+
+ // Find last portion of filename.
+ while (x > 0) {
+ if (appName[x] == '/' || appName[x] == '\\') { x++; break; }
+ x--;
+ len++;
+ }
+ x--;
+
+ // We use this + length of new extension for new string length.
+ newName = (char *)malloc(len);
+ if (newName) {
+ if (strlen(appName) - x < len) {
+ // Replace extension
+ strncpy(newName, &appName[x + 1], len - 1);
+ c = strstr(newName, ".");
+ if (c) *c = 0;
+ strncat(newName, ".", len - 1);
+ strncat(newName, extension, len - 1);
+ }
+ }
+
+ return newName;
+}
diff --git a/roo_e/util.h b/roo_e/util.h
index c93e488..9eb2a06 100644
--- a/roo_e/util.h
+++ b/roo_e/util.h
@@ -25,25 +25,14 @@
#define UTIL_H
-#include
-#include
-#include
+#include "roo_e.h"
-#define debug printf
-
-#ifndef va_copy
-#if defined(__GNUC__) || defined(__clang__)
-#define va_copy(dest, src) __builtin_va_copy(dest, src)
-#else
-#define va_copy(dest, src) (dest = src)
-#endif
-#endif
-
-
-char *utilCreateString(char *format, ...);
-char *utilCreateStringVArgs(char *format, va_list args);
-bool utilEndsWith(const char *haystack, const char *needle, bool caseSensitive);
+char *utilCreateString(char *format, ...);
+char *utilCreateStringVArgs(char *format, va_list args);
+bool utilEndsWith(const char *haystack, const char *needle, bool caseSensitive);
+char *utilFileExtensionChange(char *appName, char *extension);
+uint8_t utilFileExists(char *filename);
#endif // UTIL_H