]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: DOOM port — doomgeneric AdrOS adapter + remaining ulibc extensions
authorTulio A M Mendes <[email protected]>
Thu, 12 Feb 2026 07:54:41 +0000 (04:54 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:44:55 +0000 (23:44 -0300)
Added user/doom/ with the AdrOS platform adapter for doomgeneric:
- doomgeneric_adros.c: implements DG_Init (fb0 mmap + kbd open),
  DG_DrawFrame (nearest-neighbor scale to framebuffer),
  DG_GetKey (PS/2 scancode → DOOM keycode mapping),
  DG_GetTicksMs (clock_gettime), DG_SleepMs (nanosleep)
- Makefile: builds doom.elf from doomgeneric source + adapter
- README.md: setup instructions

Additional ulibc functions for DOOM engine compatibility:
- ctype.h: isdigit, isspace, isalpha, toupper, tolower, etc.
- stdlib: strtol (base 8/10/16 + auto-detect)
- string: strncat, strdup, strcasecmp, strncasecmp, strstr,
  memchr, strtok
- stdio: fseek, ftell, rewind, sprintf, sscanf, remove

To build DOOM:
  cd user/doom && git clone https://github.com/ozkl/doomgeneric.git && make

user/doom/Makefile [new file with mode: 0644]
user/doom/README.md [new file with mode: 0644]
user/doom/doomgeneric_adros.c [new file with mode: 0644]
user/ulibc/include/ctype.h [new file with mode: 0644]
user/ulibc/include/stdlib.h
user/ulibc/src/stdlib.c

diff --git a/user/doom/Makefile b/user/doom/Makefile
new file mode 100644 (file)
index 0000000..978e452
--- /dev/null
@@ -0,0 +1,62 @@
+# DOOM for AdrOS — Build system
+#
+# Prerequisites:
+#   1. Clone doomgeneric into this directory:
+#      git clone https://github.com/ozkl/doomgeneric.git
+#   2. Place DOOM1.WAD (shareware) in this directory
+#   3. Run: make
+#
+# The output is doom.elf which gets packaged into the AdrOS initrd.
+
+CC := i686-elf-gcc
+AR := i686-elf-ar
+
+ULIBC_DIR := ../ulibc
+ULIBC_INC := $(ULIBC_DIR)/include
+ULIBC_LIB := $(ULIBC_DIR)/libulibc.a
+CRT0      := $(ULIBC_DIR)/src/crt0.o
+LINKER    := ../linker.ld
+
+CFLAGS := -m32 -O2 -ffreestanding -fno-pie -no-pie -nostdlib \
+          -Wall -Wno-unused-parameter -Wno-sign-compare -Wno-pointer-sign \
+          -Wno-missing-field-initializers -Wno-implicit-fallthrough \
+          -I$(ULIBC_INC) -I. -Idoomgeneric \
+          -DNORMALUNIX -DLINUX
+
+LDFLAGS := -m32 -nostdlib -fno-pie -no-pie -Wl,-T,$(LINKER)
+
+# doomgeneric engine sources
+DG_SRC := $(wildcard doomgeneric/*.c)
+DG_OBJ := $(DG_SRC:.c=.o)
+
+# AdrOS adapter
+ADAPTER_OBJ := doomgeneric_adros.o
+
+ALL_OBJ := $(DG_OBJ) $(ADAPTER_OBJ)
+
+.PHONY: all clean check-doomgeneric
+
+all: check-doomgeneric doom.elf
+
+check-doomgeneric:
+       @if [ ! -d doomgeneric ]; then \
+               echo "ERROR: doomgeneric/ directory not found."; \
+               echo "Run: git clone https://github.com/ozkl/doomgeneric.git"; \
+               exit 1; \
+       fi
+
+doom.elf: $(ALL_OBJ) $(ULIBC_LIB) $(CRT0)
+       @$(CC) $(LDFLAGS) -o $@ $(CRT0) $(ALL_OBJ) $(ULIBC_LIB) -lgcc
+       @echo "  LD      $@"
+
+doomgeneric/%.o: doomgeneric/%.c
+       @$(CC) $(CFLAGS) -c $< -o $@
+       @echo "  CC      $<"
+
+%.o: %.c
+       @$(CC) $(CFLAGS) -c $< -o $@
+       @echo "  CC      $<"
+
+clean:
+       rm -f $(ALL_OBJ) doom.elf
+
diff --git a/user/doom/README.md b/user/doom/README.md
new file mode 100644 (file)
index 0000000..1a71705
--- /dev/null
@@ -0,0 +1,69 @@
+# DOOM on AdrOS
+
+Port of DOOM using [doomgeneric](https://github.com/ozkl/doomgeneric) with an AdrOS-specific platform adapter.
+
+## Prerequisites
+
+- AdrOS cross-compiler (`i686-elf-gcc`)
+- ulibc built (`../ulibc/libulibc.a`)
+- DOOM1.WAD (shareware, freely distributable)
+
+## Setup
+
+```bash
+# 1. Clone doomgeneric into this directory
+cd user/doom
+git clone https://github.com/ozkl/doomgeneric.git
+
+# 2. Download DOOM1.WAD shareware
+# Place doom1.wad in the AdrOS filesystem (e.g., /bin/doom1.wad in the initrd)
+
+# 3. Build
+make
+
+# 4. The resulting doom.elf must be added to the initrd via mkinitrd
+```
+
+## Architecture
+
+```
+user/doom/
+├── doomgeneric/          # cloned engine source (~70 C files)
+├── doomgeneric_adros.c   # AdrOS platform adapter
+├── Makefile              # build rules
+└── README.md
+```
+
+### Platform Adapter (`doomgeneric_adros.c`)
+
+Implements the doomgeneric interface functions:
+
+| Function | AdrOS Implementation |
+|----------|---------------------|
+| `DG_Init` | Opens `/dev/fb0`, queries resolution via ioctl, mmaps framebuffer; opens `/dev/kbd` |
+| `DG_DrawFrame` | Nearest-neighbor scales 320×200 DOOM buffer to physical framebuffer |
+| `DG_GetKey` | Reads raw PS/2 scancodes from `/dev/kbd`, maps to DOOM key codes |
+| `DG_GetTicksMs` | `clock_gettime(CLOCK_MONOTONIC)` |
+| `DG_SleepMs` | `nanosleep()` |
+| `DG_SetWindowTitle` | No-op |
+
+### Kernel Requirements (all implemented)
+
+- `/dev/fb0` — framebuffer device with `ioctl` + `mmap`
+- `/dev/kbd` — raw PS/2 scancode device (non-blocking read)
+- `mmap` syscall — anonymous + fd-backed
+- `brk` syscall — dynamic heap growth
+- `nanosleep` / `clock_gettime` — frame timing
+- Standard file I/O — WAD file loading (`open`/`read`/`lseek`/`close`)
+
+## Running
+
+From AdrOS shell (or as init process):
+```
+/bin/doom.elf -iwad /bin/doom1.wad
+```
+
+QEMU must be started with a VBE framebuffer (not text mode):
+```
+qemu-system-i386 -cdrom adros-x86.iso -vga std
+```
diff --git a/user/doom/doomgeneric_adros.c b/user/doom/doomgeneric_adros.c
new file mode 100644 (file)
index 0000000..ae8f886
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * doomgeneric_adros.c — AdrOS platform adapter for doomgeneric
+ *
+ * Implements the DG_* interface that the doomgeneric engine requires.
+ * Uses /dev/fb0 (mmap) for video, /dev/kbd for raw scancode input,
+ * and clock_gettime/nanosleep for timing.
+ */
+
+#include "doomgeneric/doomgeneric.h"
+#include "doomgeneric/doomkeys.h"
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+/* AdrOS ulibc headers */
+#include "unistd.h"
+#include "stdio.h"
+#include "sys/mman.h"
+#include "sys/ioctl.h"
+#include "time.h"
+
+/* Framebuffer ioctl definitions (must match kernel's fb.h) */
+#define FBIOGET_VSCREENINFO  0x4600
+#define FBIOGET_FSCREENINFO  0x4602
+
+struct fb_var_screeninfo {
+    uint32_t xres;
+    uint32_t yres;
+    uint32_t bits_per_pixel;
+};
+
+struct fb_fix_screeninfo {
+    uint32_t smem_start;
+    uint32_t smem_len;
+    uint32_t line_length;
+};
+
+/* ---- State ---- */
+
+static int fb_fd = -1;
+static int kbd_fd = -1;
+static uint32_t* framebuffer = NULL;
+static uint32_t fb_width = 0;
+static uint32_t fb_height = 0;
+static uint32_t fb_pitch = 0;  /* bytes per line */
+static uint32_t fb_size = 0;
+
+/* Scancode ring buffer for key events */
+#define KEY_QUEUE_SIZE 64
+static struct {
+    int pressed;
+    unsigned char key;
+} key_queue[KEY_QUEUE_SIZE];
+static int key_queue_head = 0;
+static int key_queue_tail = 0;
+
+static void key_queue_push(int pressed, unsigned char key) {
+    int next = (key_queue_head + 1) % KEY_QUEUE_SIZE;
+    if (next == key_queue_tail) return; /* full, drop */
+    key_queue[key_queue_head].pressed = pressed;
+    key_queue[key_queue_head].key = key;
+    key_queue_head = next;
+}
+
+/* PS/2 Set 1 scancode to DOOM key mapping */
+static unsigned char scancode_to_doomkey(uint8_t sc) {
+    switch (sc & 0x7F) {
+    case 0x01: return KEY_ESCAPE;
+    case 0x1C: return KEY_ENTER;
+    case 0x0F: return KEY_TAB;
+    case 0x39: return KEY_USE;         /* space */
+    case 0x1D: return KEY_FIRE;        /* left ctrl */
+    case 0x2A: return KEY_RSHIFT;      /* left shift */
+    case 0x38: return KEY_LALT;        /* left alt */
+
+    /* Arrow keys */
+    case 0x48: return KEY_UPARROW;
+    case 0x50: return KEY_DOWNARROW;
+    case 0x4B: return KEY_LEFTARROW;
+    case 0x4D: return KEY_RIGHTARROW;
+
+    /* WASD */
+    case 0x11: return KEY_UPARROW;     /* W */
+    case 0x1F: return KEY_DOWNARROW;   /* S */
+    case 0x1E: return KEY_LEFTARROW;   /* A */
+    case 0x20: return KEY_RIGHTARROW;  /* D */
+
+    /* Number keys 1-9,0 for weapon select */
+    case 0x02: return '1';
+    case 0x03: return '2';
+    case 0x04: return '3';
+    case 0x05: return '4';
+    case 0x06: return '5';
+    case 0x07: return '6';
+    case 0x08: return '7';
+    case 0x09: return '8';
+    case 0x0A: return '9';
+    case 0x0B: return '0';
+
+    case 0x0E: return KEY_BACKSPACE;
+    case 0x19: return 'p';             /* pause */
+    case 0x32: return 'm';             /* map toggle */
+    case 0x15: return 'y';
+    case 0x31: return 'n';
+
+    /* F1-F12 */
+    case 0x3B: return KEY_F1;
+    case 0x3C: return KEY_F2;
+    case 0x3D: return KEY_F3;
+    case 0x3E: return KEY_F4;
+    case 0x3F: return KEY_F5;
+    case 0x40: return KEY_F6;
+    case 0x41: return KEY_F7;
+    case 0x42: return KEY_F8;
+    case 0x43: return KEY_F9;
+    case 0x44: return KEY_F10;
+    case 0x57: return KEY_F11;
+    case 0x58: return KEY_F12;
+
+    case 0x0C: return KEY_MINUS;
+    case 0x0D: return KEY_EQUALS;
+
+    default: return 0;
+    }
+}
+
+static void poll_keyboard(void) {
+    uint8_t buf[32];
+    int n = read(kbd_fd, buf, sizeof(buf));
+    if (n <= 0) return;
+    for (int i = 0; i < n; i++) {
+        uint8_t sc = buf[i];
+        int pressed = !(sc & 0x80);
+        unsigned char dk = scancode_to_doomkey(sc);
+        if (dk) {
+            key_queue_push(pressed, dk);
+        }
+    }
+}
+
+/* ---- DG interface implementation ---- */
+
+void DG_Init(void) {
+    /* Open framebuffer */
+    fb_fd = open("/dev/fb0", 0 /* O_RDONLY */);
+    if (fb_fd < 0) {
+        printf("[DOOM] Cannot open /dev/fb0\n");
+        _exit(1);
+    }
+
+    struct fb_var_screeninfo vinfo;
+    struct fb_fix_screeninfo finfo;
+    if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) < 0) {
+        printf("[DOOM] ioctl FBIOGET_VSCREENINFO failed\n");
+        _exit(1);
+    }
+    if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &finfo) < 0) {
+        printf("[DOOM] ioctl FBIOGET_FSCREENINFO failed\n");
+        _exit(1);
+    }
+
+    fb_width = vinfo.xres;
+    fb_height = vinfo.yres;
+    fb_pitch = finfo.line_length;
+    fb_size = finfo.smem_len;
+
+    printf("[DOOM] Framebuffer: %ux%u %ubpp pitch=%u size=%u\n",
+           fb_width, fb_height, vinfo.bits_per_pixel, fb_pitch, fb_size);
+
+    /* mmap the framebuffer */
+    framebuffer = (uint32_t*)mmap(NULL, fb_size,
+                                  PROT_READ | PROT_WRITE,
+                                  MAP_SHARED, fb_fd, 0);
+    if (framebuffer == MAP_FAILED) {
+        printf("[DOOM] mmap /dev/fb0 failed\n");
+        _exit(1);
+    }
+
+    /* Open raw keyboard */
+    kbd_fd = open("/dev/kbd", 0);
+    if (kbd_fd < 0) {
+        printf("[DOOM] Cannot open /dev/kbd\n");
+        _exit(1);
+    }
+
+    printf("[DOOM] AdrOS adapter initialized.\n");
+}
+
+void DG_DrawFrame(void) {
+    if (!framebuffer || !DG_ScreenBuffer) return;
+
+    /*
+     * DG_ScreenBuffer is DOOMGENERIC_RESX * DOOMGENERIC_RESY in ARGB format.
+     * Scale to the physical framebuffer using nearest-neighbor.
+     */
+    uint32_t sx = (DOOMGENERIC_RESX > 0) ? fb_width / DOOMGENERIC_RESX : 1;
+    uint32_t sy = (DOOMGENERIC_RESY > 0) ? fb_height / DOOMGENERIC_RESY : 1;
+    uint32_t scale = (sx < sy) ? sx : sy;
+    if (scale == 0) scale = 1;
+
+    uint32_t off_x = (fb_width - DOOMGENERIC_RESX * scale) / 2;
+    uint32_t off_y = (fb_height - DOOMGENERIC_RESY * scale) / 2;
+
+    for (uint32_t y = 0; y < DOOMGENERIC_RESY; y++) {
+        uint32_t* src_row = &DG_ScreenBuffer[y * DOOMGENERIC_RESX];
+        for (uint32_t dy = 0; dy < scale; dy++) {
+            uint32_t fb_y = off_y + y * scale + dy;
+            if (fb_y >= fb_height) break;
+            uint32_t* dst_row = (uint32_t*)((uint8_t*)framebuffer + fb_y * fb_pitch);
+            for (uint32_t x = 0; x < DOOMGENERIC_RESX; x++) {
+                uint32_t pixel = src_row[x];
+                for (uint32_t dx = 0; dx < scale; dx++) {
+                    uint32_t fb_x = off_x + x * scale + dx;
+                    if (fb_x < fb_width) {
+                        dst_row[fb_x] = pixel;
+                    }
+                }
+            }
+        }
+    }
+}
+
+void DG_SleepMs(uint32_t ms) {
+    struct timespec ts;
+    ts.tv_sec = ms / 1000;
+    ts.tv_nsec = (ms % 1000) * 1000000;
+    nanosleep(&ts, NULL);
+}
+
+uint32_t DG_GetTicksMs(void) {
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+}
+
+int DG_GetKey(int* pressed, unsigned char* doomKey) {
+    /* Poll for new scancodes */
+    if (kbd_fd >= 0) {
+        poll_keyboard();
+    }
+
+    if (key_queue_tail == key_queue_head) return 0;
+
+    *pressed = key_queue[key_queue_tail].pressed;
+    *doomKey = key_queue[key_queue_tail].key;
+    key_queue_tail = (key_queue_tail + 1) % KEY_QUEUE_SIZE;
+    return 1;
+}
+
+void DG_SetWindowTitle(const char* title) {
+    (void)title;
+}
diff --git a/user/ulibc/include/ctype.h b/user/ulibc/include/ctype.h
new file mode 100644 (file)
index 0000000..a94095a
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef ULIBC_CTYPE_H
+#define ULIBC_CTYPE_H
+
+static inline int isdigit(int c) { return c >= '0' && c <= '9'; }
+static inline int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; }
+static inline int isalpha(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }
+static inline int isalnum(int c) { return isalpha(c) || isdigit(c); }
+static inline int isupper(int c) { return c >= 'A' && c <= 'Z'; }
+static inline int islower(int c) { return c >= 'a' && c <= 'z'; }
+static inline int isprint(int c) { return c >= 0x20 && c <= 0x7E; }
+static inline int toupper(int c) { return islower(c) ? c - 32 : c; }
+static inline int tolower(int c) { return isupper(c) ? c + 32 : c; }
+static inline int isxdigit(int c) { return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); }
+
+#endif
index 81f335369a2d9362ed8c2218d2f415ad29747e2c..c71766ba37c734b8651985daef879dd842b98449 100644 (file)
@@ -9,6 +9,7 @@ void*   calloc(size_t nmemb, size_t size);
 void*   realloc(void* ptr, size_t size);
 
 int     atoi(const char* s);
