diff --git a/modules/Makefile b/modules/Makefile
index 77020ea..e6e7905 100644
--- a/modules/Makefile
+++ b/modules/Makefile
@@ -19,6 +19,11 @@ include $(topdir)/MCONFIG.embedded
 
 INCLUDES   = -I$(com32)/include
 
+CFLAGS_COM   = -O2 -Wall -Wno-pointer-sign -fomit-frame-pointer -m32 -march=i386 \
+               -fno-align-functions -fno-align-labels -fno-align-jumps -fno-align-loops \
+               -fno-builtin -nostdinc -I .
+ASMFLAGS_COM = -O99 -felf
+
 BINS = pxechain.com gfxboot.com poweroff.com int18.com
 
 all: $(BINS)
@@ -49,6 +54,15 @@ $(LIB): $(LIBOBJS)
 %.ppm.gz: %.png
 	$(PNGTOPNM) $< | gzip -9 > $@
 
+libio.o: libio.asm
+	nasm $(ASMFLAGS_COM) -o $@ -l $*.lst $<
+
+gfxboot.o: gfxboot.c libio.h
+	$(CC) -g $(CFLAGS_COM) -c -o $@ $<
+
+gfxboot.com: gfxboot.o libio.o
+	ld -M -Tcom.ld -o $@ $^ >$*.map
+
 tidy dist:
 	rm -f *.o *.a *.lst *.elf *.map .*.d
 
