--- /dev/null
+*.swp
+*.o
+*.d
+*.obj
+*.OBJ
+*.exe
+*.EXE
+*.lnk
+*.LNK
+*.lbc
+*.LBC
+*.lib
+*.LIB
+*.err
+*.ERR
+*.log
+*.LOG
+*sdf
+game
+*.zip
+data
+*.a
+*.dll
+*.occ
+*.OCC
+*.map
+*.MAP
+*.CFG
+*.cfg
+*.$$$
+*.dep
+*.DEP
+*.odj
+*.COF
+*.dja
+*.DJA
--- /dev/null
+!ifdef __UNIX__
+dosobj = src/dos/main.obj src/dos/gfx.obj src/dos/timer.obj src/dos/watdpmi.obj &
+ src/dos/vbe.obj src/dos/vga.obj src/dos/keyb.obj src/dos/mouse.obj &
+ src/dos/logger.obj
+gameobj = src/game.obj src/util.obj
+incpath = -Isrc -Isrc/dos -Ilibs/imago/src
+libpath = libpath libs/imago
+RM = rm -f
+!else
+dosobj = src\dos\main.obj src\dos\gfx.obj src\dos\timer.obj src\dos\watdpmi.obj &
+ src\dos\vbe.obj src\dos\vga.obj src\dos\keyb.obj src\dos\mouse.obj &
+ src\dos\logger.obj
+gameobj = src\game.obj src\util.obj
+incpath = -Isrc -Isrc\dos -Ilibs\imago\src
+libpath = libpath libs\imago
+RM = del
+!endif
+
+obj = $(dosobj) $(gameobj)
+bin = game.exe
+
+CC = wcc386
+LD = wlink
+CFLAGS = -d3 -5 -fp5 -otebmileran -s -zq -bt=dos $(incpath)
+LDFLAGS = option map $(libpath)
+# library { $(libs) }
+
+$(bin): $(obj)
+ %write objects.lnk $(obj)
+ $(LD) debug all name $@ system dos4g file { @objects } $(LDFLAGS)
+
+.c: src;src/dos
+.asm: src;src/dos
+
+.c.obj: .autodepend
+ $(CC) -fo=$@ $(CFLAGS) $[*
+
+.asm.obj:
+ nasm -f obj -o $@ $[*.asm
+
+clean: .symbolic
+ $(RM) src\*.obj
+ $(RM) src\dos\*.obj
+ $(RM) objects.lnk
+ $(RM) $(bin)
--- /dev/null
+#ifndef DPMI_H_
+#define DPMI_H_
+
+#ifdef __DJGPP__
+#include <dpmi.h>
+#include <sys/nearptr.h>
+
+#define virt_to_phys(v) ((v) + __djgpp_base_address)
+#define phys_to_virt(p) ((p) - __djgpp_base_address)
+
+#else /* not djgpp (basically watcom) */
+
+#define virt_to_phys(v) (v)
+#define phys_to_virt(p) (p)
+
+#endif /* __DJGPP__ */
+
+#include "inttypes.h"
+#include "util.h"
+
+#pragma pack (push, 1)
+struct dpmi_regs {
+ uint32_t edi, esi, ebp;
+ uint32_t reserved;
+ uint32_t ebx, edx, ecx, eax;
+ uint16_t flags;
+ uint16_t es, ds, fs, gs;
+ uint16_t ip, cs, sp, ss;
+} PACKED;
+#pragma pack (pop)
+
+uint16_t dpmi_alloc(unsigned int par, uint16_t *sel);
+void dpmi_free(uint16_t sel);
+void dpmi_int(int inum, struct dpmi_regs *regs);
+void *dpmi_mmap(uint32_t phys_addr, unsigned int size);
+void dpmi_munmap(void *addr);
+
+#ifdef __WATCOMC__
+#pragma aux dpmi_alloc = \
+ "mov ax, 0x100" \
+ "int 0x31" \
+ "mov [edi], dx" \
+ "jnc alloc_skip_err" \
+ "xor ax, ax" \
+ "alloc_skip_err:" \
+ value[ax] \
+ parm[ebx][edi] \
+ modify[dx];
+
+#pragma aux dpmi_free = \
+ "mov ax, 0x101" \
+ "int 0x31" \
+ parm[dx] \
+ modify[ax];
+
+#pragma aux dpmi_int = \
+ "mov ax, 0x300" \
+ "xor ecx, ecx" \
+ "int 0x31" \
+ parm[ebx][edi] \
+ modify[ax ecx];
+
+#pragma aux dpmi_mmap = \
+ "mov ax, 0x800" \
+ "mov cx, bx" \
+ "shr ebx, 16" \
+ "mov di, si" \
+ "shr esi, 16" \
+ "int 0x31" \
+ "jnc mmap_skip_err" \
+ "xor bx, bx" \
+ "xor cx, cx" \
+ "mmap_skip_err:" \
+ "mov ax, bx" \
+ "shl eax, 16" \
+ "mov ax, cx" \
+ value[eax] \
+ parm[ebx][esi] \
+ modify[bx cx di esi];
+
+#pragma aux dpmi_munmap = \
+ "mov ax, 0x801" \
+ "mov cx, bx" \
+ "shr ebx, 16" \
+ "int 0x31" \
+ parm[ebx] \
+ modify[ax cx ebx];
+#endif /* __WATCOMC__ */
+
+#ifdef __DJGPP__
+#define dpmi_int(inum, regs) __dpmi_int((inum), (__dpmi_regs*)(regs))
+#endif
+
+#endif /* DPMI_H_ */
--- /dev/null
+#ifdef __DJGPP__
+#include <dpmi.h>
+#include <sys/nearptr.h>
+#include "cdpmi.h"
+#include "inttypes.h"
+
+uint16_t dpmi_alloc(unsigned int par, uint16_t *sel)
+{
+ int tmp;
+ uint16_t seg = __dpmi_allocate_dos_memory(par, &tmp);
+ *sel = tmp;
+ return seg;
+}
+
+void dpmi_free(uint16_t sel)
+{
+ __dpmi_free_dos_memory(sel);
+}
+
+void *dpmi_mmap(uint32_t phys_addr, unsigned int size)
+{
+ __dpmi_meminfo mem;
+ mem.address = phys_addr;
+ mem.size = size;
+ __dpmi_physical_address_mapping(&mem);
+ return (void*)(mem.address - __djgpp_base_address);
+}
+
+void dpmi_munmap(void *addr)
+{
+ __dpmi_meminfo mem;
+ mem.address = (uint32_t)addr + __djgpp_base_address;
+ __dpmi_free_physical_address_mapping(&mem);
+}
+#endif /* __DJGPP__ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include "gfx.h"
+#include "vbe.h"
+#include "vga.h"
+
+#define SAME_BPP(a, b) \
+ ((a) == (b) || ((a) == 16 && (b) == 15) || ((a) == 15 && (b) == 16) || \
+ ((a) == 32 && (b) == 24) || ((a) == 24 && (b) == 32))
+
+static uint32_t calc_mask(int sz, int pos);
+
+static struct video_mode *vmodes;
+static int num_vmodes;
+
+static int vbe_init_ver;
+static struct vbe_info vbe;
+
+/* current mode */
+static struct video_mode *curmode;
+static void *vpgaddr[2];
+static int fbidx;
+static int pgcount, pgsize, fbsize;
+
+int init_video(void)
+{
+ int i, num, max_modes;
+ struct video_mode *vmptr;
+
+ if(vbe_info(&vbe) == -1) {
+ fprintf(stderr, "failed to retrieve VBE information\n");
+ return -1;
+ }
+ vbe_print_info(stdout, &vbe);
+
+ num_vmodes = 0;
+ max_modes = 4; /* TODO change */
+ if(!(vmodes = malloc(max_modes * sizeof *vmodes))) {
+ fprintf(stderr, "failed to allocate video modes list\n");
+ return -1;
+ }
+
+ num = vbe_num_modes(&vbe);
+ for(i=0; i<num; i++) {
+ struct vbe_mode_info minf;
+
+ if(vbe_mode_info(vbe.modes[i], &minf) == -1) {
+ continue;
+ }
+
+ if(num_vmodes >= max_modes) {
+ int newmax = max_modes ? (max_modes << 1) : 16;
+ if(!(vmptr = realloc(vmodes, newmax * sizeof *vmodes))) {
+ fprintf(stderr, "failed to grow video mode list (%d)\n", newmax);
+ free(vmodes);
+ return -1;
+ }
+ vmodes = vmptr;
+ max_modes = newmax;
+ }
+
+ vmptr = vmodes + num_vmodes++;
+ memset(vmptr, 0, sizeof *vmptr);
+ vmptr->mode = vbe.modes[i];
+ vmptr->xsz = minf.xres;
+ vmptr->ysz = minf.yres;
+ vmptr->bpp = minf.bpp;
+ if(minf.mem_model == VBE_TYPE_DIRECT) {
+ vmptr->rbits = minf.rsize;
+ vmptr->gbits = minf.gsize;
+ vmptr->bbits = minf.bsize;
+ vmptr->rshift = minf.rpos;
+ vmptr->gshift = minf.gpos;
+ vmptr->bshift = minf.bpos;
+ vmptr->rmask = calc_mask(minf.rsize, minf.rpos);
+ vmptr->gmask = calc_mask(minf.gsize, minf.gpos);
+ vmptr->bmask = calc_mask(minf.bsize, minf.bpos);
+ }
+
+ printf("%04x: ", vbe.modes[i]);
+ vbe_print_mode_info(stdout, &minf);
+ }
+ fflush(stdout);
+
+ vbe_init_ver = VBE_VER_MAJOR(vbe.ver);
+ return 0;
+}
+
+void cleanup_video(void)
+{
+ free(vmodes);
+}
+
+struct video_mode *video_modes(void)
+{
+ return vmodes;
+}
+
+int num_video_modes(void)
+{
+ return num_vmodes;
+}
+
+int match_video_mode(int xsz, int ysz, int bpp)
+{
+ int i, best = -1;
+ struct video_mode *vm;
+
+ for(i=0; i<num_vmodes; i++) {
+ vm = vmodes + i;
+ if(vm->xsz != xsz || vm->ysz != ysz) continue;
+ if(SAME_BPP(vm->bpp, bpp)) {
+ best = i;
+ }
+ if(vm->bpp == bpp) break;
+ }
+
+ if(best == -1) {
+ fprintf(stderr, "failed to find video mode %dx%d %d bpp)\n", xsz, ysz, bpp);
+ return -1;
+ }
+ return best;
+}
+
+int find_video_mode(int mode)
+{
+ int i;
+ struct video_mode *vm;
+
+ vm = vmodes;
+ for(i=0; i<num_vmodes; i++) {
+ if(vm->mode == mode) return i;
+ }
+ return -1;
+}
+
+void *set_video_mode(int idx, int nbuf)
+{
+ unsigned int mode;
+ struct vbe_mode_info minf;
+ struct video_mode *vm = vmodes + idx;
+
+ printf("setting video mode %x (%dx%d %d bpp)\n", (unsigned int)vm->mode,
+ vm->xsz, vm->ysz, vm->bpp);
+
+ mode = vm->mode | VBE_MODE_LFB;
+ if(vbe_setmode(mode) == -1) {
+ mode = vm->mode;
+ if(vbe_setmode(mode) == -1) {
+ fprintf(stderr, "failed to set video mode %x\n", (unsigned int)vm->mode);
+ return 0;
+ }
+ printf("Warning: failed to get a linear framebuffer. falling back to banked mode\n");
+ }
+
+ vbe_mode_info(mode, &minf);
+
+ curmode = vm;
+ if(nbuf < 1) nbuf = 1;
+ if(nbuf > 2) nbuf = 2;
+ pgcount = nbuf > minf.num_img_pages ? minf.num_img_pages : nbuf;
+ pgsize = (vm->xsz * vm->bpp / 8) * vm->ysz;
+ fbsize = pgcount * pgsize;
+
+ vpgaddr[0] = (void*)dpmi_mmap(minf.fb_addr, fbsize);
+ memset(vpgaddr[0], 0xaa, fbsize);
+
+ if(pgcount > 1) {
+ vpgaddr[1] = (char*)vpgaddr[0] + pgsize;
+ fbidx = 1;
+ page_flip(FLIP_NOW); /* start with the second page visible */
+ } else {
+ fbidx = 0;
+ vpgaddr[1] = 0;
+ }
+ return vpgaddr[0];
+}
+
+int set_text_mode(void)
+{
+ vga_setmode(3);
+ curmode = 0;
+ return 0;
+}
+
+void *page_flip(int vsync)
+{
+ if(!vpgaddr[1]) {
+ /* page flipping not supported */
+ return vpgaddr[0];
+ }
+
+ vbe_swap(fbidx ? pgsize : 0, vsync ? VBE_SWAP_VBLANK : VBE_SWAP_NOW);
+ fbidx = (fbidx + 1) & 1;
+
+ return vpgaddr[fbidx];
+}
+
+static uint32_t calc_mask(int sz, int pos)
+{
+ int i;
+ uint32_t mask = 0;
+ while(sz-- > 0) {
+ mask = (mask << 1) | 1;
+ }
+ return mask << pos;
+}
--- /dev/null
+#ifndef GFX_H_
+#define GFX_H_
+
+#include "inttypes.h"
+
+struct video_mode {
+ uint16_t mode;
+ short xsz, ysz, bpp;
+ short rbits, gbits, bbits;
+ short rshift, gshift, bshift;
+ uint32_t rmask, gmask, bmask;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int init_video(void);
+void cleanup_video(void);
+
+struct video_mode *video_modes(void);
+int num_video_modes(void);
+
+int match_video_mode(int xsz, int ysz, int bpp);
+int find_video_mode(int mode);
+
+/* argument is the mode list index [0, nmodes-1] */
+void *set_video_mode(int idx, int nbuf);
+int set_text_mode(void);
+
+void set_palette(int idx, int r, int g, int b);
+
+enum {
+ FLIP_NOW,
+ FLIP_VBLANK
+};
+/* page flip and return pointer to the start of the display area (front buffer) */
+void *page_flip(int vsync);
+
+#ifdef __WATCOMC__
+void wait_vsync(void);
+#pragma aux wait_vsync = \
+ "mov dx, 0x3da" \
+ "l1:" \
+ "in al, dx" \
+ "and al, 0x8" \
+ "jnz l1" \
+ "l2:" \
+ "in al, dx" \
+ "and al, 0x8" \
+ "jz l2" \
+ modify[al dx];
+#endif
+
+#ifdef __DJGPP__
+#define wait_vsync() asm volatile ( \
+ "mov $0x3da, %%dx\n\t" \
+ "0:\n\t" \
+ "in %%dx, %%al\n\t" \
+ "and $8, %%al\n\t" \
+ "jnz 0b\n\t" \
+ "0:\n\t" \
+ "in %%dx, %%al\n\t" \
+ "and $8, %%al\n\t" \
+ "jz 0b\n\t" \
+ :::"%eax","%edx")
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GFX_H_ */
--- /dev/null
+/*
+colcycle - color cycling image viewer
+Copyright (C) 2016 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef INT_TYPES_H_
+#define INT_TYPES_H_
+
+/*#if defined(__WATCOMC__) && __WATCOMC__ < 0x1200*/
+#if defined(__DOS__) || defined(__MSDOS__)
+typedef char int8_t;
+typedef short int16_t;
+typedef long int32_t;
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned long uint32_t;
+
+typedef long intptr_t;
+typedef unsigned long uintptr_t;
+#else
+#include <stdint.h>
+#endif
+
+#endif /* INT_TYPES_H_ */
--- /dev/null
+/*
+DOS interrupt-based keyboard driver.
+Copyright (C) 2013 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License
+along with the program. If not, see <http://www.gnu.org/licenses/>
+*/
+#define KEYB_C_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <conio.h>
+#include <dos.h>
+
+#ifdef __WATCOMC__
+#include <i86.h>
+#endif
+#ifdef __DJGPP__
+#include <dpmi.h>
+#include <go32.h>
+#include <pc.h>
+#endif
+
+#include "keyb.h"
+#include "scancode.h"
+#include "inttypes.h"
+
+#define KB_INTR 0x9
+#define KB_PORT 0x60
+
+#define PIC1_CMD_PORT 0x20
+#define OCW2_EOI (1 << 5)
+
+#ifdef __WATCOMC__
+#define INTERRUPT __interrupt __far
+
+#define DONE_INIT (prev_handler)
+static void (INTERRUPT *prev_handler)();
+#endif
+
+#ifdef __DJGPP__
+#define INTERRUPT
+
+#define DONE_INIT prev_intr.pm_offset
+static _go32_dpmi_seginfo intr, prev_intr;
+
+#define outp(p, v) outportb(p, v)
+#define inp(p) inportb(p)
+#endif
+
+static void INTERRUPT kbintr();
+
+static int *buffer;
+static int buffer_size, buf_ridx, buf_widx;
+static int last_key;
+
+static unsigned int num_pressed;
+static unsigned char keystate[256];
+
+#define ADVANCE(x) ((x) = ((x) + 1) % buffer_size)
+
+int kb_init(int bufsz)
+{
+ if(DONE_INIT) {
+ fprintf(stderr, "keyboard driver already initialized!\n");
+ return 0;
+ }
+
+ buffer_size = bufsz;
+ if(buffer_size && !(buffer = malloc(buffer_size * sizeof *buffer))) {
+ fprintf(stderr, "failed to allocate input buffer, continuing without\n");
+ buffer_size = 0;
+ }
+ buf_ridx = buf_widx = 0;
+ last_key = -1;
+
+ memset(keystate, 0, sizeof keystate);
+ num_pressed = 0;
+
+ /* set our interrupt handler */
+ _disable();
+#ifdef __WATCOMC__
+ prev_handler = _dos_getvect(KB_INTR);
+ _dos_setvect(KB_INTR, kbintr);
+#endif
+#ifdef __DJGPP__
+ _go32_dpmi_get_protected_mode_interrupt_vector(KB_INTR, &prev_intr);
+ intr.pm_offset = (intptr_t)kbintr;
+ intr.pm_selector = _go32_my_cs();
+ _go32_dpmi_allocate_iret_wrapper(&intr);
+ _go32_dpmi_set_protected_mode_interrupt_vector(KB_INTR, &intr);
+#endif
+ _enable();
+
+ return 0;
+}
+
+void kb_shutdown(void)
+{
+ if(!DONE_INIT) {
+ return;
+ }
+
+ /* restore the original interrupt handler */
+ _disable();
+#ifdef __WATCOMC__
+ _dos_setvect(KB_INTR, prev_handler);
+#endif
+#ifdef __DJGPP__
+ _go32_dpmi_set_protected_mode_interrupt_vector(KB_INTR, &prev_intr);
+ _go32_dpmi_free_iret_wrapper(&intr);
+#endif
+ _enable();
+
+ free(buffer);
+}
+
+int kb_isdown(int key)
+{
+ switch(key) {
+ case KB_ANY:
+ return num_pressed;
+
+ case KB_ALT:
+ return keystate[KB_LALT] + keystate[KB_RALT];
+
+ case KB_CTRL:
+ return keystate[KB_LCTRL] + keystate[KB_RCTRL];
+ }
+
+ if(isalpha(key)) {
+ key = tolower(key);
+ }
+ return keystate[key];
+}
+
+#ifdef __WATCOMC__
+void halt(void);
+#pragma aux halt = \
+ "sti" \
+ "hlt";
+#endif
+
+#ifdef __DJGPP__
+#define halt() asm volatile("sti\n\thlt\n\t")
+#endif
+
+void kb_wait(void)
+{
+ int key;
+ while((key = kb_getkey()) == -1) {
+#ifdef USE_HLT
+ /* put the processor to sleep while waiting for keypresses, but first
+ * make sure interrupts are enabled, or we'll sleep forever
+ */
+ halt();
+#endif
+ }
+ kb_putback(key);
+}
+
+int kb_getkey(void)
+{
+ int res;
+
+ if(buffer) {
+ if(buf_ridx == buf_widx) {
+ return -1;
+ }
+ res = buffer[buf_ridx];
+ ADVANCE(buf_ridx);
+ } else {
+ res = last_key;
+ last_key = -1;
+ }
+ return res;
+}
+
+void kb_putback(int key)
+{
+ if(buffer) {
+ /* go back a place */
+ if(--buf_ridx < 0) {
+ buf_ridx += buffer_size;
+ }
+
+ /* if the write end hasn't caught up with us, go back one place
+ * and put it there, otherwise just overwrite the oldest key which
+ * is right where we were.
+ */
+ if(buf_ridx == buf_widx) {
+ ADVANCE(buf_ridx);
+ }
+
+ buffer[buf_ridx] = key;
+ } else {
+ last_key = key;
+ }
+}
+
+static void INTERRUPT kbintr()
+{
+ unsigned char code;
+ int key, c, press;
+
+ code = inp(KB_PORT);
+
+ if(code >= 128) {
+ press = 0;
+ code -= 128;
+
+ if(num_pressed > 0) {
+ num_pressed--;
+ }
+ } else {
+ press = 1;
+
+ num_pressed++;
+ }
+
+ key = scantbl[code];
+ c = (keystate[KB_LSHIFT] | keystate[KB_RSHIFT]) ? scantbl_shift[code] : key;
+
+ if(press) {
+ /* append to buffer */
+ last_key = c;
+ if(buffer_size > 0) {
+ buffer[buf_widx] = c;
+ ADVANCE(buf_widx);
+ /* if the write end overtook the read end, advance the read end
+ * too, to discard the oldest keypress from the buffer
+ */
+ if(buf_widx == buf_ridx) {
+ ADVANCE(buf_ridx);
+ }
+ }
+ }
+
+ /* and update keystate table */
+ keystate[key] = press;
+
+ outp(PIC1_CMD_PORT, OCW2_EOI); /* send end-of-interrupt */
+}
--- /dev/null
+/*
+DOS interrupt-based keyboard driver.
+Copyright (C) 2013 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License
+along with the program. If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef KEYB_H_
+#define KEYB_H_
+
+#include "game.h"
+
+#define KB_ANY (-1)
+#define KB_ALT (-2)
+#define KB_CTRL (-3)
+#define KB_SHIFT (-4)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int kb_init(int bufsz); /* bufsz can be 0 for no buffered keys */
+void kb_shutdown(void); /* don't forget to call this at the end! */
+
+/* Boolean predicate for testing the current state of a particular key.
+ * You may also pass KB_ANY to test if any key is held down.
+ */
+int kb_isdown(int key);
+
+/* waits for any keypress */
+void kb_wait(void);
+
+/* removes and returns a single key from the input buffer.
+ * If buffering is disabled (initialized with kb_init(0)), then it always
+ * returns the last key pressed.
+ */
+int kb_getkey(void);
+
+void kb_putback(int key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* KEYB_H_ */
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "logger.h"
+
+int init_logger(const char *fname)
+{
+ int fd;
+ if((fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1) {
+ fprintf(stderr, "init_logger: failed to open %s: %s\n", fname, strerror(errno));
+ return -1;
+ }
+
+ close(1);
+ close(2);
+ dup(fd);
+ dup(fd);
+ return 0;
+}
--- /dev/null
+#ifndef LOGGER_H_
+#define LOGGER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int init_logger(const char *fname);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LOGGER_H_ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include "game.h"
+#include "keyb.h"
+#include "timer.h"
+#include "gfx.h"
+#include "logger.h"
+#include "cdpmi.h"
+
+static void draw(void);
+
+static struct video_mode *vmode;
+static void *vmem;
+
+int main(int argc, char **argv)
+{
+ struct video_mode *vmodes;
+ int vmidx;
+
+ init_logger("game.log");
+
+ init_timer(100);
+ kb_init(32);
+
+ if(init_video() == -1) {
+ return 1;
+ }
+ vmodes = video_modes();
+
+ if((vmidx = match_video_mode(640, 480, 16)) == -1) {
+ return 1;
+ }
+ if(!(vmem = set_video_mode(vmidx, 1))) {
+ return 1;
+ }
+ vmode = vmodes + vmidx;
+
+ fb_width = vmode->xsz;
+ fb_height = vmode->ysz;
+ fb_size = (vmode->xsz * vmode->bpp / 8) * vmode->ysz;
+
+ reset_timer();
+
+ for(;;) {
+ int key;
+ while((key = kb_getkey()) != -1) {
+ if(key == 27) goto break_evloop;
+ }
+
+ time_msec = get_msec();
+ draw();
+ }
+
+break_evloop:
+ set_text_mode();
+ kb_shutdown();
+ return 0;
+}
+
+static void draw(void)
+{
+ int i, j;
+ uint16_t *pptr = vmem;
+
+ for(i=0; i<fb_height; i++) {
+ for(j=0; j<fb_width; j++) {
+ int chess = (i >> 2) == (j >> 2);
+ *pptr++ = chess ? 0xff00 : 0x00ff;
+ }
+ }
+}
+
+void swap_buffers(void *pixels)
+{
+ wait_vsync();
+ memcpy(vmem, pixels, fb_size);
+}
--- /dev/null
+; vi:set filetype=nasm:
+; foo_ are watcom functions, _foo are djgpp functions
+
+QUERY equ 0
+SHOW equ 1
+HIDE equ 2
+READ equ 3
+WRITE equ 4
+PIXRATE equ 15
+XLIM equ 7
+YLIM equ 8
+
+PUSHA_EAX_OFFS equ 28
+PUSHA_ECX_OFFS equ 20
+PUSHA_EDX_OFFS equ 16
+
+ section .text
+ bits 32
+
+; int have_mouse(void)
+ global have_mouse_
+ global _have_mouse
+have_mouse_:
+_have_mouse:
+ pusha
+ mov ax, QUERY
+ int 0x33
+ and eax, 0xffff
+ mov [esp + PUSHA_EAX_OFFS], eax
+ popa
+ ret
+
+; void show_mouse(int show)
+ global show_mouse_
+show_mouse_:
+ pusha
+ test ax, ax
+ mov ax, HIDE
+ jz .skip
+ mov ax, SHOW
+.skip: int 0x33
+ popa
+ ret
+
+ global _show_mouse
+_show_mouse:
+ push ebp
+ mov ebp, esp
+ push ebx
+ push esi
+ push edi
+ mov ax, [ebp + 8]
+ test ax, ax
+ mov ax, HIDE
+ jz .skip
+ mov ax, SHOW
+.skip: int 0x33
+ pop edi
+ pop esi
+ pop ebx
+ pop ebp
+ ret
+
+; int read_mouse(int *xp, int *yp)
+ global read_mouse_
+read_mouse_:
+ pusha
+ mov esi, eax ; xp
+ mov edi, edx ; yp
+ mov ax, READ
+ int 0x33
+ xor eax, eax
+ and ecx, 0xffff
+ and edx, 0xffff
+ mov ax, bx
+ mov [esp + PUSHA_EAX_OFFS], eax
+ mov [esi], ecx
+ mov [edi], edx
+ popa
+ ret
+
+ global _read_mouse
+_read_mouse:
+ push ebp
+ mov ebp, esp
+ push ebx
+ push esi
+ push edi
+ mov ax, READ
+ int 0x33
+ xor eax, eax
+ mov ax, bx
+ and ecx, 0xffff
+ mov ebx, [ebp + 8]
+ mov [ebx], ecx
+ and edx, 0xffff
+ mov ebx, [ebp + 12]
+ mov [ebx], edx
+ pop edi
+ pop esi
+ pop ebx
+ pop ebp
+ ret
+
+; void set_mouse(int x, int y)
+ global set_mouse_
+set_mouse_:
+ pusha
+ mov cx, ax
+ mov ax, WRITE
+ int 0x33
+ popa
+ ret
+
+ global _set_mouse
+_set_mouse:
+ push ebp
+ mov ebp, esp
+ push ebx
+ push esi
+ push edi
+ mov ax, WRITE
+ mov cx, [ebp + 8]
+ mov dx, [ebp + 12]
+ int 0x33
+ pop edi
+ pop esi
+ pop ebx
+ pop ebp
+ ret
+
+; void set_mouse_limits(int xmin, int ymin, int xmax, int ymax)
+ global set_mouse_limits_
+set_mouse_limits_:
+ pusha
+ mov cx, ax
+ mov dx, bx
+ mov ax, XLIM
+ int 0x33
+ mov ax, YLIM
+ mov cx, [esp + PUSHA_EDX_OFFS]
+ mov dx, [esp + PUSHA_ECX_OFFS]
+ int 0x33
+ popa
+ ret
+
+ global _set_mouse_limits
+_set_mouse_limits:
+ push ebp
+ mov ebp, esp
+ push ebx
+ push esi
+ push edi
+ mov ax, XLIM
+ mov cx, [ebp + 8]
+ mov dx, [ebp + 16]
+ int 0x33
+ mov ax, YLIM
+ mov cx, [ebp + 12]
+ mov dx, [ebp + 20]
+ int 0x33
+ pop edi
+ pop esi
+ pop ebx
+ pop ebp
+ ret
+
+; void set_mouse_rate(int xrate, int yrate)
+ global set_mouse_rate_
+set_mouse_rate_:
+ pusha
+ mov cx, ax
+ mov ax, PIXRATE
+ int 0x33
+ popa
+ ret
+
+ global _set_mouse_rate
+_set_mouse_rate:
+ push ebp
+ mov ebp, esp
+ push ebx
+ push esi
+ push edi
+ mov ax, PIXRATE
+ mov cx, [esp + 4]
+ mov dx, [esp + 8]
+ int 0x33
+ pop edi
+ pop esi
+ pop ebx
+ pop ebp
+ ret
--- /dev/null
+#ifndef MOUSE_H_
+#define MOUSE_H_
+
+enum {
+ MOUSE_LEFT = 1,
+ MOUSE_RIGHT = 2,
+ MOUSE_MIDDLE = 4
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int have_mouse(void);
+void show_mouse(int show);
+int read_mouse(int *xp, int *yp);
+void set_mouse(int x, int y);
+void set_mouse_limits(int xmin, int ymin, int xmax, int ymax);
+void set_mouse_rate(int xrate, int yrate);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MOUSE_H_ */
--- /dev/null
+/*
+colcycle - color cycling image viewer
+Copyright (C) 2016 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef PIT8254_H_
+#define PIT8254_H_
+
+/* frequency of the oscillator driving the 8254 timer */
+#define OSC_FREQ_HZ 1193182
+
+/* I/O ports connected to the 8254 */
+#define PORT_DATA0 0x40
+#define PORT_DATA1 0x41
+#define PORT_DATA2 0x42
+#define PORT_CMD 0x43
+
+/* command bits */
+#define CMD_CHAN0 0
+#define CMD_CHAN1 (1 << 6)
+#define CMD_CHAN2 (2 << 6)
+#define CMD_RDBACK (3 << 6)
+
+#define CMD_LATCH 0
+#define CMD_ACCESS_LOW (1 << 4)
+#define CMD_ACCESS_HIGH (2 << 4)
+#define CMD_ACCESS_BOTH (3 << 4)
+
+#define CMD_OP_INT_TERM 0
+#define CMD_OP_ONESHOT (1 << 1)
+#define CMD_OP_RATE (2 << 1)
+#define CMD_OP_SQWAVE (3 << 1)
+#define CMD_OP_SW_STROBE (4 << 1)
+#define CMD_OP_HW_STROBE (5 << 1)
+
+#define CMD_MODE_BIN 0
+#define CMD_MODE_BCD 1
+
+#endif /* PIT8254_H_ */
--- /dev/null
+/*
+colcycle - color cycling image viewer
+Copyright (C) 2016 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef KEYB_C_
+#error "do not include scancode.h anywhere..."
+#endif
+
+/* table with rough translations from set 1 scancodes to ASCII-ish */
+static int scantbl[] = {
+ 0, KB_ESC, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', /* 0 - e */
+ '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', /* f - 1c */
+ KB_LCTRL, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', /* 1d - 29 */
+ KB_LSHIFT, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', KB_RSHIFT, /* 2a - 36 */
+ KB_NUM_MUL, KB_LALT, ' ', KB_CAPSLK, KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6, KB_F7, KB_F8, KB_F9, KB_F10, /* 37 - 44 */
+ KB_NUMLK, KB_SCRLK, KB_NUM_7, KB_NUM_8, KB_NUM_9, KB_NUM_MINUS, KB_NUM_4, KB_NUM_5, KB_NUM_6, KB_NUM_PLUS, /* 45 - 4e */
+ KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_0, KB_NUM_DOT, KB_SYSRQ, 0, 0, KB_F11, KB_F12, /* 4d - 58 */
+ 0, 0, 0, 0, 0, 0, 0, /* 59 - 5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 70 - 7f */
+};
+
+static int scantbl_shift[] = {
+ 0, KB_ESC, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', /* 0 - e */
+ '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', /* f - 1c */
+ KB_LCTRL, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', /* 1d - 29 */
+ KB_LSHIFT, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', KB_RSHIFT, /* 2a - 36 */
+ KB_NUM_MUL, KB_LALT, ' ', KB_CAPSLK, KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6, KB_F7, KB_F8, KB_F9, KB_F10, /* 37 - 44 */
+ KB_NUMLK, KB_SCRLK, KB_NUM_7, KB_NUM_8, KB_NUM_9, KB_NUM_MINUS, KB_NUM_4, KB_NUM_5, KB_NUM_6, KB_NUM_PLUS, /* 45 - 4e */
+ KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_0, KB_NUM_DOT, KB_SYSRQ, 0, 0, KB_F11, KB_F12, /* 4d - 58 */
+ 0, 0, 0, 0, 0, 0, 0, /* 59 - 5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 70 - 7f */
+};
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <conio.h>
+#include <dos.h>
+
+#ifdef __WATCOMC__
+#include <i86.h>
+#endif
+
+#ifdef __DJGPP__
+#include <dpmi.h>
+#include <go32.h>
+#include <pc.h>
+#endif
+
+#include "pit8254.h"
+#include "inttypes.h"
+#include "util.h"
+
+#define PIT_TIMER_INTR 8
+#define DOS_TIMER_INTR 0x1c
+
+/* macro to divide and round to the nearest integer */
+#define DIV_ROUND(a, b) \
+ ((a) / (b) + ((a) % (b)) / ((b) / 2))
+
+static void set_timer_reload(int reload_val);
+static void cleanup(void);
+
+#ifdef __WATCOMC__
+#define INTERRUPT __interrupt __far
+
+static void INTERRUPT dos_timer_intr();
+
+static void (INTERRUPT *prev_timer_intr)();
+#endif
+
+#ifdef __DJGPP__
+#define INTERRUPT
+
+static _go32_dpmi_seginfo intr, prev_intr;
+
+#define outp(p, v) outportb(p, v)
+#endif
+
+static void INTERRUPT timer_irq();
+
+static volatile unsigned long ticks;
+static unsigned long tick_interval, ticks_per_dos_intr;
+static int inum;
+
+void init_timer(int res_hz)
+{
+ _disable();
+
+ if(res_hz > 0) {
+ int reload_val = DIV_ROUND(OSC_FREQ_HZ, res_hz);
+ set_timer_reload(reload_val);
+
+ tick_interval = DIV_ROUND(1000, res_hz);
+ ticks_per_dos_intr = DIV_ROUND(65535L, reload_val);
+
+ inum = PIT_TIMER_INTR;
+#ifdef __WATCOMC__
+ prev_timer_intr = _dos_getvect(inum);
+ _dos_setvect(inum, timer_irq);
+#endif
+#ifdef __DJGPP__
+ _go32_dpmi_get_protected_mode_interrupt_vector(inum, &prev_intr);
+ intr.pm_offset = (intptr_t)timer_irq;
+ intr.pm_selector = _go32_my_cs();
+ _go32_dpmi_allocate_iret_wrapper(&intr);
+ _go32_dpmi_set_protected_mode_interrupt_vector(inum, &intr);
+#endif
+ } else {
+ tick_interval = 55;
+
+ inum = DOS_TIMER_INTR;
+#ifdef __WATCOMC__
+ prev_timer_intr = _dos_getvect(inum);
+ _dos_setvect(inum, dos_timer_intr);
+#endif
+#ifdef __DJGPP__
+ assert(0);
+#endif
+ }
+ _enable();
+
+ atexit(cleanup);
+}
+
+static void cleanup(void)
+{
+ if(!inum) {
+ return; /* init hasn't ran, there's nothing to cleanup */
+ }
+
+ _disable();
+ if(inum == PIT_TIMER_INTR) {
+ /* restore the original timer frequency */
+ set_timer_reload(65535);
+ }
+
+ /* restore the original interrupt handler */
+#ifdef __WATCOMC__
+ _dos_setvect(inum, prev_timer_intr);
+#endif
+#ifdef __DJGPP__
+ _go32_dpmi_set_protected_mode_interrupt_vector(inum, &prev_intr);
+ _go32_dpmi_free_iret_wrapper(&intr);
+#endif
+
+ _enable();
+}
+
+void reset_timer(void)
+{
+ ticks = 0;
+}
+
+unsigned long get_msec(void)
+{
+ return ticks * tick_interval;
+}
+
+void sleep_msec(unsigned long msec)
+{
+ unsigned long wakeup_time = ticks + msec / tick_interval;
+ while(ticks < wakeup_time) {
+#ifdef USE_HLT
+ halt();
+#endif
+ }
+}
+
+static void set_timer_reload(int reload_val)
+{
+ outp(PORT_CMD, CMD_CHAN0 | CMD_ACCESS_BOTH | CMD_OP_SQWAVE);
+ outp(PORT_DATA0, reload_val & 0xff);
+ outp(PORT_DATA0, (reload_val >> 8) & 0xff);
+}
+
+#ifdef __WATCOMC__
+static void INTERRUPT dos_timer_intr()
+{
+ ticks++;
+ _chain_intr(prev_timer_intr); /* DOES NOT RETURN */
+}
+#endif
+
+/* first PIC command port */
+#define PIC1_CMD 0x20
+/* end of interrupt control word */
+#define OCW2_EOI (1 << 5)
+
+static void INTERRUPT timer_irq()
+{
+ static unsigned long dos_ticks;
+
+ ticks++;
+
+#ifdef __WATCOMC__
+ if(++dos_ticks >= ticks_per_dos_intr) {
+ /* I suppose the dos irq handler does the EOI so I shouldn't
+ * do it if I am to call the previous function
+ */
+ dos_ticks = 0;
+ _chain_intr(prev_timer_intr); /* XXX DOES NOT RETURN */
+ return; /* just for clarity */
+ }
+#endif
+
+ /* send EOI to the PIC */
+ outp(PIC1_CMD, OCW2_EOI);
+}
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <stddef.h>
+#include <assert.h>
+#include "vbe.h"
+#include "cdpmi.h"
+
+
+#define FIXPTR(ptr) \
+ do { \
+ uint32_t paddr = (uint32_t)(ptr); \
+ uint16_t pseg = paddr >> 16; \
+ uint16_t poffs = paddr & 0xffff; \
+ if(pseg == seg && poffs < 512) { \
+ paddr = ((uint32_t)seg << 4) + poffs; \
+ } else { \
+ paddr = ((uint32_t)pseg << 4) + poffs; \
+ } \
+ (ptr) = (void*)phys_to_virt(paddr); \
+ } while(0)
+
+/* hijack the "VESA" sig field, to pre-cache number of modes */
+#define NMODES(inf) *(uint16_t*)((inf)->sig)
+#define NACCMODES(inf) *(uint16_t*)((inf)->sig + 2)
+
+static int cur_pitch;
+/* TODO update cur_pitch on mode-change and on setscanlen */
+
+
+int vbe_info(struct vbe_info *info)
+{
+ int i, num;
+ void *lowbuf;
+ uint16_t seg, sel;
+ uint16_t *modeptr;
+ struct dpmi_regs regs = {0};
+
+ assert(sizeof *info == 512);
+
+ if(!(seg = dpmi_alloc(sizeof *info / 16, &sel))) {
+ return -1;
+ }
+ lowbuf = (void*)phys_to_virt((uint32_t)seg << 4);
+
+ memcpy(lowbuf, "VBE2", 4);
+
+ regs.eax = 0x4f00;
+ regs.es = seg;
+ regs.edi = 0;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ fprintf(stderr, "vbe_get_info (4f00) failed\n");
+ dpmi_free(sel);
+ return -1;
+ }
+
+ memcpy(info, lowbuf, sizeof *info);
+ dpmi_free(sel);
+
+ FIXPTR(info->oem_name);
+ FIXPTR(info->vendor);
+ FIXPTR(info->product);
+ FIXPTR(info->revstr);
+ FIXPTR(info->modes);
+ FIXPTR(info->accel_modes);
+
+ modeptr = info->modes;
+ while(*modeptr != 0xffff) {
+ if(modeptr - info->modes >= 256) {
+ modeptr = info->modes;
+ break;
+ }
+ modeptr++;
+ }
+ NMODES(info) = modeptr - info->modes;
+
+ if(info->caps & VBE_ACCEL) {
+ modeptr = info->accel_modes;
+ while(*modeptr != 0xffff) {
+ if(modeptr - info->accel_modes >= 256) {
+ modeptr = info->accel_modes;
+ break;
+ }
+ modeptr++;
+ }
+ NACCMODES(info) = modeptr - info->accel_modes;
+ }
+ return 0;
+}
+
+int vbe_num_modes(struct vbe_info *info)
+{
+ return NMODES(info);
+}
+
+int vbe_mode_info(int mode, struct vbe_mode_info *minf)
+{
+ int i, num;
+ void *lowbuf;
+ uint16_t seg, sel;
+ struct dpmi_regs regs = {0};
+
+ assert(sizeof *minf == 256);
+ assert(offsetof(struct vbe_mode_info, max_pixel_clock) == 0x3e);
+
+ if(!(seg = dpmi_alloc(sizeof *minf / 16, &sel))) {
+ return -1;
+ }
+ lowbuf = (void*)phys_to_virt((uint32_t)seg << 4);
+
+ regs.eax = 0x4f01;
+ regs.ecx = mode;
+ regs.es = seg;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ fprintf(stderr, "vbe_mode_info (4f01) failed\n");
+ dpmi_free(sel);
+ return -1;
+ }
+
+ memcpy(minf, lowbuf, sizeof *minf);
+ dpmi_free(sel);
+ return 0;
+}
+
+void vbe_print_info(FILE *fp, struct vbe_info *vinf)
+{
+ fprintf(fp, "vbe version: %u.%u\n", VBE_VER_MAJOR(vinf->ver), VBE_VER_MINOR(vinf->ver));
+ if(VBE_VER_MAJOR(vinf->ver) >= 2) {
+ fprintf(fp, "%s - %s (%s)\n", vinf->vendor, vinf->product, vinf->revstr);
+ if(vinf->caps & VBE_ACCEL) {
+ fprintf(fp, "vbe/af %d.%d\n", VBE_VER_MAJOR(vinf->accel_ver), VBE_VER_MINOR(vinf->accel_ver));
+ }
+ } else {
+ fprintf(fp, "oem: %s\n", vinf->oem_name);
+ }
+ fprintf(fp, "video memory: %dkb\n", vinf->vmem_blk * 64);
+
+ if(vinf->caps) {
+ fprintf(fp, "caps:");
+ if(vinf->caps & VBE_8BIT_DAC) fprintf(fp, " dac8");
+ if(vinf->caps & VBE_NON_VGA) fprintf(fp, " non-vga");
+ if(vinf->caps & VBE_DAC_BLANK) fprintf(fp, " dac-blank");
+ if(vinf->caps & VBE_ACCEL) fprintf(fp, " af");
+ if(vinf->caps & VBE_MUSTLOCK) fprintf(fp, " af-lock");
+ if(vinf->caps & VBE_HWCURSOR) fprintf(fp, " af-curs");
+ if(vinf->caps & VBE_HWCLIP) fprintf(fp, " af-clip");
+ if(vinf->caps & VBE_TRANSP_BLT) fprintf(fp, " af-tblt");
+ fprintf(fp, "\n");
+ }
+
+ fprintf(fp, "%d video modes available\n", NMODES(vinf));
+ if(vinf->caps & VBE_ACCEL) {
+ fprintf(fp, "%d accelerated (VBE/AF) modes available\n", NACCMODES(vinf));
+ }
+ fflush(fp);
+}
+
+void vbe_print_mode_info(FILE *fp, struct vbe_mode_info *minf)
+{
+ fprintf(fp, "%dx%d %dbpp", minf->xres, minf->yres, minf->bpp);
+
+ switch(minf->mem_model) {
+ case VBE_TYPE_DIRECT:
+ fprintf(fp, " (rgb");
+ if(0) {
+ case VBE_TYPE_YUV:
+ fprintf(fp, " (yuv");
+ }
+ fprintf(fp, " %d%d%d)", minf->rsize, minf->gsize, minf->bsize);
+ break;
+ case VBE_TYPE_PLANAR:
+ fprintf(fp, " (%d planes)", minf->num_planes);
+ break;
+ case VBE_TYPE_PACKED:
+ fprintf(fp, " (packed)");
+ break;
+ case VBE_TYPE_TEXT:
+ fprintf(fp, " (%dx%d cells)", minf->xcharsz, minf->ycharsz);
+ break;
+ case VBE_TYPE_CGA:
+ fprintf(fp, " (CGA)");
+ break;
+ case VBE_TYPE_UNCHAIN:
+ fprintf(fp, " (unchained-%d)", minf->num_planes);
+ break;
+ }
+ fprintf(fp, " %dpg", minf->num_img_pages);
+
+ if(minf->attr & VBE_ATTR_LFB) {
+ fprintf(fp, " lfb@%lx", (unsigned long)minf->fb_addr);
+ }
+
+ fprintf(fp, " [");
+ if(minf->attr & VBE_ATTR_AVAIL) fprintf(fp, " avail");
+ if(minf->attr & VBE_ATTR_OPTINFO) fprintf(fp, " opt");
+ if(minf->attr & VBE_ATTR_TTY) fprintf(fp, " tty");
+ if(minf->attr & VBE_ATTR_COLOR) fprintf(fp, " color");
+ if(minf->attr & VBE_ATTR_GFX) fprintf(fp, " gfx");
+ if(minf->attr & VBE_ATTR_NOTVGA) fprintf(fp, " non-vga");
+ if(minf->attr & VBE_ATTR_BANKED) fprintf(fp, " banked");
+ if(minf->attr & VBE_ATTR_LFB) fprintf(fp, " lfb");
+ if(minf->attr & VBE_ATTR_DBLSCAN) fprintf(fp, " dblscan");
+ if(minf->attr & VBE_ATTR_ILACE) fprintf(fp, " ilace");
+ if(minf->attr & VBE_ATTR_TRIPLEBUF) fprintf(fp, " trplbuf");
+ if(minf->attr & VBE_ATTR_STEREO) fprintf(fp, " stereo");
+ if(minf->attr & VBE_ATTR_STEREO_2FB) fprintf(fp, " stdual");
+ fprintf(fp, " ]\n");
+ fflush(fp);
+}
+
+int vbe_setmode(uint16_t mode)
+{
+ struct dpmi_regs regs = {0};
+
+ regs.eax = 0x4f02;
+ regs.ebx = mode;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+
+ cur_pitch = vbe_getpitch();
+ return 0;
+}
+
+int vbe_setmode_crtc(uint16_t mode, struct vbe_crtc_info *crtc)
+{
+ void *lowbuf;
+ uint16_t seg, sel;
+ struct dpmi_regs regs = {0};
+
+ assert(sizeof *crtc == 59);
+
+ if(!(seg = dpmi_alloc((sizeof *crtc + 15) / 16, &sel))) {
+ return -1;
+ }
+ lowbuf = (void*)phys_to_virt((uint32_t)seg << 4);
+
+ memcpy(lowbuf, crtc, sizeof *crtc);
+
+ regs.eax = 0x4f02;
+ regs.ebx = mode;
+ regs.es = seg;
+ dpmi_int(0x10, ®s);
+
+ dpmi_free(sel);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+
+ cur_pitch = vbe_getpitch();
+ return 0;
+}
+
+int vbe_getmode(void)
+{
+ struct dpmi_regs regs = {0};
+
+ regs.eax = 0x4f03;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+ return regs.ebx & 0xffff;
+}
+
+int vbe_state_size(unsigned int flags)
+{
+ struct dpmi_regs regs = {0};
+
+ regs.eax = 0x4f04;
+ regs.edx = 0;
+ regs.ecx = flags;
+ dpmi_int(0x10, ®s);
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+ return (regs.ebx & 0xffff) * 64;
+}
+
+int vbe_save(void *stbuf, int sz, unsigned int flags)
+{
+ void *lowbuf;
+ uint16_t seg, sel;
+ struct dpmi_regs regs = {0};
+
+ if(!(seg = dpmi_alloc((sz + 15) / 16, &sel))) {
+ return -1;
+ }
+ lowbuf = (void*)phys_to_virt((uint32_t)seg << 4);
+
+ regs.eax = 0x4f04;
+ regs.edx = 1; /* save */
+ regs.ecx = flags;
+ regs.es = seg;
+ dpmi_int(0x10, ®s);
+ if((regs.eax & 0xffff) != 0x4f) {
+ dpmi_free(sel);
+ return -1;
+ }
+
+ memcpy(stbuf, lowbuf, sz);
+ dpmi_free(sel);
+ return 0;
+}
+
+int vbe_restore(void *stbuf, int sz, unsigned int flags)
+{
+ void *lowbuf;
+ uint16_t seg, sel;
+ struct dpmi_regs regs = {0};
+
+ if(!(seg = dpmi_alloc((sz + 15) / 16, &sel))) {
+ return -1;
+ }
+ lowbuf = (void*)phys_to_virt((uint32_t)seg << 4);
+
+ memcpy(lowbuf, stbuf, sz);
+
+ regs.eax = 0x4f04;
+ regs.edx = 2; /* restore */
+ regs.ecx = flags;
+ regs.es = seg;
+ dpmi_int(0x10, ®s);
+ dpmi_free(sel);
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+ return 0;
+}
+
+int vbe_setwin(int wid, int pos)
+{
+ struct dpmi_regs regs;
+
+ if(wid & ~1) return -1;
+
+ regs.eax = 0x4f05;
+ regs.ebx = wid;
+ regs.edx = pos;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+ return 0;
+}
+
+int vbe_getwin(int wid)
+{
+ struct dpmi_regs regs;
+
+ if(wid & ~1) return -1;
+
+ regs.eax = 0x4f05;
+ regs.ebx = wid;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+
+ return regs.edx & 0xffff;
+}
+
+int vbe_setscanlen(int len_pix)
+{
+ struct dpmi_regs regs;
+
+ regs.eax = 0x4f06;
+ regs.ebx = 0; /* set scanline length in pixels */
+ regs.ecx = len_pix;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+
+ cur_pitch = vbe_getpitch();
+ return regs.ecx;
+}
+
+int vbe_getscanlen(void)
+{
+ struct dpmi_regs regs;
+
+ regs.eax = 0x4f06;
+ regs.ebx = 1; /* get scanline length */
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+ return regs.ecx;
+}
+
+int vbe_getpitch(void)
+{
+ struct dpmi_regs regs;
+
+ regs.eax = 0x4f06;
+ regs.ebx = 1; /* get scanline length */
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+ return regs.ebx;
+}
+
+int vbe_scanline_info(struct vbe_scanline_info *sinf)
+{
+ struct dpmi_regs regs;
+
+ regs.eax = 0x4f06;
+ regs.ebx = 1; /* get scanline length */
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+
+ sinf->size = regs.ebx & 0xffff;
+ sinf->num_pixels = regs.ecx & 0xffff;
+ sinf->max_scanlines = regs.edx & 0xffff;
+ return 0;
+}
+
+enum {
+ SDISP_SET = 0x00,
+ SDISP_GET = 0x01,
+ SDISP_ALTSET = 0x02,
+ SDISP_SET_STEREO = 0x03,
+ SDISP_GETSCHED = 0x04,
+ SDISP_STEREO_ON = 0x05,
+ SDISP_STEREO_OFF = 0x06,
+ SDISP_SET_VBLANK = 0x80,
+ SDISP_ALTSET_VBLANK = 0x82,
+ SDISP_SET_STEREO_VBLANK = 0x83
+};
+
+int vbe_setdisp(int x, int y, int when)
+{
+ struct dpmi_regs regs;
+
+ regs.eax = 0x4f07;
+ regs.ebx = (when == VBE_SWAP_VBLANK) ? SDISP_SET_VBLANK : SDISP_SET;
+ regs.ecx = x;
+ regs.edx = y;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+ return 0;
+}
+
+int vbe_swap(uint32_t voffs, int when)
+{
+ struct dpmi_regs regs;
+ int op;
+
+ switch(when) {
+ case VBE_SWAP_ASYNC:
+ op = SDISP_ALTSET;
+ break;
+
+ case VBE_SWAP_NOW:
+ /* XXX is this the only way? */
+ return vbe_setdisp(voffs % cur_pitch, voffs / cur_pitch, when);
+
+ case VBE_SWAP_VBLANK:
+ default:
+ op = SDISP_ALTSET_VBLANK;
+ break;
+ }
+
+
+ regs.eax = 0x4f07;
+ regs.ebx = op;
+ regs.ecx = voffs;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return -1;
+ }
+ return 0;
+}
+
+int vbe_swap_pending(void)
+{
+ struct dpmi_regs regs;
+
+ regs.eax = 0x4f07;
+ regs.ebx = SDISP_GETSCHED;
+ dpmi_int(0x10, ®s);
+
+ if((regs.eax & 0xffff) != 0x4f) {
+ return 0;
+ }
+ return regs.ecx;
+}
--- /dev/null
+#ifndef VBE_H_
+#define VBE_H_
+
+#include <stdio.h>
+#include "inttypes.h"
+#include "util.h"
+
+#pragma pack (push, 1)
+struct vbe_info {
+ char sig[4];
+ uint16_t ver;
+ char *oem_name;
+ uint32_t caps;
+ uint16_t *modes;
+ uint16_t vmem_blk; /* video memory size in 64k blocks */
+ uint16_t oem_ver;
+ char *vendor;
+ char *product;
+ char *revstr;
+ uint16_t accel_ver;
+ uint16_t *accel_modes;
+ char reserved[216];
+ char oem_data[256];
+} PACKED;
+
+struct vbe_mode_info {
+ uint16_t attr;
+ uint8_t wina_attr, winb_attr;
+ uint16_t win_gran, win_size;
+ uint16_t wina_seg, winb_seg;
+ uint32_t win_func;
+ uint16_t scanline_bytes;
+
+ /* VBE 1.2 and above */
+ uint16_t xres, yres;
+ uint8_t xcharsz, ycharsz;
+ uint8_t num_planes;
+ uint8_t bpp;
+ uint8_t num_banks;
+ uint8_t mem_model;
+ uint8_t bank_size; /* bank size in KB */
+ uint8_t num_img_pages;
+ uint8_t reserved1;
+
+ /* direct color fields */
+ uint8_t rsize, rpos;
+ uint8_t gsize, gpos;
+ uint8_t bsize, bpos;
+ uint8_t xsize, xpos;
+ uint8_t cmode_info; /* direct color mode attributes */
+
+ /* VBE 2.0 and above */
+ uint32_t fb_addr; /* physical address of the linear framebuffer */
+ uint32_t os_addr; /* phys. address of off-screen memory */
+ uint16_t os_size; /* size in KB of off-screen memory */
+
+ /* VBE 3.0 and above */
+ uint16_t lfb_scanline_bytes;
+ uint8_t banked_num_img_pages;
+ uint8_t lfb_num_img_pages;
+ uint8_t lfb_rsize, lfb_rpos;
+ uint8_t lfb_gsize, lfb_gpos;
+ uint8_t lfb_bsize, lfb_bpos;
+ uint8_t lfb_xsize, lfb_xpos;
+ uint32_t max_pixel_clock;
+
+ char reserved2[190];
+} PACKED;
+
+struct vbe_crtc_info {
+ uint16_t htotal, hsync_start, hsync_end;
+ uint16_t vtotal, vsync_start, vsync_end;
+ uint8_t flags;
+ uint32_t pixel_clock;
+ uint16_t rate_centihz; /* refresh rate in 1/100 hz (pck / (htotal * vtotal)) */
+ char reserved[40];
+} PACKED;
+#pragma pack (pop)
+
+/* returned by vbe_scanline_info() */
+struct vbe_scanline_info {
+ int size;
+ int num_pixels;
+ int max_scanlines;
+};
+
+enum {
+ VBE_8BIT_DAC = 0x01,
+ VBE_NON_VGA = 0x02,
+ VBE_DAC_BLANK = 0x04,
+ VBE_STEREO = 0x08, /* ? */
+ VBE_ACCEL = 0x08,
+ VBE_STEREO_VESA = 0x10, /* ? */
+ VBE_MUSTLOCK = 0x10,
+ VBE_HWCURSOR = 0x20,
+ VBE_HWCLIP = 0x40,
+ VBE_TRANSP_BLT = 0x80
+};
+
+#define VBE_VER_MAJOR(v) (((v) >> 8) & 0xff)
+#define VBE_VER_MINOR(v) ((v) & 0xff)
+
+/* VBE mode attribute flags (vbe_mode_info.attr) */
+enum {
+ VBE_ATTR_AVAIL = 0x0001,
+ VBE_ATTR_OPTINFO = 0x0002,
+ VBE_ATTR_TTY = 0x0004,
+ VBE_ATTR_COLOR = 0x0008,
+ VBE_ATTR_GFX = 0x0010,
+ /* VBE 2.0 */
+ VBE_ATTR_NOTVGA = 0x0020,
+ VBE_ATTR_BANKED = 0x0040,
+ VBE_ATTR_LFB = 0x0080,
+ VBE_ATTR_DBLSCAN = 0x0100,
+ /* VBE 3.0 */
+ VBE_ATTR_ILACE = 0x0200, /* ! */
+ VBE_ATTR_TRIPLEBUF = 0x0400,
+ VBE_ATTR_STEREO = 0x0800,
+ VBE_ATTR_STEREO_2FB = 0x1000,
+ /* VBE/AF */
+ VBE_ATTR_MUSTLOCK = 0x0200 /* ! */
+};
+
+/* VBE memory model type (vbe_mode_info.mem_model) */
+enum {
+ VBE_TYPE_TEXT,
+ VBE_TYPE_CGA,
+ VBE_TYPE_HERCULES,
+ VBE_TYPE_PLANAR,
+ VBE_TYPE_PACKED,
+ VBE_TYPE_UNCHAIN,
+ VBE_TYPE_DIRECT,
+ VBE_TYPE_YUV
+};
+
+/* VBE window attribute (vbe_mode_info.win(a|b)_attr) */
+enum {
+ VBE_WIN_AVAIL = 0x01,
+ VBE_WIN_RD = 0x02,
+ VBE_WIN_WR = 0x04
+};
+
+/* mode number flags */
+enum {
+ VBE_MODE_RATE = 0x0800, /* VBE 3.0+ user-specified refresh rate */
+ VBE_MODE_ACCEL = 0x2000, /* VBE/AF */
+ VBE_MODE_LFB = 0x4000, /* VBE 2.0+ */
+ VBE_MODE_PRESERVE = 0x8000
+};
+
+/* standard mode numbers */
+enum {
+ VBE_640X400_8BPP = 0x100,
+ VBE_640X480_8BPP = 0x101,
+ VBE_800X600_4BPP = 0x102,
+ VBE_800X600_8BPP = 0x103,
+ VBE_1024X768_4BPP = 0x104,
+ VBE_1024X768_8BPP = 0x105,
+ VBE_1280X1024_4BPP = 0x106,
+ VBE_1280X1024_8BPP = 0x107,
+ VBE_80X60_TEXT = 0x108,
+ VBE_132X25_TEXT = 0x109,
+ VBE_132X43_TEXT = 0x10a,
+ VBE_132X50_TEXT = 0x10b,
+ VBE_132X60_TEXT = 0x10c,
+ /* VBE 1.2 */
+ VBE_320X200_15BPP = 0x10d,
+ VBE_320X200_16BPP = 0x10e,
+ VBE_320X200_24BPP = 0x10f,
+ VBE_640X480_15BPP = 0x110,
+ VBE_640X480_16BPP = 0x111,
+ VBE_640X480_24BPP = 0x112,
+ VBE_800X600_15BPP = 0x113,
+ VBE_800X600_16BPP = 0x114,
+ VBE_800X600_24BPP = 0x115,
+ VBE_1024X768_15BPP = 0x116,
+ VBE_1024X768_16BPP = 0x117,
+ VBE_1024X768_24BPP = 0x118,
+ VBE_1280X1024_15BPP = 0x119,
+ VBE_1280X1024_16BPP = 0x11a,
+ VBE_1280X1024_24BPP = 0x11b,
+ /* VBE 2.0 */
+ VBE_1600X1200_8BPP = 0x120,
+ VBE_1600X1200_15BPP = 0x121,
+ VBE_1600X1200_16BPP = 0x122,
+
+ VBE_VMEM_MODE = 0x81ff
+};
+
+/* VBE CRTC flags (vbe_crtc_info.flags) */
+enum {
+ VBE_CRTC_DBLSCAN = 0x01,
+ VBE_CRTC_ILACE = 0x02,
+ VBE_CRTC_HSYNC_NEG = 0x04,
+ VBE_CRTC_VSYNC_NEG = 0x08
+};
+
+enum {
+ VBE_STATE_CTRLHW = 0x01,
+ VBE_STATE_BIOS = 0x02,
+ VBE_STATE_DAC = 0x04,
+ VBE_STATE_REGS = 0x08,
+
+ VBE_STATE_ALL = 0xffff
+};
+
+enum {
+ VBE_SWAP_NOW,
+ VBE_SWAP_VBLANK,
+ VBE_SWAP_ASYNC /* schedule swap and return (triple-buffering) */
+};
+
+int vbe_info(struct vbe_info *info);
+int vbe_num_modes(struct vbe_info *info);
+int vbe_mode_info(int mode, struct vbe_mode_info *minf);
+
+void vbe_print_info(FILE *fp, struct vbe_info *info);
+void vbe_print_mode_info(FILE *fp, struct vbe_mode_info *minf);
+
+int vbe_setmode(uint16_t mode);
+int vbe_setmode_crtc(uint16_t mode, struct vbe_crtc_info *crtc);
+int vbe_getmode(void);
+
+int vbe_state_size(unsigned int flags);
+int vbe_save(void *stbuf, int sz, unsigned int flags);
+int vbe_restore(void *stbuf, int sz, unsigned int flags);
+
+int vbe_setwin(int wid, int pos);
+int vbe_getwin(int wid);
+
+/* returns the actual length in pixels, which might not be what was requested */
+int vbe_setscanlen(int len_pix);
+int vbe_getscanlen(void);
+int vbe_getpitch(void);
+int vbe_scanline_info(struct vbe_scanline_info *sinf);
+
+int vbe_setdisp(int x, int y, int when);
+int vbe_swap(uint32_t voffs, int when);
+int vbe_swap_pending(void); /* 0: not pending (done) or error, 1: pending swap */
+/* TODO add stereo swap */
+
+#endif /* VBE_H_ */
--- /dev/null
+#include "vga.h"
+#include "cdpmi.h"
+
+int vga_setmode(int mode)
+{
+ struct dpmi_regs regs = {0};
+
+ regs.eax = mode; /* func 00 | mode */
+ dpmi_int(0x10, ®s);
+ return 0;
+}
--- /dev/null
+#ifndef VGA_H_
+#define VGA_H_
+
+#include "inttypes.h"
+
+int vga_setmode(int mode);
+
+#ifdef __WATCOMC__
+void vga_setpal(int16_t idx, uint8_t r, uint8_t g, uint8_t b);
+#pragma aux vga_setpal = \
+ "test ax, 0x8000" \
+ "jnz skip_dacaddr" \
+ "mov dx, 0x3c8" \
+ "out dx, al" \
+ "skip_dacaddr:" \
+ "mov dx, 0x3c9" \
+ "mov al, bl" \
+ "shr al, 2" \
+ "out dx, al" \
+ "mov al, bh" \
+ "shr al, 2" \
+ "out dx, al" \
+ "mov al, cl" \
+ "shr al, 2" \
+ "out dx, al" \
+ parm[ax][bl][bh][cl] \
+ modify[dx];
+#endif /* __WATCOMC__ */
+
+#endif /* VGA_H_ */
--- /dev/null
+#ifdef __WATCOMC__
+#include "cdpmi.h"
+
+void dpmi_real_int(int inum, struct dpmi_real_regs *regs)
+{
+ unsigned char int_num = (unsigned char)inum;
+ __asm {
+ mov eax, 0x300
+ mov edi, regs
+ mov bl, int_num
+ mov bh, 0
+ xor ecx, ecx
+ int 0x31
+ }
+}
+
+void *dpmi_mmap(uint32_t phys_addr, unsigned int size)
+{
+ uint16_t mem_high, mem_low;
+ uint16_t phys_high = phys_addr >> 16;
+ uint16_t phys_low = phys_addr & 0xffff;
+ uint16_t size_high = size >> 16;
+ uint16_t size_low = size & 0xffff;
+ unsigned int err, res = 0;
+
+ __asm {
+ mov eax, 0x800
+ mov bx, phys_high
+ mov cx, phys_low
+ mov si, size_high
+ mov di, size_low
+ int 0x31
+ add res, 1
+ mov err, eax
+ mov mem_high, bx
+ mov mem_low, cx
+ }
+
+ if(res == 2) {
+ return 0;
+ }
+ return (void*)(((uint32_t)mem_high << 16) | ((uint32_t)mem_low));
+}
+
+void dpmi_munmap(void *addr)
+{
+ uint16_t mem_high = (uint32_t)addr >> 16;
+ uint16_t mem_low = (uint16_t)addr;
+
+ __asm {
+ mov eax, 0x801
+ mov bx, mem_high
+ mov cx, mem_low
+ int 0x31
+ }
+}
+#else
+int stop_gcc_crying_about_empty_translation_units = 0;
+#endif /* __WATCOM__ */
--- /dev/null
+#include "game.h"
+
+int fb_width, fb_height;
+long fb_size;
+void *fb_pixels, *vmem;
+
+long time_msec;
+
+int game_init(int argc, char **argv)
+{
+ return 0;
+}
+
+void game_cleanup(void)
+{
+}
+
+void game_draw(void)
+{
+}
+
+void game_keyboard(int key, int press)
+{
+}
--- /dev/null
+#ifndef GAME_H_
+#define GAME_H_
+
+#define FB_BPP 8
+
+extern int fb_width;
+extern int fb_height;
+extern long fb_size;
+extern void *fb_pixels, *vmem;
+
+extern long time_msec;
+
+/* special keys */
+enum {
+ KB_ESC = 27,
+ KB_BACKSP = 127,
+
+ KB_NUM_0, KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_4,
+ KB_NUM_5, KB_NUM_6, KB_NUM_7, KB_NUM_8, KB_NUM_9,
+ KB_NUM_DOT, KB_NUM_DIV, KB_NUM_MUL, KB_NUM_MINUS, KB_NUM_PLUS, KB_NUM_ENTER, KB_NUM_EQUALS,
+ KB_UP, KB_DOWN, KB_RIGHT, KB_LEFT,
+ KB_INSERT, KB_HOME, KB_END, KB_PGUP, KB_PGDN,
+ KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6,
+ KB_F7, KB_F8, KB_F9, KB_F10, KB_F11, KB_F12,
+ KB_F13, KB_F14, KB_F15,
+ KB_NUMLK, KB_CAPSLK, KB_SCRLK,
+ KB_RSHIFT, KB_LSHIFT, KB_RCTRL, KB_LCTRL, KB_RALT, KB_LALT,
+ KB_RMETA, KB_LMETA, KB_LSUPER, KB_RSUPER, KB_MODE, KB_COMPOSE,
+ KB_HELP, KB_PRINT, KB_SYSRQ, KB_BREAK
+};
+
+int game_init(int argc, char **argv);
+void game_cleanup(void);
+
+void game_draw(void);
+
+void game_keyboard(int key, int press);
+
+
+#endif /* GAME_H_ */
--- /dev/null
+#ifndef TIMER_H_
+#define TIMER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* expects the required timer resolution in hertz
+ * if res_hz is 0, the current resolution is retained
+ */
+void init_timer(int res_hz);
+
+void reset_timer(void);
+unsigned long get_msec(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TIMER_H_ */
--- /dev/null
+#include "util.h"
+
+uint32_t perf_start_count, perf_interval_count;
--- /dev/null
+#ifndef UTIL_H_
+#define UTIL_H_
+
+#include "inttypes.h"
+
+#ifdef __GNUC__
+#define INLINE __inline
+#define PACKED __attribute__((packed))
+
+#elif defined(__WATCOMC__)
+#define INLINE __inline
+#define PACKED
+
+#else
+#define INLINE
+#define PACKED
+#endif
+
+/* fast conversion of double -> 32bit int
+ * for details see:
+ * - http://chrishecker.com/images/f/fb/Gdmfp.pdf
+ * - http://stereopsis.com/FPU.html#convert
+ */
+static INLINE int32_t cround64(double val)
+{
+ val += 6755399441055744.0;
+ return *(int32_t*)&val;
+}
+
+extern uint32_t perf_start_count, perf_interval_count;
+
+#ifdef __WATCOMC__
+void perf_start(void);
+#pragma aux perf_start = \
+ "xor eax, eax" \
+ "cpuid" \
+ "rdtsc" \
+ "mov [perf_start_count], eax" \
+ modify[eax ebx ecx edx];
+
+void perf_end(void);
+#pragma aux perf_end = \
+ "xor eax, eax" \
+ "cpuid" \
+ "rdtsc" \
+ "sub eax, [perf_start_count]" \
+ "mov [perf_interval_count], eax" \
+ modify [eax ebx ecx edx];
+
+void debug_break(void);
+#pragma aux debug_break = "int 3";
+#endif
+
+#ifdef __GNUC__
+#define perf_start() asm volatile ( \
+ "xor %%eax, %%eax\n" \
+ "cpuid\n" \
+ "rdtsc\n" \
+ "mov %%eax, %0\n" \
+ : "=m"(perf_start_count) \
+ :: "%eax", "%ebx", "%ecx", "%edx")
+
+#define perf_end() asm volatile ( \
+ "xor %%eax, %%eax\n" \
+ "cpuid\n" \
+ "rdtsc\n" \
+ "sub %1, %%eax\n" \
+ "mov %%eax, %0\n" \
+ : "=m"(perf_interval_count) \
+ : "m"(perf_start_count) \
+ : "%eax", "%ebx", "%ecx", "%edx")
+
+#define debug_break() \
+ asm volatile ("int $3")
+#endif
+
+#ifdef _MSC_VER
+#define perf_start() \
+ do { \
+ __asm { \
+ xor eax, eax \
+ cpuid \
+ rdtsc \
+ mov [perf_start_count], eax \
+ } \
+ } while(0)
+
+#define perf_end() \
+ do { \
+ __asm { \
+ xor eax, eax \
+ cpuid \
+ rdtsc \
+ sub eax, [perf_start_count] \
+ mov [perf_interval_count], eax \
+ } \
+ } while(0)
+
+#define debug_break() \
+ do { \
+ __asm { int 3 } \
+ } while(0)
+#endif
+
+#endif /* UTIL_H_ */