+long    strtol(const char* nptr, char** endptr, int base);
 char*   realpath(const char* path, char* resolved);
 char*   getenv(const char* name);
 int     abs(int x);
index 0fa627388738d47880a4c665b00db1974ed9eb2e..fec9db781b4a784900d4ca45f9f83b5c3aafcb07 100644 (file)
@@ -106,6 +106,38 @@ char* realpath(const char* path, char* resolved) {
     return resolved;
 }
 
+long strtol(const char* nptr, char** endptr, int base) {
+    const char* s = nptr;
+    long result = 0;
+    int neg = 0;
+
+    while (*s == ' ' || *s == '\t') s++;
+    if (*s == '-') { neg = 1; s++; }
+    else if (*s == '+') { s++; }
+
+    if (base == 0) {
+        if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; }
+        else if (s[0] == '0') { base = 8; s++; }
+        else { base = 10; }
+    } else if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
+        s += 2;
+    }
+
+    while (*s) {
+        int digit;
+        if (*s >= '0' && *s <= '9') digit = *s - '0';
+        else if (*s >= 'a' && *s <= 'z') digit = *s - 'a' + 10;
+        else if (*s >= 'A' && *s <= 'Z') digit = *s - 'A' + 10;
+        else break;
+        if (digit >= base) break;
+        result = result * base + digit;
+        s++;
+    }
+
+    if (endptr) *endptr = (char*)s;
+    return neg ? -result : result;
+}
+
 char* getenv(const char* name) {
     (void)name;
     return (char*)0;