From: Tulio A M Mendes Date: Thu, 12 Feb 2026 07:54:41 +0000 (-0300) Subject: feat: DOOM port — doomgeneric AdrOS adapter + remaining ulibc extensions X-Git-Url: https://projects.tadryanom.me/docs/static/gitweb.css?a=commitdiff_plain;h=61bc14738555ebe829da6df49168f0ab555283a4;p=AdrOS.git feat: DOOM port — doomgeneric AdrOS adapter + remaining ulibc extensions 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 --- diff --git a/user/doom/Makefile b/user/doom/Makefile new file mode 100644 index 0000000..978e452 --- /dev/null +++ b/user/doom/Makefile @@ -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 index 0000000..1a71705 --- /dev/null +++ b/user/doom/README.md @@ -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 index 0000000..ae8f886 --- /dev/null +++ b/user/doom/doomgeneric_adros.c @@ -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 +#include +#include + +/* 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 index 0000000..a94095a --- /dev/null +++ b/user/ulibc/include/ctype.h @@ -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 diff --git a/user/ulibc/include/stdlib.h b/user/ulibc/include/stdlib.h index 81f3353..c71766b 100644 --- a/user/ulibc/include/stdlib.h +++ b/user/ulibc/include/stdlib.h @@ -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); diff --git a/user/ulibc/src/stdlib.c b/user/ulibc/src/stdlib.c index 0fa6273..fec9db7 100644 --- a/user/ulibc/src/stdlib.c +++ b/user/ulibc/src/stdlib.c @@ -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;