Files
nt-lite/code/doom/doom.c
T

331 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// doom.c — NT-Lite port of PureDOOM
// Win32 personality carrying all the weight, just like the real ntoskrnl does.
// Drop doom1.wad (or doom.wad) next to this binary on your FAT32 volume.
#include <windows.h>
#define DOOM_IMPLEMENTATION
#include "PureDOOM.h"
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <intrin.h>
int _fltused;
// ---------------------------------------------------------------------------
// Globals
// ---------------------------------------------------------------------------
static HANDLE g_stdout;
// ---------------------------------------------------------------------------
// doom_print_fn
// ---------------------------------------------------------------------------
static void doom_print_wrapper(const char* msg)
{
UINT len = (UINT)strlen(msg);
WriteConsoleA(g_stdout, msg, len, 0, 0);
}
// ---------------------------------------------------------------------------
// doom_malloc_fn / doom_free_fn
// ---------------------------------------------------------------------------
static void* doom_malloc_wrapper(int size)
{
void* p = HeapAlloc(0, HEAP_ZERO_MEMORY, size);
if (!p) ExitProcess(GetLastError());
return p;
}
static void doom_free_wrapper(void* ptr)
{
HeapFree(0, 0, ptr);
}
// ---------------------------------------------------------------------------
// File I/O wrappers
// ---------------------------------------------------------------------------
static void* doom_open_wrapper(const char* path, const char* mode)
{
DWORD access = GENERIC_READ;
DWORD disp = OPEN_EXISTING;
if (mode[0] == 'w') {
access = GENERIC_WRITE;
disp = CREATE_ALWAYS;
} else if (mode[0] == 'r' && mode[1] == '+') {
access = GENERIC_READ | GENERIC_WRITE;
}
HANDLE h = CreateFileA(path, access, FILE_SHARE_READ, 0, disp, 0, 0);
return (h == INVALID_HANDLE_VALUE) ? 0 : h;
}
static void doom_close_wrapper(void* handle)
{
CloseHandle((HANDLE)handle);
}
static int doom_read_wrapper(void* handle, void* buf, int count)
{
DWORD read = 0;
ReadFile((HANDLE)handle, buf, (DWORD)count, &read, 0);
return (int)read;
}
static int doom_write_wrapper(void* handle, const void* buf, int count)
{
DWORD written = 0;
WriteFile((HANDLE)handle, buf, (DWORD)count, &written, 0);
return (int)written;
}
static int doom_seek_wrapper(void* handle, int offset, doom_seek_t origin)
{
DWORD method;
switch (origin) {
case DOOM_SEEK_SET: method = FILE_BEGIN; break;
case DOOM_SEEK_CUR: method = FILE_CURRENT; break;
case DOOM_SEEK_END: method = FILE_END; break;
default: method = FILE_BEGIN; break;
}
SetFilePointer((HANDLE)handle, (LONG)offset, 0, method);
return 0;
}
static int doom_tell_wrapper(void* handle)
{
return (int)SetFilePointer((HANDLE)handle, 0, 0, FILE_CURRENT);
}
static int doom_eof_wrapper(void* handle)
{
DWORD size = GetFileSize((HANDLE)handle, 0);
DWORD pos = (DWORD)doom_tell_wrapper(handle);
return (pos >= size) ? 1 : 0;
}
// ---------------------------------------------------------------------------
// doom_gettime_fn
// ---------------------------------------------------------------------------
static void doom_gettime_wrapper(int* sec, int* usec)
{
DWORD t = GetTickCount(); // milliseconds
*sec = (int)(t / 1000);
*usec = (int)((t % 1000) * 1000); // ms remainder → us
}
// ---------------------------------------------------------------------------
// doom_exit_fn
// ---------------------------------------------------------------------------
static void doom_exit_wrapper(int code)
{
ExitProcess((UINT)code);
}
// ---------------------------------------------------------------------------
// doom_getenv_fn — we don't have environment variables
// ---------------------------------------------------------------------------
static char* doom_getenv_wrapper(const char* name)
{
if (strcmp(name, "HOME") == 0) {
static char home[] = "c:";
doom_print_wrapper("getenv: Returning HOME="); doom_print_wrapper(home); doom_print_wrapper("\n");
return home;
}
doom_print_wrapper("getenv: Unknown variable "); doom_print_wrapper(name); doom_print_wrapper("\n");
return NULL;
}
// ---------------------------------------------------------------------------
// Input — poll GetAsyncKeyState every frame
// Generates key_down / key_up edge events, not held-key spam.
// ---------------------------------------------------------------------------
typedef struct { int vk; doom_key_t key; } KeyMap;
// NOTE: Win32 VK codes for letters are uppercase ASCII (e.g. 'W' = 0x57).
// DOOM_KEY_* for letters are lowercase ASCII (e.g. DOOM_KEY_W = 'w' = 0x77).
// These are different values — the VK column is what GetAsyncKeyState takes,
// the doom_key column is what PureDOOM wants. Don't collapse them.
static const KeyMap g_keymap[] = {
// Letters — VK is uppercase, DOOM key is lowercase
{ 'W', DOOM_KEY_W },
{ 'A', DOOM_KEY_A },
{ 'S', DOOM_KEY_S },
{ 'D', DOOM_KEY_D },
{ 'E', DOOM_KEY_E },
{ 'Q', DOOM_KEY_Q },
{ 'Y', DOOM_KEY_Y }, // yes/no menus
{ 'N', DOOM_KEY_N },
// Arrow keys
{ VK_UP, DOOM_KEY_UP_ARROW },
{ VK_DOWN, DOOM_KEY_DOWN_ARROW },
{ VK_LEFT, DOOM_KEY_LEFT_ARROW },
{ VK_RIGHT, DOOM_KEY_RIGHT_ARROW },
// Action keys — Ctrl=fire, Shift=run, Alt=strafe
{ VK_CONTROL, DOOM_KEY_CTRL },
{ VK_SPACE, DOOM_KEY_SPACE },
{ VK_SHIFT, DOOM_KEY_SHIFT },
{ VK_MENU, DOOM_KEY_ALT },
// Navigation
{ VK_ESCAPE, DOOM_KEY_ESCAPE },
{ VK_RETURN, DOOM_KEY_ENTER },
{ VK_TAB, DOOM_KEY_TAB },
{ VK_BACK, DOOM_KEY_BACKSPACE },
{ VK_PAUSE, DOOM_KEY_PAUSE },
// Weapon slots
{ '1', DOOM_KEY_1 },
{ '2', DOOM_KEY_2 },
{ '3', DOOM_KEY_3 },
{ '4', DOOM_KEY_4 },
{ '5', DOOM_KEY_5 },
{ '6', DOOM_KEY_6 },
{ '7', DOOM_KEY_7 },
// Misc
{ '-', DOOM_KEY_MINUS },
{ '=', DOOM_KEY_EQUALS },
{ ',', DOOM_KEY_COMMA }, // default strafe left
{ '.', DOOM_KEY_PERIOD }, // default strafe right
// Function keys — save/load/volume/etc
{ VK_F1, DOOM_KEY_F1 },
{ VK_F2, DOOM_KEY_F2 },
{ VK_F3, DOOM_KEY_F3 },
{ VK_F4, DOOM_KEY_F4 },
{ VK_F5, DOOM_KEY_F5 },
{ VK_F6, DOOM_KEY_F6 },
{ VK_F7, DOOM_KEY_F7 },
{ VK_F8, DOOM_KEY_F8 },
{ VK_F9, DOOM_KEY_F9 },
{ VK_F10, DOOM_KEY_F10 },
{ VK_F11, DOOM_KEY_F11 },
{ VK_F12, DOOM_KEY_F12 },
};
#define KEYMAP_COUNT (sizeof(g_keymap) / sizeof(g_keymap[0]))
static short g_prev_key[256];
static void poll_input(void)
{
for (int i = 0; i < (int)KEYMAP_COUNT; i++) {
int vk = g_keymap[i].vk;
short cur = GetAsyncKeyState(vk);
int cur_down = (cur & 0x8000) != 0;
int prev_down = (g_prev_key[vk] & 0x8000) != 0;
if (cur_down && !prev_down) doom_key_down(g_keymap[i].key);
if (!cur_down && prev_down) doom_key_up (g_keymap[i].key);
g_prev_key[vk] = cur;
}
}
// ---------------------------------------------------------------------------
// Render — StretchDIBits to screen, same pipeline as video.c.
//
// NOTE: PureDOOM's framebuffer is RGBA; Win32 StretchDIBits with BI_RGB/32bpp
// expects BGRA. Channels will be swapped (red ↔ blue) until you either:
// a) swap in the blit loop below, or
// b) tell PureDOOM to give you BGRA if a future version supports it.
// For a first boot, blue-tinted demons are still demons.
// ---------------------------------------------------------------------------
#define DOOM_W 320
#define DOOM_H 200
#define SCALE 3
#define DISP_W (DOOM_W * SCALE)
#define DISP_H (DOOM_H * SCALE)
static BITMAPINFO g_bmi;
static uint32_t g_blit_buf[DOOM_W * DOOM_H]; // BGRA-swapped copy
static void render_frame(const uint8_t* rgba)
{
// Swap R <-> B so Win32 GDI sees correct colours
const uint32_t* src = (const uint32_t*)rgba;
for (int i = 0; i < DOOM_W * DOOM_H; i++) {
uint32_t p = src[i];
// RGBA -> BGRA: swap byte 0 (R) and byte 2 (B)
g_blit_buf[i] = (p & 0xFF00FF00u)
| ((p & 0x00FF0000u) >> 16)
| ((p & 0x000000FFu) << 16);
}
PAINTSTRUCT ps;
HDC hdc = BeginPaint((HWND)0, &ps);
StretchDIBits(
hdc,
0, 0, DISP_W, DISP_H, // destination — scaled up
0, 0, DOOM_W, DOOM_H, // source — native 320×200
g_blit_buf,
&g_bmi,
DIB_RGB_COLORS,
SRCCOPY
);
EndPaint((HWND)0, &ps);
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(const char* cmdline)
{
(void)cmdline;
g_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleA(g_stdout, "doom: starting\n", 15, 0, 0);
// Prepare the BMI header once (top-down, like video.c)
memset(&g_bmi, 0, sizeof(g_bmi));
g_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
g_bmi.bmiHeader.biWidth = DOOM_W;
g_bmi.bmiHeader.biHeight = -DOOM_H; // negative = top-down
g_bmi.bmiHeader.biPlanes = 1;
g_bmi.bmiHeader.biBitCount = 32;
g_bmi.bmiHeader.biCompression = BI_RGB;
// Wire up PureDOOM
doom_set_print(doom_print_wrapper);
doom_set_malloc(doom_malloc_wrapper, doom_free_wrapper);
doom_set_file_io(
doom_open_wrapper,
doom_close_wrapper,
doom_read_wrapper,
doom_write_wrapper,
doom_seek_wrapper,
doom_tell_wrapper,
doom_eof_wrapper
);
doom_set_gettime(doom_gettime_wrapper);
doom_set_exit(doom_exit_wrapper);
doom_set_getenv(doom_getenv_wrapper);
// Resolution + modern WASD bindings
doom_set_resolution(DOOM_W, DOOM_H);
doom_set_default_int("key_up", DOOM_KEY_W);
doom_set_default_int("key_down", DOOM_KEY_S);
doom_set_default_int("key_strafeleft", DOOM_KEY_A);
doom_set_default_int("key_straferight", DOOM_KEY_D);
doom_set_default_int("key_use", DOOM_KEY_E);
doom_set_default_int("mouse_move", 0);
// No extra args — PureDOOM will scan for doom1.wad / doom.wad
const char* args[] = { "doom" };
doom_init(1, (char**)args, 0);
WriteConsoleA(g_stdout, "doom: entering game loop\n", 25, 0, 0);
// Game loop — identical shape to video.c's Bink loop
while (1) {
poll_input();
doom_update();
render_frame((const uint8_t*)doom_get_framebuffer(4 /* RGBA */));
}
// Unreachable: doom_exit() -> ExitProcess() handles clean shutdown
ExitProcess(0);
}