// 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 #define DOOM_IMPLEMENTATION #include "PureDOOM.h" #include #include #include #include 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); }