diff --git a/modules/com.ld b/modules/com.ld
new file mode 100644
index 0000000..a98f9aa
--- /dev/null
+++ b/modules/com.ld
@@ -0,0 +1,16 @@
+/*
+  linker script for DOS program (.COM)
+ */
+
+OUTPUT_FORMAT("binary")
+OUTPUT_ARCH(i386)
+SEARCH_DIR(".")
+ENTRY(_start)
+SECTIONS
+{
+  .init 0x100 : { *(.init) }
+  .text       : { *(.text) }
+  .rodata     : { *(.rodata*) }
+  .data       : { *(.data) }
+  .bss        : { __bss_start = .; *(.bss) }
+}
diff --git a/modules/gfxboot.c b/modules/gfxboot.c
new file mode 100644
index 0000000..a59da40
--- /dev/null
+++ b/modules/gfxboot.c
@@ -0,0 +1,1040 @@
+/*
+ *
+ * gfxboot.c
+ *
+ * A comboot program to load gfxboot graphics.
+ *
+ * It is based on work done by Sebastian Herbszt in gfxboot.asm.
+ *
+ * Copyright (c) 2009 Steffen Winterfeldt.
+ *
+ * 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, Inc., 53 Temple Place Ste 330, Boston MA
+ * 02111-1307, USA; either version 2 of the License, or (at your option) any
+ * later version; incorporated herein by reference.
+ *
+ */
+
+#include <libio.h>
+
+#define ID_SYSLINUX		0x31
+#define ID_PXELINUX		0x32
+#define ID_ISOLINUX		0x33
+#define ID_EXTLINUX		0x34
+
+#define MAX_CONFIG_LINE_LEN	2048
+#define MAX_CMDLINE_LEN		1024
+
+// basic memory layout in MB
+#define GFX_MEMORY_START	1
+#define	GFX_MEMORY_SIZE		7
+// assume initrd needs at least that much
+#define INITRD_MIN_MEMORY	64
+
+#define GFX_BC_INIT		0
+#define GFX_BC_DONE		1
+#define GFX_BC_INPUT		2
+#define GFX_BC_MENU_INIT	3
+#define GFX_BC_INFOBOX_INIT	4
+#define GFX_BC_INFOBOX_DONE	5
+#define GFX_BC_PROGRESS_INIT	6
+#define GFX_BC_PROGRESS_DONE	7
+#define GFX_BC_PROGRESS_UPDATE	8
+#define GFX_BC_PROGRESS_LIMIT	9		// unused
+#define GFX_BC_PASSWORD_INIT	10
+#define GFX_BC_PASSWORD_DONE	11
+
+// for now, static values
+#define MENU_LABEL_SIZE		128
+#define MENU_ARG_SIZE		512
+#define MENU_ENTRY_SIZE		(MENU_LABEL_SIZE + MENU_ARG_SIZE)
+// entry 0 is reserved for the default entry
+#define MAX_MENU_ENTRIES	(0x10000 / MENU_ENTRY_SIZE)
+
+
+typedef struct {
+  uint16_t handle;
+  uint16_t block_size;
+  int file_size;		// file size (-1: unknown)
+  uint32_t buf;			// must be block_size
+  unsigned buf_size;		// in block_size units
+  unsigned data_len;		// valid bytes in buf
+  unsigned buf_idx;		// read pos in buffer
+} fd_t;
+
+
+// gfx config data (52 bytes)
+typedef struct __attribute__ ((packed)) {
+  uint8_t bootloader;		// 0: boot loader type (0: lilo, 1: syslinux, 2: grub)
+  uint8_t sector_shift;		// 1: sector shift
+  uint8_t media_type;		// 2: media type (0: disk, 1: floppy, 2: cdrom)
+  uint8_t failsafe;		// 3: turn on failsafe mode (bitmask)
+				//    0: SHIFT pressed
+				//    1: skip gfxboot
+				//    2: skip monitor detection
+  uint8_t sysconfig_size;	// 4: size of sysconfig data
+  uint8_t boot_drive;		// 5: BIOS boot drive
+  uint16_t callback;		// 6: offset to callback handler
+  uint16_t bootloader_seg;	// 8: code/data segment used by bootloader; must follow gfx_callback
+  uint16_t reserved_1;		// 10
+  uint32_t user_info_0;		// 12: data for info box
+  uint32_t user_info_1;		// 16: data for info box
+  uint32_t bios_mem_size;	// 20: BIOS memory size (in bytes)
+  uint16_t xmem_0;		// 24: extended mem area 0 (start:size in MB; 12:4 bits)
+  uint16_t xmem_1;		// 26: extended mem area 1
+  uint16_t xmem_2;		// 28: extended mem area 2
+  uint16_t xmem_3;		// 30: extended mem area 3
+  uint32_t file;		// 32: start of gfx file
+  uint32_t archive_start;	// 36: start of cpio archive
+  uint32_t archive_end;		// 40: end of cpio archive
+  uint32_t mem0_start;		// 44: low free memory start
+  uint32_t mem0_end;		// 48: low free memory end
+} gfx_config_t;
+
+
+// menu description (18 bytes)
+typedef struct __attribute__ ((packed)) {
+  uint16_t entries;
+  uint32_t default_entry;	// seg:ofs
+  uint32_t label_list;		// seg:ofs
+  uint16_t label_size;
+  uint32_t arg_list;		// seg:ofs
+  uint16_t arg_size;
+} menu_t;
+
+
+// e820 mem descriptor
+typedef struct __attribute__ ((packed)) {
+  uint64_t base;
+  uint64_t len;
+  uint32_t type;
+  uint32_t cont;
+} mem_info_t;
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// global file descriptor, implicitly used by read(), getc(), fgets()
+fd_t fd;
+
+gfx_config_t gfx_config;
+menu_t menu;
+
+struct {
+  uint32_t jmp_table[12];
+  uint16_t code_seg;
+  char fname_buf[64];
+} gfx;
+
+unsigned comboot, comboot_len;
+unsigned io_buf, io_buf_len;
+unsigned menu_buf, menu_buf_len;
+unsigned freemem, freemem_len;
+
+unsigned initrd_end;
+unsigned kernel_start;
+
+int timeout;
+
+char cmdline[MAX_CMDLINE_LEN];
+char current_label[64];
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+int open(char *name);
+int open32(uint32_t name);
+int read(void *buf, int size);
+int read32(uint32_t buf, int size);
+int getc(void);
+char *fgets(char *s, int size);
+
+int strlen(char *s);
+int strcmp(char *s1, char *s2);
+char *skip_spaces(char *s);
+char *skip_nonspaces(char *s);
+void chop_line(char *s);
+
+int atoi(char *s);
+
+uint32_t get_config_file_name32(void);
+int read_config_file(void);
+
+unsigned magic_ok(uint32_t buf);
+unsigned find_file(uint32_t buf, unsigned len, unsigned *gfx_file_start, unsigned *file_len);
+
+int get_mem_info(mem_info_t *mi);
+int gfx_init(char *file);
+void gfx_done(void);
+int gfx_input(void);
+int gfx_menu_init(void);
+
+void gfx_infobox32(int type, uint32_t str1, uint32_t str2);
+void gfx_infobox(int type, char *str1, char *str2);
+
+void boot(void);
+void show_message(char *file);
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int main(int argc, char **argv)
+{
+  x86regs_t r;
+  uint8_t syslinux_id;
+  int menu_index;
+
+  r.eax = 0x0a;		// Get Derivative-Specific Information
+  r.ecx = 9;
+  x86int(0x22, &r);
+  syslinux_id = (uint8_t) r.eax;
+  gfx_config.sector_shift = (uint8_t) r.ecx;
+  gfx_config.boot_drive = (uint8_t) r.edx;
+
+  // define our memory layout
+  // all must be at least 16 bytes aligned
+
+  // 64k comboot code
+  comboot = comboot_seg() << 4;
+  comboot_len = 0x10000;
+
+  // 16k file io buffer
+  io_buf = comboot + comboot_len;
+  io_buf_len = 0x4000;
+
+  // 64k for parsed menu data
+  menu_buf = io_buf + io_buf_len;
+  menu_buf_len = 0x10000;
+
+  // use remaining mem for gfx core
+  freemem = menu_buf + menu_buf_len;
+  // comboot api passes low memory end at address 2
+  freemem_len = ((*(uint16_t *) 2) << 4) - freemem;
+
+  gfx_config.bootloader = 1;
+  gfx_config.sysconfig_size = sizeof gfx_config;
+  gfx_config.bootloader_seg = comboot >> 4;
+
+  // not gfx_cb() directly, we need a wrapper
+  gfx_config.callback = (uint32_t) _gfx_cb;
+
+  if(syslinux_id == ID_PXELINUX) {
+    gfx_config.sector_shift = 11;
+    gfx_config.boot_drive = 0;
+  }
+
+  if(argc < 2) {
+    printf("Usage: gfxboot.com bootlogo_file [message_file]\n");
+    if(argc > 2) show_message(argv[2]);
+
+    return 0;
+  }
+
+  if(read_config_file()) {
+    printf("Error reading config file\n");
+    if(argc > 2) show_message(argv[2]);
+
+    return 0;
+  }
+
+  if(gfx_init(argv[1])) {
+    printf("Error setting up gfxboot\n");
+    if(argc > 2) show_message(argv[2]);
+
+    return 0;
+  }
+
+  gfx_menu_init();
+
+  for(;;) {
+    menu_index = gfx_input();
+
+    // abort gfx, return to text mode prompt
+    if(menu_index == -1) {
+      gfx_done();
+      break;
+    }
+
+    // get label name, is needed later for messages etc.
+    memcpy(current_label, cmdline, sizeof current_label);
+    current_label[sizeof current_label - 1] = 0;
+    *skip_nonspaces(current_label) = 0;
+
+    // does not return if it succeeds
+    boot();
+  }
+
+  if(argc > 2) show_message(argv[2]);
+
+  return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+//   0: ok, -1: error
+//
+int open32(uint32_t name)
+{
+  x86regs_t r;
+
+  fd.handle = 0;
+  fd.data_len = fd.buf_idx = 0;
+
+  r.esi = name & 0xf;
+  r.eax = 0x06;		// Open file
+  r.es = name >> 4;
+  x86int(0x22, &r);
+
+  fd.block_size = r.ecx;
+  fd.file_size = r.eax;
+
+  if(
+    (r.eflags & X86_CF) ||
+    !fd.file_size || !fd.block_size || fd.block_size > io_buf_len
+  ) return -1;
+
+  fd.handle = r.esi;
+
+  fd.buf = io_buf;
+  fd.buf_size = io_buf_len / fd.block_size;
+
+  // printf("block size = 0x%x, file size = %d\n", fd.block_size, fd.file_size);
+
+  return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+//   0: ok, -1: error
+//
+int open(char *name)
+{
+  return open32((uint32_t) name + comboot);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Note: buf is 32bit address
+//
+int read32(uint32_t buf, int size)
+{
+  x86regs_t r;
+  int i, len = 0;
+
+  while(size) {
+    i = fd.data_len - fd.buf_idx;
+
+    if(i > 0) {
+      i = i < size ? i : size;
+      memcpy32(buf, fd.buf + fd.buf_idx, i);
+      len += i;
+      buf += i;
+      size -= i;
+      fd.buf_idx += i;
+    }
+
+    if(!size || !fd.handle) break;
+
+    r.eax = 0x07;		// Read file
+    r.ecx = fd.buf_size;
+    r.ebx = 0;
+    r.es = fd.buf >> 4;
+    r.esi = fd.handle;
+    x86int(0x22, &r);
+    fd.handle = r.esi;
+    fd.data_len = r.ecx;
+    fd.buf_idx = 0;
+  }
+
+  return len;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int read(void *buf, int size)
+{
+  return read32((uint32_t) buf + comboot, size);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int getc()
+{
+  char buf[1];
+
+  return read(buf, 1) ? *buf : EOF;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *fgets(char *s, int size)
+{
+  char *buf = s;
+  int c = EOF;
+
+  while(size--) {
+    c = getc();
+    if(c == EOF) break;
+    *buf++ = c;
+    if(c == 0x0a) break;
+  }
+
+  *buf = 0;
+
+  return c == EOF && s == buf ? 0 : s;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int strlen(char *s)
+{
+  char *s0 = s + 1;
+
+  while(*s++);
+
+  return s - s0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int strcmp(char *s1, char *s2)
+{
+  while(*s1 && *s1 == *s2) s1++, s2++;
+
+  return (uint8_t) *s1 - (uint8_t) *s2;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *skip_spaces(char *s)
+{
+  while(*s && (*s == ' ' || *s == '\t')) s++;
+
+  return s;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+char *skip_nonspaces(char *s)
+{
+  while(*s && *s != ' ' && *s != '\t') s++;
+
+  return s;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void chop_line(char *s)
+{
+  int i = strlen(s);
+
+  if(!i) return;
+
+  while(--i >= 0) {
+    if(s[i] == ' ' || s[i] == '\t' || s[i] == '\n') {
+      s[i] = 0;
+    }
+    else {
+      break;
+    }
+  }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int atoi(char *s)
+{
+  int i = 0, sign = 1;
+
+  if(*s == '-') s++, sign = -1;
+  if(*s == '+') s++;
+
+  while(*s >= '0' && *s <= '9') {
+    i = 10 * i + *s++ - '0';
+  }
+
+  return sign * i;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+//   config file name (32 bit address)
+//
+uint32_t get_config_file_name32()
+{
+  x86regs_t r;
+
+  r.eax = 0x0e;		// Get configuration file name
+  x86int(0x22, &r);
+
+  return (r.es << 4) + (uint16_t) r.ebx;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Read and parse syslinux config file.
+//
+// return:
+//   0: ok, 1: error
+//
+int read_config_file(void)
+{
+  char *s, *t, buf[MAX_CONFIG_LINE_LEN];
+  unsigned u;
+  int menu_idx = 0;
+
+  // clear memory before we start
+  memset32(menu_buf, 0, menu_buf_len);
+
+  if(open32(get_config_file_name32()) == -1) return 1;
+
+  while((s = fgets(buf, sizeof buf))) {
+    chop_line(s);
+    s = skip_spaces(s);
+    if(!*s || *s == '#') continue;
+    t = skip_nonspaces(s);
+    if(*t) *t++ = 0;
+    t = skip_spaces(t);
+
+    if(!strcmp(s, "timeout")) {
+      timeout = atoi(t);
+    }
+
+    if(!strcmp(s, "default")) {
+      u = strlen(t);
+      if(u > MENU_LABEL_SIZE - 1) u = MENU_LABEL_SIZE - 1;
+      memcpy32(menu_buf, comboot + (uint32_t) t, u);
+    }
+
+    if(!strcmp(s, "label")) {
+      menu_idx++;
+      if(menu_idx < MAX_MENU_ENTRIES) {
+        u = strlen(t);
+        if(u > MENU_LABEL_SIZE - 1) u = MENU_LABEL_SIZE - 1;
+        memcpy32(
+          menu_buf + menu_idx * MENU_LABEL_SIZE,
+          comboot + (uint32_t) t,
+          u
+        );
+      }
+    }
+
+    if(!strcmp(s, "append")) {
+      if(menu_idx < MAX_MENU_ENTRIES) {
+        u = strlen(t);
+        if(u > MENU_ARG_SIZE - 1) u = MENU_ARG_SIZE - 1;
+        memcpy32(
+          menu_buf + menu_idx * MENU_ARG_SIZE + MAX_MENU_ENTRIES * MENU_LABEL_SIZE,
+          comboot + (uint32_t) t,
+          u
+        );
+      }
+    }
+  }
+
+  menu.entries = menu_idx;
+  menu.label_size = MENU_LABEL_SIZE;
+  menu.arg_size = MENU_ARG_SIZE;
+  menu.default_entry = ((menu_buf >> 4) << 16) + (menu_buf & 0xf);
+  u = menu_buf + MENU_LABEL_SIZE;
+  menu.label_list = ((u >> 4) << 16) + (u & 0xf);
+  u = menu_buf + MAX_MENU_ENTRIES * MENU_LABEL_SIZE + MENU_ARG_SIZE;
+  menu.arg_list = ((u >> 4) << 16) + (u & 0xf);
+
+  return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Check header and return code start offset.
+//
+// Note: buf is 32bit address
+//
+unsigned magic_ok(uint32_t buf)
+{
+  if(
+    _mem32(buf) == 0x0b2d97f00 &&		/* magic id */
+    (_mem8(buf + 4) == 8)			/* version 8 */
+  ) {
+    return _mem32(buf + 8);
+  }
+
+  return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Search cpio archive for gfx file.
+//
+// Note: buf is 32bit address
+//
+unsigned find_file(uint32_t buf, unsigned len, unsigned *gfx_file_start, unsigned *file_len)
+{
+  unsigned i, fname_len, code_start = 0;
+
+  *gfx_file_start = 0;
+
+  for(i = 0; i < len;) {
+    if((len - i) >= 0x1a && _mem16(buf + i) == 0x71c7) {
+      fname_len = _mem16(buf + i + 20);
+      *file_len = _mem16(buf + i + 24) + (_mem16(buf + i + 22) << 16);
+      i += 26 + fname_len;
+      i = ((i + 1) & ~1);
+      if((code_start = magic_ok(buf + i))) {
+        *gfx_file_start = i;
+        return code_start;
+      }
+      i += *file_len;
+      i = ((i + 1) & ~1);
+    }
+    else {
+      break;
+    }
+  }
+
+  return code_start;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_cb(x86regs_t *r)
+{
+  uint8_t f_nr = r->eax;
+
+  switch(f_nr) {
+    case 0:	// cb_status
+      // edx = filename buffer (64 bytes)
+      r->edx = comboot + (uint32_t) gfx.fname_buf;
+      r->eax = 0;
+      // printf("<0x%x, %p + 0x%x>", r->edx, gfx.fname_buf, comboot);
+      break;
+
+    case 1:	// cb_fopen
+      // file name in gfx.fname_buf
+      // al = 0: ok, 1: file not found
+      // ecx = file length (al = 0)
+      if(open(gfx.fname_buf) == -1) {
+        r->eax = 1;
+      }
+      else {
+        r->eax = 0;
+        r->ecx = fd.file_size;
+      }
+      break;
+
+    case 2:	// cb_fread
+      // edx = buffer address (linear)
+      // ecx = data length (< 64k)
+      if(!fd.handle) {
+        r->eax = r->ecx = 0;
+        break;
+      }
+      r->esi = fd.handle;
+      r->ebx = 0;
+      r->es = io_buf >> 4;
+      r->ecx = io_buf_len / fd.block_size;
+      r->eax = 7;
+      x86int(0x22, r);
+      fd.handle = r->esi;
+      if((r->eflags & X86_CF)) {
+        r->eax = 1;
+      }
+      else {
+        r->edx = io_buf;
+        r->eax = 0;
+      }
+      break;
+
+    case 3:	// cb_getcwd
+      // edx filename
+      r->eax = 0x1f;		// Get current working directory
+      x86int(0x22, r);
+      r->edx = (r->es << 4) + (uint16_t) r->ebx;
+      r->eax = 0;
+      break;
+
+    case 4:	// cb_chdir
+      r->es = comboot >> 4;
+      r->ebx = (uint32_t) gfx.fname_buf;
+      r->eax = 0x25;		// Get current working directory
+      x86int(0x22, r);
+      r->eax = (r->eflags & X86_CF) ? 1 : 0;
+      break;
+
+    case 5:	// cb_readsector
+      // in: edx = sector
+      // out: edx = buffer (linear address)
+      r->esi = r->edi = 0;
+      r->ebx = 0;
+      r->es = io_buf >> 4;
+      r->ecx = 1;
+      r->eax = 0x19;		// Read disk
+      x86int(0x22, r);
+      r->eax = 0;
+      r->edx = io_buf;
+      break;
+
+    case 6:	// cb_mount
+      r->eax = 0x26;
+      x86int(0x22, r);
+      r->eax = (r->eflags & X86_CF) ? 1 : 0;
+      break;
+
+    default:
+      r->eax = 0xff;
+  }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+//   0: ok, 1: error
+//
+int get_mem_info(mem_info_t *mi)
+{
+  x86regs_t r;
+
+  mi->base = mi->len = mi->type = 0;
+
+  r.eax = 0xe820;
+  r.edx = 0x534d4150;
+  r.ebx = mi->cont;
+  r.ecx = 20;
+  r.es = comboot >> 4;
+  r.edi = (unsigned) mi;
+  x86int(0x15, &r);
+
+  mi->cont = 0;
+  if(r.eax != 0x534d4150 || (r.eflags & X86_CF)) return 1;
+  mi->cont = r.ebx;
+
+  return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+//   0: ok, 1: error
+//
+int gfx_init(char *file)
+{
+  x86regs_t r;
+  int file_max, file_size, ofs;
+  unsigned u, code_start, file_start = 0, file_len = 0;
+  unsigned start2, len2, end2;
+  mem_info_t mi;
+
+  gfx_config.mem0_start = freemem;
+  gfx_config.mem0_end = freemem + freemem_len;
+
+  kernel_start = (GFX_MEMORY_START + GFX_MEMORY_SIZE) << 20;
+  initrd_end = 0;
+
+  gfx_config.xmem_0 = (GFX_MEMORY_START << 4) + GFX_MEMORY_SIZE;
+
+  r.eax = 0x28;		// Get memory size
+  x86int(0x22, &r);
+  u = (r.eflags & X86_CF) ? 0 : r.eax;
+
+  // round up
+  gfx_config.bios_mem_size = u ? ((u + 0xfffff) >> 20) << 20 : 256;
+
+  if(u > 0) {
+    // new interface
+
+    if(u < INITRD_MIN_MEMORY << 20) {
+      // ok, maybe there is a bigger block...
+
+      mi.cont = 0;
+      start2 = len2 = 0;
+      while(!get_mem_info(&mi)) {
+#if 0
+        printf(
+          "%08x%08x %08x%08x %08x %08x\n",
+          (unsigned) (mi.base >> 32), (unsigned) mi.base,
+          (unsigned) (mi.len >> 32), (unsigned) mi.len,
+          mi.type, mi.cont
+        );
+#endif
+        if(mi.type == 1) {
+          if((mi.base >> 32) || (mi.base + mi.len) >> 32) break;
+          if(mi.len > len2) {
+            start2 = mi.base;
+            len2 = mi.len;
+          }
+        }
+
+        if(!mi.cont) break;
+      }
+
+#if 0
+      printf("largest: %08x %08x\n", start2, len2);
+      getchar();
+#endif
+
+      if(len2 && len2 > 2 << 20 && len2 > u) {
+        // align to full MBs
+        end2 = (start2 + len2) & ~0xfffff;
+        start2 = (start2 + 0xfffff) & ~0xfffff;
+        len2 = end2 - start2;
+      }
+      else {
+        start2 = len2 = 0;
+      }
+
+      if(len2) {
+        u = len2;
+        initrd_end = end2;
+        // we could relocate the kernel as well...
+        // kernel_start = start2;
+      }
+    }
+
+    if(u < INITRD_MIN_MEMORY << 20) {
+      // a bit too small for us
+      printf("%u MB RAM is a bit small... - giving up\n", u >> 20);
+
+      return 1;
+    }
+  }
+
+#if 0
+  printf("mem = 0x%05x, mem0_start = 0x%05x, mem0_end = 0x%05x\n",
+    gfx.mem, gfx_config.mem0_start, gfx_config.mem0_end
+  );
+#endif
+
+  if(open(file) == -1) return 1;
+
+  // leave room for later alignment
+  gfx_config.archive_start = gfx_config.mem0_start + 0x10;
+
+  // read at most that much
+  file_size = file_max = gfx_config.mem0_end - gfx_config.archive_start;
+
+  if(fd.file_size != -1 && fd.file_size > file_size) return 1;
+
+  // if we have the real size, use it
+  if(fd.file_size != -1) file_size = fd.file_size;
+
+  file_size = read32(gfx_config.archive_start, file_size);
+
+  if(!file_size || file_size == file_max) return 1;
+
+  gfx_config.archive_end = gfx_config.archive_start + file_size;
+
+  // update free mem pointer & align it a bit
+  gfx_config.mem0_start = (gfx_config.archive_end + 3) & ~3;
+
+  // locate file inside cpio archive
+  if(!(code_start = find_file(gfx_config.archive_start, file_size, &file_start, &file_len))) {
+    printf("%s: invalid file format\n", file);
+
+    return 1;
+  }
+
+#if 0
+  printf("code_start = 0x%x, archive_start = 0x%x, file size = 0x%x, file_start = 0x%x, file_len = 0x%x\n",
+    code_start, gfx_config.archive_start, file_size, file_start, file_len
+  );
+#endif
+
+  if((ofs = (gfx_config.archive_start + file_start + code_start) & 0xf)) {
+    printf("oops: needs to be aligned!\n");
+
+    memcpy32(gfx_config.archive_start - ofs, gfx_config.archive_start, file_size);
+    gfx_config.archive_start -= ofs;
+    gfx_config.archive_end -= ofs;
+
+    return 1;
+  }
+
+  gfx_config.file = gfx_config.archive_start + file_start;
+  gfx.code_seg = (gfx_config.file + code_start) >> 4;
+
+  for(u = 0; u < sizeof gfx.jmp_table / sizeof *gfx.jmp_table; u++) {
+    gfx.jmp_table[u] = (gfx.code_seg << 16) + _mem16(gfx_config.file + code_start + 2 * u);
+  }
+
+#if 0
+  for(u = 0; u < sizeof gfx.jmp_table / sizeof *gfx.jmp_table; u++) {
+    printf("%d: 0x%08x\n", u, gfx.jmp_table[u]);
+  }
+#endif
+
+  // we are ready to start
+
+  r.esi = comboot + (uint32_t) &gfx_config;
+  farcall(gfx.jmp_table[GFX_BC_INIT], &r);
+
+  if((r.eflags & X86_CF)) {
+    printf("graphics initialization failed\n");
+
+    return 1;
+  }
+
+  return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_done()
+{
+  x86regs_t r;
+
+  farcall(gfx.jmp_table[GFX_BC_DONE], &r);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// return:
+//   boot menu index (-1: go to text mode prompt)
+//
+int gfx_input()
+{
+  x86regs_t r;
+
+  r.edi = comboot + (uint32_t) cmdline;
+  r.ecx = sizeof cmdline;
+  r.eax = timeout * 182 / 100;
+  timeout = 0;		// use timeout only first time
+  farcall(gfx.jmp_table[GFX_BC_INPUT], &r);
+  if((r.eflags & X86_CF)) r.eax = 1;
+
+  if(r.eax == 1) return -1;
+
+  return r.ebx;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int gfx_menu_init()
+{
+  x86regs_t r;
+
+  r.esi = comboot + (uint32_t) &menu;
+  farcall(gfx.jmp_table[GFX_BC_MENU_INIT], &r);
+
+  return 0;
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_infobox(int type, char *str1, char *str2)
+{
+  gfx_infobox32(type, comboot + (uint32_t) str1, str2 ? comboot + (uint32_t) str2 : 0);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void gfx_infobox32(int type, uint32_t str1, uint32_t str2)
+{
+  x86regs_t r;
+
+  r.eax = type;
+  r.esi = str1;
+  r.edi = str2;
+  farcall(gfx.jmp_table[GFX_BC_INFOBOX_INIT], &r);
+  r.edi = r.eax = 0;
+  farcall(gfx.jmp_table[GFX_BC_INPUT], &r);
+  farcall(gfx.jmp_table[GFX_BC_INFOBOX_DONE], &r);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Load & run kernel.
+//
+// Returns only on error.
+//
+void boot()
+{
+  x86regs_t r;
+
+  r.es = comboot >> 4;
+  r.ebx = (uint32_t) cmdline;
+  r.edi = kernel_start;			// kernel load address
+  r.edx = initrd_end;			// end of initrd load area (or 0)
+  r.esi = (uint32_t) _syslinux_hook;	// cs:si = syslinux callbacks
+  r.eax = 0x27;				// Load & run kernel (extended API)
+
+  x86int(0x22, &r);
+  if(!(r.eflags & X86_CF)) return;
+
+  r.es = comboot >> 4;
+  r.ebx = (uint32_t) cmdline;
+  r.eax = 3;		// Run command
+  x86int(0x22, &r);
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void syslinux_hook(x86regs_t *r)
+{
+  uint8_t f_nr = r->eax;
+
+  // bit 7: 0/1 continue/don't continue kernel loading in syslinux
+
+  switch(f_nr & 0x7f) {
+    case 0:		// abort kernel/initrd loading
+      gfx_infobox(0, "abort kernel loading", 0);
+      break;
+
+    case 1:		// kernel/initrd not found
+      gfx_infobox(0, "kernel not found: ", current_label);
+      break;
+
+    case 2:		// kernel corrupt
+      gfx_infobox(0, "kernel broken", 0);
+      break;
+
+    case 3:		// out of memory (while initrd loading)
+      gfx_infobox(0, "out of memory", 0);
+      break;
+
+    case 4:		// progress start
+      r->eax = r->ecx >> gfx_config.sector_shift;	// kernel size in sectors
+      r->esi = comboot + (uint32_t) current_label;
+      farcall(gfx.jmp_table[GFX_BC_PROGRESS_INIT], r);
+      break;
+
+    case 5:		// progress increment
+      // always 64k
+      r->eax = 0x10000 >> gfx_config.sector_shift;
+      farcall(gfx.jmp_table[GFX_BC_PROGRESS_UPDATE], r);
+      break;
+
+    case 6:		// progress end: kernel loaded, stop gfxboot
+      farcall(gfx.jmp_table[GFX_BC_PROGRESS_DONE], r);
+      gfx_done();
+      break;
+
+    case 7:		// stop gfxboot
+      gfx_done();
+      break;
+  }
+}
+
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void show_message(char *file)
+{
+  int c;
+
+  if(open(file) == -1) return;
+
+  while((c = getc()) != EOF) {
+    if(c < ' ' && c != '\n' && c != '\t') continue;
+    printf("%c", c);
+  }
+}
+
+
diff --git a/modules/libio.asm b/modules/libio.asm
new file mode 100644
index 0000000..1f77b44
--- /dev/null
+++ b/modules/libio.asm
@@ -0,0 +1,854 @@
+;
+; libio.asm
+;
+; A very minimalistic libc fragment.
+;
+; Copyright (c) 2009 Steffen Winterfeldt.
+;
+; 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, Inc., 53 Temple Place Ste 330, Boston MA 02111-1307,
+; USA; either version 2 of the License, or (at your option) any later
+; version; incorporated herein by reference.
+;
+
+
+; max argv elements passed to main()
+%define MAX_ARGS	8
+
+
+		bits 16
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; interface functions
+;
+; Make sure not to modify registers!
+;
+
+		global printf
+		global getchar
+		global clrscr
+		global memcpy
+		global memcpy32
+		global memset
+		global memset32
+		global x86int
+		global farcall
+		global reboot
+
+		global _gfx_cb
+		extern gfx_cb
+		global _syslinux_hook
+		extern syslinux_hook
+
+		global _start
+		extern _main
+
+		extern __bss_start
+
+		section .init
+
+_start:
+		cld
+
+		; clear static memory
+		mov di,__bss_start
+		mov cx,sp
+		sub cx,di
+		xor ax,ax
+		rep stosb
+
+		; parse args
+		mov ebx,80h
+		movzx si,byte [bx]
+		mov byte [si+81h],0dh	; just make sure
+		xor ecx,ecx
+		sub sp,MAX_ARGS * 4
+		mov ebp,esp
+		inc cx
+cmd_10:
+		inc bx
+		call skip_spaces
+		cmp al,0dh
+		jz cmd_60
+		imul si,cx,4
+		mov [bp+si],ebx
+		call skip_nonspaces
+		mov byte [bx],0
+		inc cx
+		cmp cx,MAX_ARGS
+		jae cmd_60
+		cmp al,0dh
+		jnz cmd_10
+cmd_60:
+		mov byte [bx],0
+
+		mov [bp],ebx		; argv[0] = ""
+
+		push ebp
+		push ecx
+
+		call dword _main
+
+		add sp,MAX_ARGS * 4 + 8
+
+		ret
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+skip_spaces:
+		mov al,[bx]
+		cmp al,0dh
+		jz skip_spaces_90
+		cmp al,' '
+		jz skip_spaces_10
+		cmp al,9
+		jnz skip_spaces_90
+skip_spaces_10:
+		inc bx
+		jmp skip_spaces
+skip_spaces_90:
+		ret
+
+skip_nonspaces:
+		mov al,[bx]
+		cmp al,0dh
+		jz skip_nonspaces_90
+		cmp al,' '
+		jz skip_nonspaces_90
+		cmp al,9
+		jz skip_nonspaces_90
+		inc bx
+		jmp skip_nonspaces
+skip_nonspaces_90:
+		ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+		section .text
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Write text to console.
+;
+; args on stack
+;
+; Note: 32 bit call/ret!
+;
+printf:
+		mov [pf_args],sp
+
+		pushad
+
+		call pf_next_arg
+		call pf_next_arg
+		mov si,ax
+printf_10:
+		lodsb
+		or al,al
+		jz printf_90
+		cmp al,'%'
+		jnz printf_70
+		mov byte [pf_pad],' '
+		lodsb
+		dec si
+		cmp al,'0'
+		jnz printf_20
+		mov [pf_pad],al
+printf_20:
+		call get_number
+		mov [pf_num],ecx
+		lodsb
+		or al,al
+		jz printf_90
+		cmp al,'%'
+		jz printf_70
+
+		cmp al,'S'
+		jnz printf_23
+		mov byte [pf_raw_char],1
+		jmp printf_24
+printf_23:
+		cmp al,'s'
+		jnz printf_30
+printf_24:
+		push si
+
+		call pf_next_arg
+		mov si,ax
+		call puts
+
+		sub ecx,[pf_num]
+		neg ecx
+		mov al,' '
+		call putc_n
+
+		pop si
+
+		mov byte [pf_raw_char],0
+		jmp printf_10
+
+printf_30:
+		cmp al,'u'
+		jnz printf_35
+
+		mov dx,10
+printf_31:
+		push si
+
+		call pf_next_arg
+		or dh,dh
+		jz printf_34
+		test eax,eax
+		jns printf_34
+		neg eax
+		push eax
+		mov al,'-'
+		call putc
+		pop eax
+printf_34:
+		mov cl,[pf_num]
+		mov ch,[pf_pad]
+		call number
+		call puts
+
+		pop si
+
+		jmp printf_10
+
+printf_35:
+		cmp al,'x'
+		jnz printf_36
+
+printf_35a:
+		mov dx,10h
+		jmp printf_31
+
+printf_36:
+		cmp al,'d'
+		jnz printf_37
+printf_36a:
+		mov dx,10ah
+		jmp printf_31
+
+printf_37:
+		cmp al,'i'
+		jz printf_36a
+
+		cmp al,'p'
+		jnz printf_40
+		mov al,'0'
+		call putc
+		mov al,'x'
+		call putc
+		jmp printf_35a
+
+printf_40:
+		cmp al,'c'
+		jnz printf_45
+
+		push si
+		call pf_next_arg
+		call putc
+		pop si
+		jmp printf_10
+printf_45:
+
+		; more ...
+
+
+printf_70:
+		call putc
+		jmp printf_10
+printf_90:
+		popad
+
+		o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Get next printf arg from [pf_args].
+;
+; return:
+;  eax		arg
+;
+; changes no regs
+;
+pf_next_arg:
+		movzx eax,word [pf_args]
+		add word [pf_args],4
+		mov eax,[eax]
+		ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Convert string to number.
+;
+;  si		string
+;
+; return:
+;  ecx		number
+;  si		points past number
+;  CF		not a number
+;
+get_number:
+
+		xor ecx,ecx
+		mov ah,1
+get_number_10:
+		lodsb
+		or al,al
+		jz get_number_90
+		sub al,'0'
+		jb get_number_90
+		cmp al,9
+		ja get_number_90
+		movzx eax,al
+		imul ecx,ecx,10
+		add ecx,eax
+		jmp get_number_10
+get_number_90:
+		dec si
+		shr ah,1
+		ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Convert a number to string.
+;
+;  eax		number
+;  cl		field size
+;  ch		padding char
+;  dl		base
+;
+; return:
+;  si		string
+;
+number:
+		mov di,num_buf
+		push ax
+		push cx
+		mov al,ch
+		mov cx,num_buf_end - num_buf
+		rep stosb
+		pop cx
+		pop ax
+		movzx cx,cl
+		movzx ebx,dl
+number_10:
+		xor edx,edx
+		div ebx
+		cmp dl,9
+		jbe number_20
+		add dl,27h
+number_20:
+		add dl,'0'
+		dec edi
+		mov [di],dl
+		or eax,eax
+		jz number_30
+		cmp di,num_buf
+		ja number_10
+number_30:
+		mov si,di
+		or cx,cx
+		jz number_90
+		cmp cx,num_buf_end - num_buf
+		jae number_90
+		mov si,num_buf_end
+		sub si,cx
+number_90:
+		ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Write string.
+;
+;  si		text
+;
+; return:
+;  cx		length
+;
+puts:
+		xor cx,cx
+puts_10:
+		lodsb
+		or al,al
+		jz puts_90
+		call putc
+		inc cx
+		jmp puts_10
+puts_90:
+		ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Write char multiple times.
+;
+;  al		char
+;  cx		count (does nothing if count <= 0)
+;
+putc_n:
+		cmp cx,0
+		jle putc_n_90
+		call putc
+		dec cx
+		jmp putc_n
+putc_n_90:
+		ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Print char.
+;
+;  al		char
+;
+putc:
+		pusha
+		cmp al,0ah
+		jnz putc_30
+		push ax
+		mov al,0dh
+		call putc_50
+		pop ax
+putc_30:
+		call putc_50
+		popa
+		ret
+putc_50:
+		mov bx,7
+		mov ah,0eh
+		int 10h
+		ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Read char from stdin.
+;
+; return:
+;  eax		char
+;
+; Note: 32 bit call/ret!
+;
+getchar:
+		pushad
+		mov ah,10h
+		int 16h
+		mov [gc_tmp],al
+		popad
+		movzx eax,byte [gc_tmp]
+		o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; Clear screen.
+;
+; Note: 32 bit call/ret!
+;
+clrscr:
+		pushad
+		push es
+		push word 40h
+		pop es
+		mov ax,600h
+		mov bh,7
+		xor cx,cx
+		mov dl,[es:4ah]
+		or dl,dl
+		jnz clrscr_20
+		mov dl,80
+clrscr_20:
+		dec dl
+		mov dh,[es:84h]
+		or dh,dh
+		jnz clrscr_30
+		mov dh,24
+clrscr_30:
+		int 10h
+		mov ah,2
+		mov bh,[es:62h]
+		xor dx,dx
+		int 10h
+		pop es
+		popad
+		o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; dst = memcpy(dst, src, size).
+;
+; args on stack
+;
+; return:
+;  eax		dst
+;
+; Note: 32 bit call/ret!
+;
+memcpy:
+		pushad
+
+		mov edi,[esp+0x20+4]
+		mov esi,[esp+0x20+8]
+		mov ecx,[esp+0x20+12]
+
+		rep movsb
+
+		popad
+
+		mov eax,[esp+4]
+
+		o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; dst = memset(dst, val, size).
+;
+; args on stack
+;
+; return:
+;  eax		dst
+;
+; Note: 32 bit call/ret!
+;
+memset:
+		pushad
+
+		mov edi,[esp+0x20+4]
+		mov al,[esp+0x20+8]
+		mov ecx,[esp+0x20+12]
+
+		rep stosb
+
+		popad
+
+		mov eax,[esp+4]
+
+		o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; dst = memset32(dst, val, size).
+;
+; args on stack
+;
+; return:
+;  eax		dst
+;
+; Note: 32 bit call/ret!
+;
+memset32:
+		pushad
+
+		push es
+
+		mov ebx,[esp+0x22+4]	; edi
+		mov al,[esp+0x22+8]
+		mov edx,[esp+0x22+12]
+
+memset32_20:
+		mov edi,ebx
+		mov ecx,ebx
+		and di,0fh
+		shr ecx,4
+		mov es,cx
+
+		mov ecx,0fff0h
+		cmp edx,ecx
+		ja memset32_40
+		mov ecx,edx
+memset32_40:
+		add ebx,ecx
+		sub edx,ecx
+
+		rep stosb
+
+		jnz memset32_20
+
+		pop es
+
+		popad
+
+		mov eax,[esp+4]
+
+		o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; dst = memcpy32(dst, src, size).
+;
+; dst, src are 32bit linear addresses
+;
+; args on stack
+;
+; return:
+;  eax		dst
+;
+; Note: 32 bit call/ret!
+;
+memcpy32:
+		pushad
+
+		push ds
+		push es
+
+		mov ebx,[esp+0x24+4]	; edi
+		mov eax,[esp+0x24+8]	; esi
+		mov edx,[esp+0x24+12]
+
+memcpy32_20:
+		mov edi,ebx
+		mov ecx,ebx
+		and di,0fh
+		shr ecx,4
+		mov es,cx
+
+		mov esi,eax
+		mov ecx,eax
+		and si,0fh
+		shr ecx,4
+		mov ds,cx
+
+		mov ecx,0fff0h
+		cmp edx,ecx
+		ja memcpy32_40
+		mov ecx,edx
+memcpy32_40:
+		add ebx,ecx
+		add eax,ecx
+		sub edx,ecx
+
+		rep movsb
+
+		jnz memcpy32_20
+
+		pop es
+		pop ds
+
+		popad
+
+		mov eax,[esp+4]
+
+		o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; x86int(int, *regs).
+;
+; args on stack
+;
+; Note: 32 bit call/ret!
+;
+x86int:
+		pushad
+
+		mov al,[esp+0x20+4]
+		mov [x86int_p],al
+		mov ebx,[esp+0x20+8]
+
+		mov ecx,[bx+8]
+		mov edx,[bx+0ch]
+		mov esi,[bx+10h]
+		mov edi,[bx+14h]
+		mov ebp,[bx+18h]
+		mov ah,[bx+1ch]		; eflags
+		sahf
+		mov eax,[bx]
+
+		mov es,[bx+22h]
+		mov fs,[bx+24h]
+		mov gs,[bx+26h]
+		mov ds,[bx+20h]
+
+		mov ebx,[cs:bx+4]
+
+		int 0h
+x86int_p	equ $-1
+
+		push ebx
+		mov ebx,[esp+0x24+8]
+		pop dword [cs:bx+4]
+
+		mov [cs:bx],eax
+		mov [cs:bx+20h],ds
+
+		mov ax,cs
+		mov ds,ax
+
+		mov [cs:bx+22h],es
+		mov [cs:bx+24h],fs
+		mov [cs:bx+26h],gs
+
+		mov es,ax
+		mov fs,ax
+		mov gs,ax
+
+		mov [bx+8],ecx
+		mov [bx+0ch],edx
+		mov [bx+10h],esi
+		mov [bx+14h],edi
+		mov [bx+18h],ebp
+		pushfd
+		pop dword [bx+1ch]
+
+		popad
+
+		o32 ret
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; farcall(addr, *regs).
+;
+; args on stack
+;
+; Note: 32 bit call/ret!
+;
+farcall:
+		pushad
+
+		mov ebx,[esp+0x20+8]
+
+		mov ecx,[bx+8]
+		mov edx,[bx+0ch]
+		mov esi,[bx+10h]
+		mov edi,[bx+14h]
+		mov ebp,[bx+18h]
+		mov ah,[bx+1ch]		; eflags
+		sahf
+		mov eax,[bx]
+
+		mov [farcall_stack],sp
+		sub word [farcall_stack],1000h	; 4k stack should be enough for gfxboot
+		mov [farcall_stack+2],ss
+
+		mov es,[bx+22h]
+		mov fs,[bx+24h]
+		mov gs,[bx+26h]
+		mov ds,[bx+20h]
+
+		mov ebx,[cs:bx+4]
+
+		call far [esp+0x20+4]
+
+		push ebx
+		mov ebx,[esp+0x24+8]
+		pop dword [cs:bx+4]
+
+		mov [cs:bx],eax
+		mov [cs:bx+20h],ds
+
+		mov ax,cs
+		mov ds,ax
+
+		mov [cs:bx+22h],es
+		mov [cs:bx+24h],fs
+		mov [cs:bx+26h],gs
+
+		mov es,ax
+		mov fs,ax
+		mov gs,ax
+
+		mov [bx+8],ecx
+		mov [bx+0ch],edx
+		mov [bx+10h],esi
+		mov [bx+14h],edi
+		mov [bx+18h],ebp
+		pushfd
+		pop dword [bx+1ch]
+
+		popad
+
+		o32 ret
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; wrapper around gfx_cb()
+;
+; we need to switch stack to ensure ss = cs = ds = es for gcc
+;
+_gfx_cb:
+		push cs
+		pop ds
+		push cs
+		pop es
+		mov [cb_stack],sp
+		mov [cb_stack+2],ss
+		lss sp,[farcall_stack]
+		sub sp,28h		; sizeof x86regs_t
+		mov [esp+18h],ebp
+		mov ebp,esp
+		push ebp
+		mov [bp],eax
+		mov [bp+4],ebx
+		mov [bp+8],ecx
+		mov [bp+0ch],edx
+		mov [bp+10h],esi
+		mov [bp+14h],edi
+		call dword gfx_cb
+		lea ebp,[esp+4]
+		mov eax,[bp]
+		mov ebx,[bp+4]
+		mov ecx,[bp+8]
+		mov edx,[bp+0ch]
+		mov esi,[bp+10h]
+		mov edi,[bp+14h]
+		mov ebp,[bp+18h]
+		lss sp,[cb_stack]
+		retf
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+; wrapper around syslinux_hook()
+;
+; ensure cs = ds = es for gcc
+;
+_syslinux_hook:
+		push cs
+		pop ds
+		push cs
+		pop es
+		sub sp,28h		; sizeof x86regs_t
+		mov [esp+18h],ebp
+		mov ebp,esp
+		push ebp
+		mov [bp],eax
+		mov [bp+4],ebx
+		mov [bp+8],ecx
+		mov [bp+0ch],edx
+		mov [bp+10h],esi
+		mov [bp+14h],edi
+		call dword syslinux_hook
+		lea ebp,[esp+4]
+		mov eax,[bp]
+		mov ebx,[bp+4]
+		mov ecx,[bp+8]
+		mov edx,[bp+0ch]
+		mov esi,[bp+10h]
+		mov edi,[bp+14h]
+		mov ebp,[bp+18h]
+		add sp,28h+4
+		retf
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+reboot:
+		mov word [472h],1234h
+		jmp 0ffffh:0
+		int 19h
+		jmp $
+
+
+; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+		section .data
+
+farcall_stack	dd 0
+cb_stack	dd 0
+
+; buffer for number conversions
+; must be large enough for ps_status_info()
+num_buf		times 23h db 0
+num_buf_end	db 0
+
+; temp data for printf
+pf_args		dw 0
+pf_num		dd 0
+pf_sig		db 0
+pf_pad		db 0
+pf_raw_char	db 0
+gc_tmp		db 0
+
diff --git a/modules/libio.h b/modules/libio.h
new file mode 100644
index 0000000..16af520
--- /dev/null
+++ b/modules/libio.h
@@ -0,0 +1,133 @@
+/*
+ *
+ * libio.h	include file for libio
+ *
+ * Copyright (c) 2009 Steffen Winterfeldt.
+ *
+ * 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, Inc., 53 Temple Place Ste 330, Boston MA
+ * 02111-1307, USA; either version 2 of the License, or (at your option) any
+ * later version; incorporated herein by reference.
+ *
+ */
+
+#ifndef _LIBIO_H
+#define _LIBIO_H
+
+
+asm(".code16gcc\n");
+
+
+#define int8_t char
+#define int16_t short
+#define int32_t int
+#define int64_t long long
+#define uint8_t unsigned char
+#define uint16_t unsigned short
+#define uint32_t unsigned
+#define uint64_t unsigned long long
+
+#define X86_CF 0x0001
+#define X86_PF 0x0004
+#define X86_AF 0x0010
+#define X86_ZF 0x0040
+#define X86_SF 0x0080
+#define X86_TF 0x0100
+#define X86_IF 0x0200
+#define X86_DF 0x0400
+#define X86_OF 0x0800
+
+#define EOF -1
+
+#define main _main
+
+
+typedef struct __attribute ((packed)) {
+  uint32_t eax, ebx, ecx, edx, esi, edi, ebp, eflags;
+  uint16_t ds, es, fs, gs;
+} x86regs_t;
+
+
+static inline uint16_t comboot_seg(void)
+{
+  uint16_t u;
+
+  asm("mov %%cs, %0" : "=r" (u));
+
+  return u;
+}
+
+
+static inline uint8_t _mem8(uint32_t p)
+{
+  uint8_t u;
+
+  asm(
+    "movl %1,%%esi\n"
+    "shrl $4,%%esi\n"
+    "mov %%si,%%fs\n"
+    "movl %1,%%esi\n"
+    "and $15, %%si\n"
+    "movb %%fs:(%%si),%0\n"
+    : "=abcd" (u) : "r" (p) : "si"
+  );
+
+  return u;
+}
+
+
+static inline uint16_t _mem16(uint32_t p)
+{
+  uint16_t u;
+
+  asm(
+    "movl %1,%%esi\n"
+    "shrl $4,%%esi\n"
+    "mov %%si,%%fs\n"
+    "movl %1,%%esi\n"
+    "and $15, %%si\n"
+    "movw %%fs:(%%si),%0\n"
+    : "=r" (u) : "r" (p) : "si"
+  );
+
+  return u;
+}
+
+
+static inline uint32_t _mem32(uint32_t p)
+{
+  uint32_t u;
+
+  asm(
+    "movl %1,%%esi\n"
+    "shrl $4,%%esi\n"
+    "mov %%si,%%fs\n"
+    "movl %1,%%esi\n"
+    "and $15, %%si\n"
+    "movl %%fs:(%%si),%0\n"
+    : "=r" (u) : "r" (p) : "si"
+  );
+
+  return u;
+}
+
+
+int _main(int argc, char **argv);
+void printf(char *format, ...) __attribute__ ((format (printf, 1, 2)));
+int getchar(void);
+void clrscr(void);
+void *memcpy(void *dest, const void *src, int n);
+uint32_t memcpy32(uint32_t dest, uint32_t src, int n);
+void *memset(void *dest, int c, int n);
+uint32_t memset32(uint32_t dest, int c, int n);
+void x86int(unsigned intr, x86regs_t *regs);
+void farcall(uint32_t seg_ofs, x86regs_t *regs);
+void gfx_cb(x86regs_t *r);
+void _gfx_cb(void);
+void syslinux_hook(x86regs_t *r);
+void _syslinux_hook(void);
+void reboot(void);
+
+#endif	/* _LIBIO_H */
+
