/* 
 * (C) Copyright 1992, ..., 2004 the "DOSEMU-Development-Team".
 *
 * for details see file COPYING in the DOSEMU distribution
 */

/* 
 * DANG_BEGIN_MODULE
 *
 * REMARK
 * Video BIOS implementation.
 *
 * This module handles the int10 video functions.
 * Most functions here change only the video memory and status 
 * variables; the actual screen is then rendered asynchronously 
 * after these by Video->update_screen.
 *
 * /REMARK
 * DANG_END_MODULE
 *
 * DANG_BEGIN_CHANGELOG
 *
 * 5/24/95, Erik Mouw (J.A.K.Mouw@et.tudelft.nl) and 
 * Arjan Filius (I.A.Filius@et.tudelft.nl)
 * changed int10() to make graphics work with X.
 *
 * 1998/04/05: Put some work into set_video_mode() (made it
 * more VGA comaptible) and removed new_set_video_mode().
 * Removed (useless) global variable "gfx_mode".
 * -- sw (Steffen Winterfeldt <wfeldt@suse.de>)
 * 
 * Readded new_set_video_mode, its needed for non-X compiles.
 * -- EB 3 June 1998
 *
 * Renamed new_set_video_mode to X_set_video_mode to avoid confusion
 * in the future
 * -- Hans 980614
 *
 * 1998/12/12: Fixed some bugs and integrated Josef Pavlik's <jet@spintec.com>
 * patches; improved font/palette changes, get screen mode (ah = 0x0f) fixed,
 * cursor shape is now initialized during mode set.
 * -- sw
 *
 * 2000/05/18: Splitted int10() into a X and non-X part. Reworked to X part so
 * that it supports fonts in gfx modes.
 * -- sw
 *
 * 2002/11/30: Started user font support. Needs some more work!
 * -- eric@coli.uni-sb.de - Eric
 *
 * DANG_END_CHANGELOG
 */


#include "config.h"

#include <string.h>
#include <unistd.h>

#if X_GRAPHICS
#include "X.h"
#endif

#include "emu.h"
#include "video.h"
#include "memory.h"
#include "bios.h"
#include "vc.h"
#include "speaker.h"

#if X_GRAPHICS
#include "vgaemu.h"
#endif

/*
 * Activate some debug output.
 */
#define DEBUG_INT10	0

/* a bit brutal, but enough for now */
#if VIDEO_CHECK_DIRTY
#define set_dirty(page) vm86s.screen_bitmap = 0xffffffff
#else
#define set_dirty(page)
#endif

#if USE_DUALMON
  #define BIOS_CONFIG_SCREEN_MODE (READ_WORD(BIOS_CONFIGURATION) & 0x30)
  #define IS_SCREENMODE_MDA (BIOS_CONFIG_SCREEN_MODE == 0x30)
  /* This is the text screen base, the DOS program actually has to use.
   * Programs that support simultaneous dual monitor support rely on
   * the fact, that the BIOS takes B0000 for EQUIP-flags 4..5 = 3
   * else B8000 as regenbuffer address. Each compatible PC-BIOS behaves so.
   * This is ugly, but there is no screen buffer address in the BIOS-DATA
   * at 0x400. (Hans)
   */
  #define BIOS_SCREEN_BASE (IS_SCREENMODE_MDA ? MDA_PHYS_TEXT_BASE : VGA_PHYS_TEXT_BASE)
#endif

/* this maps the cursor shape given by int10, fn1 to the actually
   displayed cursor start&end values in cursor_shape. This seems 
   to be typical IBM Black Compatiblity Magic.
   I modeled it approximately from the behaviour of my own 
   VGA's BIOS.
   I'm not sure if it is correct for start=end and for font_heights
   other than 16.
*/

#define i10_msg(x...) v_printf("INT10: " x)

#if DEBUG_INT10 >= 1
#define i10_deb(x...) v_printf("INT10: " x)
#else
#define i10_deb(x...)
#endif

static void tty_char_out(unsigned char ch, int s, int attr);
static void int10_old(void);
#if X_GRAPHICS
static void int10_new(void);
static void vga_ROM_to_RAM(unsigned height, int bank);
#endif

static inline void set_cursor_shape(ushort shape) {
   int cs,ce;

   if (config.cardtype == CARD_NONE)
     return;

   cs=CURSOR_START(shape) & 0x1F;
   ce=CURSOR_END(shape) & 0x1F;

   if (shape & 0x6000 || cs>ce) {
      i10_deb("no cursor\n");
      cursor_shape=NO_CURSOR;
      cursor_blink=0;
      return;
   }
   
   /* is this correct (my own VGA just turns off the cursor if
      this bit is set)
   */
   cursor_blink = 1;

   cs&=0x0F;
   ce&=0x0F;
   if (ce>3 && ce<12 && (config.cardtype != CARD_MDA)) {
      if (cs>ce-3) cs+=font_height-ce-1;
      else if (cs>3) cs=font_height/2;
      ce=font_height-1;
   }
   i10_msg("mapped cursor: start %d, end %d\n", cs, ce);
   CURSOR_START(cursor_shape)=cs;
   CURSOR_END(cursor_shape)=ce;
}

/* This is a better scroll routine, mostly for aesthetic reasons. It was
 * just too horrible to contemplate a scroll that worked 1 character at a
 * time :-)
 * 
 * It may give some performance improvement on some systems (it does
 * on mine) (Andrew Tridgell)
 */
void
Scroll(us *sadr, int x0, int y0, int x1, int y1, int l, int att)
{
  int dx = x1 - x0 + 1;
  int dy = y1 - y0 + 1;
  int x, y, co, li;
  us blank = ' ' | (att << 8);
  us tbuf[MAX_COLUMNS];

  if (config.cardtype == CARD_NONE)
     return;

  li= READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1) + 1;
  co= READ_WORD(BIOS_SCREEN_COLUMNS);

  if ( (config.cardtype == CARD_MDA) && ((att & 7) != 0) && ((att & 7) != 7) )
    {
      blank = ' ' | ((att | 7) << 8);
    }


  if (x1 >= co || y1 >= li)
    {
      v_printf("VID: Scroll parameters out of bounds, in Scroll!\n");
      v_printf("VID: Attempting to fix with clipping!\n");
    /* kludge for ansi.sys' clear screen - we'd better do real clipping */
    /* Also a cludge to fix list, but in the other dimension */
      if (x1 >= co) x1 = co -1;
      if (y1 >= li) y1 = li -1;
      dx = x1 - x0 +1;
      dy = y1 - x0 +1;
    }
  if (dx <= 0 || dy <= 0 || x0 < 0 || x1 >= co || y0 < 0 || y1 >= li)
    {
      v_printf("VID: Scroll parameters impossibly out of bounds, giving up!\n");
    return;
    }

  /* make a blank line */
  for (x = 0; x < dx; x++)
    tbuf[x] = blank;

  if (l >= dy || l <= -dy)
    l = 0;

  set_dirty(video_page);
  
  if (l == 0) {			/* Wipe mode */
    for (y = y0; y <= y1; y++)
      /*memcpy(&sadr[y * co + x0], tbuf, dx * sizeof(us));*/
      MEMCPY_2DOS(&sadr[y * co + x0], tbuf, dx * sizeof(us));
    return;
  }

  if (l > 0) {
    if (dx == co)
      /*memcpy(&sadr[y0 * co], &sadr[(y0 + l) * co], (dy - l) * dx * sizeof(us));*/
      MEMCPY_DOS2DOS(&sadr[y0 * co], &sadr[(y0 + l) * co], (dy - l) * dx * sizeof(us));
    else
      for (y = y0; y <= (y1 - l); y++)
	/*memcpy(&sadr[y * co + x0], &sadr[(y + l) * co + x0], dx * sizeof(us));*/
	MEMCPY_DOS2DOS(&sadr[y * co + x0], &sadr[(y + l) * co + x0], dx * sizeof(us));

    for (y = y1 - l + 1; y <= y1; y++)
      /*memcpy(&sadr[y * co + x0], tbuf, dx * sizeof(us));*/
      MEMCPY_2DOS(&sadr[y * co + x0], tbuf, dx * sizeof(us));
  }
  else {
    for (y = y1; y >= (y0 - l); y--)
      /*memcpy(&sadr[y * co + x0], &sadr[(y + l) * co + x0], dx * sizeof(us));*/
      MEMCPY_DOS2DOS(&sadr[y * co + x0], &sadr[(y + l) * co + x0], dx * sizeof(us));

    for (y = y0 - l - 1; y >= y0; y--)
      /*memcpy(&sadr[y * co + x0], tbuf, dx * sizeof(us));*/
      MEMCPY_2DOS(&sadr[y * co + x0], tbuf, dx * sizeof(us));
  }
}

#if USE_DUALMON
  #define bios_scroll(x0,y0,x1,y1,n,attr) ({\
    if (IS_SCREENMODE_MDA) Scroll((void *)MDA_PHYS_TEXT_BASE,x0,y0,x1,y1,n,attr); \
    else Scroll(screen_adr,x0,y0,x1,y1,n,attr);\
   })
#else
  #define bios_scroll(x0,y0,x1,y1,n,attr) Scroll(screen_adr,x0,y0,x1,y1,n,attr)
#endif

#if X_GRAPHICS
static int using_text_mode(void)
{
  unsigned char mode = READ_BYTE(BIOS_VIDEO_MODE);
  return mode <= 3 || mode == 7 || (mode >= 0x50 && mode <= 0x5a);
}
#endif

/* Output a character to the screen. */ 
void char_out(unsigned char ch, int page)
{
  tty_char_out(ch, page, -1);
}

/*
 * Output a character to the screen.
 * If attr != -1, set the attribute byte, too.
 */ 
void tty_char_out(unsigned char ch, int s, int attr)
{
  int newline_att = 7;
  int xpos, ypos, co, li;
  int gfx_mode = 0;
  unsigned char *dst;

#if USE_DUALMON
  int virt_text_base = BIOS_SCREEN_BASE;
#if X_GRAPHICS
  if (config.X) virt_text_base = (int)vga.mem.base;
#endif
#endif

/* i10_deb("tty_char_out: char 0x%02x, page %d, attr 0x%02x\n", ch, s, attr); */

  if (config.cardtype == CARD_NONE) {
     putchar (ch);
     return;
  }

  li= READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1) + 1;
  co= READ_WORD(BIOS_SCREEN_COLUMNS);

  xpos = get_bios_cursor_x_position(s);
  ypos = get_bios_cursor_y_position(s);

  /* check for gfx mode */
#if X_GRAPHICS
  if(config.X && !using_text_mode()) gfx_mode = 1;
#endif

  switch (ch) {
  case '\r':         /* Carriage return */
    xpos = 0;
    break;

  case '\n':         /* Newline */
    /* Color newline */
    newline_att = gfx_mode ? 0 : ATTR(SCREEN_ADR(s) + ypos*co + xpos);
    ypos++;
    xpos = 0;                  /* EDLIN needs this behavior */
    break;

  case 8:           /* Backspace */
    if (xpos > 0) xpos--;
    break;
  
  case '\t':        /* Tab */
    i10_deb("char_out: tab\n");
    do {
	char_out(' ', s); 
  	xpos = get_bios_cursor_x_position(s);
    } while (xpos % 8 != 0);
    break;

  case 7:           /* Bell */
    speaker_on(125, 0x637);
    return;

  default:          /* Printable character */
#if X_GRAPHICS
    if(gfx_mode) {
      vgaemu_put_char(xpos, ypos, ch, attr);
    }
    else
#endif
    {
      dst = (unsigned char *) (SCREEN_ADR(s) + ypos*co + xpos);
      WRITE_BYTE(dst, ch);
      if(attr != -1) WRITE_BYTE(dst + 1, attr);
      set_dirty(s);
    }
    xpos++;
  }

  if (xpos == co) {
    xpos = 0;
    ypos++;
  }
  if (ypos == li) {
    ypos--;
#if X_GRAPHICS
    if(gfx_mode)
      vgaemu_scroll(0, 0, co - 1, li - 1, 1, 0);
    else
#endif
      bios_scroll(0,0,co-1,li-1,1,newline_att);
  }
  set_bios_cursor_x_position(s, xpos);
  set_bios_cursor_y_position(s, ypos);
  cursor_col = xpos;
  cursor_row = ypos;
}

/* The following clears the screen buffer. It does it only to the screen 
 * buffer.  If in termcap mode, the screen will be cleared the next time
 * restore_screen() is called.
 */
void
clear_screen(int s, int att)
{
  u_short *schar, blank = ' ' | (att << 8);
  int lx, co, li;
#if USE_DUALMON
  int virt_text_base = BIOS_SCREEN_BASE;
#if X_GRAPHICS
  if (config.X) virt_text_base = (int)vga.mem.base;
#endif
#endif

  if (config.cardtype == CARD_NONE)
     return;

  li= READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1) + 1;
  co= READ_WORD(BIOS_SCREEN_COLUMNS);

  v_printf("INT10: cleared screen: page %d, attr 0x%02x, screen_adr %p\n", s, att, SCREEN_ADR(s));
  if (s > max_page) return;
  
  for (schar = SCREEN_ADR(s), 
       lx = 0; lx < (co * li); 
/*       *(schar++) = blank, lx++); */
       WRITE_WORD(schar++, blank), lx++);

  set_dirty(s);
  set_bios_cursor_x_position(s, 0);
  set_bios_cursor_y_position(s, 0);
  cursor_row = cursor_col = 0;
}


#if X_GRAPHICS

/*
 * set_video_mode() accepts both (S)VGA and VESA mode numbers
 * It should be fully compatible with the old set_video_mode()
 * function.
 * Note: bit 16 in "mode" is used internally to indicate that
 * nothing should be done except adjusting the font size.
 * -- 1998/04/04 sw
 */

static boolean X_set_video_mode(int mode) {
  vga_mode_info *vmi;
  int clear_mem = 1;
  int adjust_font_size = 0;
  unsigned u;

  if (config.cardtype == CARD_NONE) {
    i10_msg("set_video_mode: no video!\n");
    return 0;
  }


  if(mode & (1 << 16)) {
    adjust_font_size = 1;
    mode &= ~(1 << 16);
  }

  mode &= 0xffff;

  if(mode != video_mode) adjust_font_size = 0;

  i10_msg("set_video_mode: mode 0x%02x%s\n", mode, adjust_font_size ? " (adjust font size)" : "");

  if((vmi = vga_emu_find_mode(mode, NULL)) == NULL) {
    i10_msg("set_video_mode: undefined video mode\n");
    return 0;
  }

  if(Video->setmode == NULL) {
    i10_msg("set_video_mode: no setmode handler!\n");
    return 0;
  }

  if(adjust_font_size) {
    i10_msg("set_video_mode: font size %d lines\n", vga_font_height);

    CRTC_set_index(9);
    CRTC_write_value((CRTC_read_value() & ~0x1f) + vga_font_height -1);
    vmi->char_height = vga_font_height; /* should already be done, */
    /* as side effect of the CRTC write access in crtcemu... */

    text_scanlines = vmi->height;
    li = text_scanlines / vga_font_height;
    if(li > MAX_LINES) li = MAX_LINES;
    WRITE_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1, li - 1);
    WRITE_WORD(BIOS_FONT_HEIGHT, vga_font_height);
    if(using_text_mode()) X_set_textsize(co, li);
    return 1;
  }

  video_mode = mode;

  if(mode >= 0x80 && mode < 0x100) {
    mode &= 0x7f;
    clear_mem = 0;
  }
  if(mode & 0x8000) {
    mode &= ~0x8000;
    clear_mem = 0;
  }

  if(config.cardtype == CARD_MDA) video_mode = 7;

  /*
   * We store the SVGA mode number (if possible) even when setting
   * a VESA mode.
   * Note that this gives mode 0x7f if no VGA mode number
   * had been assigned. -- sw
   */
  WRITE_BYTE(BIOS_VIDEO_MODE, vmi->VGA_mode & 0x7f);
  WRITE_BYTE(BIOS_CURRENT_SCREEN_PAGE, 0);

  li = vmi->text_height;
  co = vmi->text_width;
  if(li > MAX_LINES) li = MAX_LINES;
  vga_font_height = vmi->char_height;
  text_scanlines = vmi->height;
  if (using_text_mode()) {
    vga_font_height = text_scanlines / 25;
    vmi->char_height = vga.char_height = vga_font_height;
  }

  WRITE_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1, li - 1);
  WRITE_WORD(BIOS_SCREEN_COLUMNS, co);
  WRITE_WORD(BIOS_FONT_HEIGHT, vga_font_height);

#if USE_DUALMON
  /*
   * The following code (& comment) is taken literally
   * from the old set_video_mode(). Don't know what it's
   * for (or if it works). -- 1998/04/04 sw 
   */

  /*
   * This to be sure in case of older DOS programs probing HGC.
   * There was no secure way to detect a HGC before VGA was invented.
   * ( Now we can do INT 10, AX=1A00 ).
   * Some older DOS programs do it by modifying EQUIP-flags
   * and then let the BIOS say, if it can ?!?!)
   * If we have config.dualmon, this happens legally.
   */
  if(config.dualmon)
    Video->setmode(7, co, li);
  else
#endif

  /* setmode needs video_mode to _still have_ the memory-clear bit -- sw */
  Video->setmode(vmi->mode_class, co, li);

  /*
   * video_mode is expected to be the mode number _without_ the
   * memory-clear bit
   * -- sw
   */
  video_mode = mode;

  if(clear_mem && using_text_mode()) clear_screen(0, 7);

  WRITE_BYTE(BIOS_VIDEO_INFO_0, clear_mem ? 0x60 : 0xe0);
  memset((void *) 0x450, 0, 0x10);	/* equiv. to set_bios_cursor_(x/y)_position(0..7, 0) */

  video_page = 0;
  WRITE_WORD(BIOS_VIDEO_MEMORY_ADDRESS, 0);
  virt_text_base = (int)vga.mem.base;
  screen_adr = (void *)vga.mem.base;
  screen_mask = 0;
  cursor_col = get_bios_cursor_x_position(0);
  cursor_row = get_bios_cursor_y_position(0);

  set_cursor_shape(0x0607);
  WRITE_WORD(BIOS_CURSOR_SHAPE, 0x0607);

  WRITE_WORD(BIOS_VIDEO_PORT, vga.config.mono_port ? 0x3b4 : 0x3d4);
  if (mode == 0x6)
    WRITE_BYTE(BIOS_VDU_COLOR_REGISTER, 0x3f);
  else if (mode <= 0x7)
    WRITE_BYTE(BIOS_VDU_COLOR_REGISTER, 0x30);

  switch(vga.char_height) {
    case 14:
      u = vgaemu_bios.font_14;
      break;
    case 16:
      u = vgaemu_bios.font_16;
      break;
    default:
      u = vgaemu_bios.font_8;
  }

  if (using_text_mode()) {
    v_printf("INT10: X_set_video_mode: 8x%d ROM font -> bank 0\n",
             vga.char_height);
    vga_ROM_to_RAM(vga.char_height, 0); /* 0 is default bank */
    i10_msg("activated font bank 0\n");
  }

  SETIVEC(0x43, 0xc000, u);

  return 1;
}    
#endif /* X_GRAPHICS */


/* XXX- shouldn't this reset the current video page to 0 ? 
*/

boolean set_video_mode(int mode) {
  int type=0;
  int old_video_mode,oldco;

#ifdef X_GRAPHICS
  if (Video == &Video_X) return X_set_video_mode(mode);
#endif  
  i10_msg("set_video_mode: mode 0x%02x\n",mode);
  
  oldco = co;
  old_video_mode = video_mode;
  video_mode=mode&0x7f;

  if (Video->setmode == 0)
    { 
      v_printf("video: no setmode handler!\n");
      goto error;
    }


  switch (mode&0x7f) {
  case 0x50:
  case 0x51:
  case 0x52:
    co=80;
    goto do_text_mode;
  case 0x53:
  case 0x54:
  case 0x55:
  case 0x56:
  case 0x57:
  case 0x58:
  case 0x59:
  case 0x5a:
    co=132;
    goto do_text_mode;
  
  case 0:
  case 1:
    /* 40 column modes... */
    co=40;
    goto do_text_mode;
    
  case 7:
    /*
     * This to be sure in case of older DOS programs probing HGC.
     * There was no secure way to detect a HGC before VGA was invented.
     * ( Now we can do INT 10, AX=1A00 ).
     * Some older DOS programs do it by modifying EQUIP-flags
     * and then let the BIOS say, if it can ?!?!)
     * If we have config.dualmon, this happens legaly.
     */
#if USE_DUALMON
    if (config.dualmon) {
      type=7;
      text_scanlines = 400;
      vga_font_height=text_scanlines/25;
    }
#endif
    /* fall through */
     
  case 2:
  case 3:
    /* set 80 column text mode */
#if USE_DUALMON
    WRITE_WORD(BIOS_SCREEN_COLUMNS, co = CO); /*80*/
#else
    co=80;
#endif
    
do_text_mode:
    li=text_scanlines/vga_font_height;
    if (li>MAX_LINES) li=MAX_LINES;
    WRITE_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1, li-1);
    WRITE_WORD(BIOS_FONT_HEIGHT, vga_font_height);
    WRITE_BYTE(BIOS_VIDEO_MODE, video_mode);
    Video->setmode(type,co,li);
    /* mode change clears screen unless bit7 of AL set */
    if (!(mode & 0x80))
       clear_screen(READ_BYTE(BIOS_CURRENT_SCREEN_PAGE), 7);

    if ( config.cardtype == CARD_MDA )
      mode = 7;
       
    break;


case 0x13:	/*Not finished ! */
case 0x5c:
case 0x5d:
case 0x5e:
case 0x62:
  /* 0x01 == GRAPH for us, but now it's sure! */
  if (Video->setmode != NULL)
  Video->setmode(0x01,0,0);
  break;

  default:
    /* handle graphics modes here */
    v_printf("undefined video mode 0x%x\n", mode);
  goto error;
  }

  WRITE_BYTE(BIOS_VIDEO_MODE, video_mode=mode&0x7f);

  if (oldco != co)
    WRITE_WORD(BIOS_SCREEN_COLUMNS, co);
  return 1;

error:
  /* don't change any state on failure */
  video_mode = old_video_mode;
  return 0;
}    

/* get the active and alternate display combination code */
static void get_dcc(int *active_dcc, int *alternate_dcc)
{
#if USE_DUALMON
  if (config.dualmon) {
    if (IS_SCREENMODE_MDA) {
      *active_dcc = MDA_VIDEO_COMBO;  /* active display */
      *alternate_dcc = video_combo;
    }
    else {
      *active_dcc = video_combo;     /* active display */
      *alternate_dcc = MDA_VIDEO_COMBO;
    }
    return;
  }
#endif
  *active_dcc = video_combo;	/* active display */
  *alternate_dcc = 0;		/* no inactive display */
}

/* INT 10 AH=1B - FUNCTIONALITY/STATE INFORMATION (PS,VGA/MCGA) */
static void return_state(Bit8u *statebuf) {
	int active_dcc, alternate_dcc;
	
#ifdef X_GRAPHICS
        if(!config.dualmon && config.X) {
	   WRITE_WORD(statebuf, vgaemu_bios.functionality - 0xc0000);
	   WRITE_WORD(statebuf + 2, 0xc000);
        }
#else
	memset(statebuf, 0, 4); /* XXX pointer to static functionality table */
#endif	
	/* store bios 0:449-0:466 at ofs 0x04 */
	memcpy(statebuf + 0x04, (char *)0x449, 0x466 - 0x449 + 1);
	/* store bios 0:484-0:486 at ofs 0x22 */
	memcpy(statebuf + 0x22, (char *)0x484, 0x486 - 0x484 + 1);
	/* correct number of rows-1 to number of rows at offset 0x22 */
	statebuf[0x22]++;
	get_dcc(&active_dcc, &alternate_dcc);
	statebuf[0x25] = active_dcc;
	statebuf[0x26] = alternate_dcc;
	statebuf[0x27] = 16;/* XXX number of colors, low byte */
	statebuf[0x28] = 0; /* XXX number of colors, high byte */
	statebuf[0x29] = 8; /* XXX number of pages supported */
	statebuf[0x2a] = 2; /* XXX number of scanlines 0-3=200,350,400,480 */
	statebuf[0x2b] = 0; /* XXX primary character block */
	statebuf[0x2c] = 0; /* XXX secondary character block */
	statebuf[0x31] = 3; /* video memory: 3 = 256K */
	statebuf[0x32] = 0; /* XXX save pointer state flags */
	memset(statebuf + 0x33, 0, 13);
}

/* helpers for font processing - eric@coli.uni-sb.de  11/2002 */
/* only for TEXT mode: Otherwise, int 0x43 is used...         */

#ifdef X_GRAPHICS
static void vga_RAM_to_RAM(unsigned height, unsigned char chr, unsigned count,
                           unsigned seg, unsigned ofs, int bank)
{
  char *dst;
  unsigned i;
  unsigned long src;
  unsigned bankofs;
  if (!count) {
    v_printf("Tried to load 0 characters of font data???\n");
    return;
  }
  src = seg;
  src <<= 4;
  src += ofs;
  bankofs = ((bank & 3) << 1) + ((bank & 4) >> 2);
  bankofs <<= 13; /* unit is 8k */
  i10_msg("load 8x%d font (char %d..%d) 0x%04x:0x%04x -> bank %d\n",
          height, chr, chr+count-1, seg, ofs, bank);
  dst = vga.mem.base + 0x20000 + bankofs;
  /* copy count characters of height bytes each to vga_font_mem */
  for(i = chr; i < chr + count; i++)
    MEMCPY_2UNIX(dst + i * 32, src + i * height, height);
}

static void vga_ROM_to_RAM(unsigned height, int bank)
{
  unsigned seg, ofs;
  seg = 0xc000;
  switch (height) { /* ALTERNATE ROM fonts not yet usable! */
  case 8:
    ofs = vgaemu_bios.font_8;
    break;
  case 14:
    ofs = vgaemu_bios.font_14;
    break;
  case 16:
    ofs = vgaemu_bios.font_16;
    break;
  default:
    v_printf("Error! Tried to load 8x%d ROM font!?\n",height);
    ofs = vgaemu_bios.font_16;
  }
  vga_RAM_to_RAM(height,0,256,seg,ofs,bank);
}
#endif /* X_GRAPHICS */ 

/******************************************************************/

/* the actual int10 handler */

int int10(void)
{
#if X_GRAPHICS
  if(!config.dualmon && config.X) {
    int10_new();
  } else
#endif
    int10_old();
  return 1;
}

void int10_old(void) /* with dualmon */
{
  /* some code here is copied from Alan Cox ***************/
  int x, y;
  unsigned int page;
  u_char c;
  us *sm;
  int co, li;

#if USE_DUALMON
  int virt_text_base = BIOS_SCREEN_BASE;
  static int last_equip=-1;

  if (config.dualmon && (last_equip != BIOS_CONFIG_SCREEN_MODE)) {
    v_printf("VID: int10 entry, equip-flags=0x%04x\n",READ_WORD(BIOS_CONFIGURATION));
    last_equip = BIOS_CONFIG_SCREEN_MODE;
    if (IS_SCREENMODE_MDA) Video->is_mapped = 1;
    else Video->is_mapped = Video_default->is_mapped;
  }
#endif

  li= READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1) + 1;
  co= READ_WORD(BIOS_SCREEN_COLUMNS);

  if (debug_level('v') >= 3)
    {
      if (debug_level('v') >= 4)
/*	dbug_printf("int10 near %04x:%08lx\n", LWORD(cs), REG(eip));*/
	dbug_printf("int10 near %04x:%08lx\n", READ_SEG_REG(cs), REG(eip));
      if ( (LO(ax) >= ' ') && (LO(ax) < 0x7f) )
	dbug_printf("int10 AH=%02x AL=%02x '%c'\n",
		    HI(ax), LO(ax), LO(ax));
      else
	dbug_printf("int10 AH=%02x AL=%02x\n", HI(ax), LO(ax));
    }

#if 0
  v_printf("VID: int10, ax=%04x bx=%04x\n",LWORD(eax),LWORD(ebx));
#endif

  switch (HI(ax)) {
  case 0x0:			/* define mode */
    v_printf("define mode: 0x%x\n", LO(ax));
    /* set appropriate font height for 25 lines */
    vga_font_height=text_scanlines/25;
    if (!set_video_mode(LO(ax))) {
       v_printf("int10,0: set_video_mode failed\n");
    }
    break;

  case 0x1:			/* define cursor shape */
    v_printf("define cursor: 0x%04x\n", LWORD(ecx));
    set_cursor_shape(LWORD(ecx));
    WRITE_WORD(BIOS_CURSOR_SHAPE, LWORD(ecx));
    break;
    
  case 0x2:			/* set cursor pos */
    page = HI(bx);
    x = LO(dx);
    y = HI(dx);
    v_printf("set cursor: pg:%d x:%d y:%d\n", page, x, y);
    if (page > 7) {
      v_printf("ERROR: video error (setcur/page>7: %d)\n", page);
      return;
    }
    if (x >= co || y >= li) {
      /* some apps use this to hide the cursor,
       * we move it 1 char behind the visible part
       */
      x = co;
      y = li -1;
    }

    set_bios_cursor_x_position(page, x);
    set_bios_cursor_y_position(page, y);
    cursor_col = x;
    cursor_row = y;
    break;

  case 0x3:			/* get cursor pos/shape */
    /* output start & end scanline even if the requested page is invalid */
    LWORD(ecx) = READ_WORD(BIOS_CURSOR_SHAPE);

    page = HI(bx);
    if (page > 7) {
      LWORD(edx) = 0;
      v_printf("ERROR: video error(0x3 page>7: %d)\n", page);
    } else {
      LO(dx) = get_bios_cursor_x_position(page);
      HI(dx) = get_bios_cursor_y_position(page);
    }
    break;

  case 0x5:
    {				/* change page */
#if USE_DUALMON
      if (config.dualmon && IS_SCREENMODE_MDA) break;
#endif
      page = LO(ax);
      v_printf("VID: change page from %d to %d!\n", READ_BYTE(BIOS_CURRENT_SCREEN_PAGE), page);
      if (page > max_page) {
	v_printf("ERROR: video error: set bad page %d\n", page);
	break;
      }
      if (config.console_video) set_vc_screen_page(page);
#if X_GRAPHICS
      if (config.X) vga_emu_set_text_page(page, TEXT_SIZE);
#endif

      WRITE_BYTE(BIOS_CURRENT_SCREEN_PAGE, video_page = page);
      WRITE_WORD(BIOS_VIDEO_MEMORY_ADDRESS, TEXT_SIZE * page);
      screen_adr = SCREEN_ADR(page);
      screen_mask = 1 << page;
      set_dirty(page);
      cursor_col = get_bios_cursor_x_position(page);
      cursor_row = get_bios_cursor_y_position(page);
      break;
    }

  case 0x6:			/* scroll up */
    v_printf("scroll up %d %d, %d %d, %d, %d\n", LO(cx), HI(cx), LO(dx), HI(dx), LO(ax), HI(bx));
    bios_scroll(LO(cx), HI(cx), LO(dx), HI(dx), LO(ax), HI(bx));
    break;

  case 0x7:			/* scroll down */
    v_printf("scroll dn %d %d, %d %d, %d, %d\n", LO(cx), HI(cx), LO(dx), HI(dx), LO(ax),HI(bx));
    bios_scroll(LO(cx), HI(cx), LO(dx), HI(dx), -LO(ax), HI(bx));
    break;

  case 0x8:			/* read character at x,y + attr */
    page = HI(bx);
    if (page > max_page) {
      v_printf("ERROR: read char from bad page %d\n", page);
      break;
    }
    sm = SCREEN_ADR(page);
    REG(eax) = sm[co * get_bios_cursor_y_position(page) 
		     + get_bios_cursor_x_position(page)];
    break;

    /* these two put literal character codes into memory, and do
       * not scroll or move the cursor...
       * the difference is that 0xA ignores color for text modes
       */
  case 0x9:
  case 0xA:
    {
      u_short *sadr;
      u_short c_attr;
      int n;

      page = HI(bx);
      sadr = (u_short *) SCREEN_ADR(page) 
	     + get_bios_cursor_y_position(page) * co
	     + get_bios_cursor_x_position(page);
      n = LWORD(ecx);
      c = LO(ax);

      /* XXX - need to make sure this doesn't overrun video memory!
       * we don't really know how large video memory is yet (I haven't
       * abstracted things yet) so we'll just leave it for the
       * Great Video Housecleaning foretold in the TODO file of the
       * ancients
       */
      if (HI(ax) == 9) {                /* use attribute from BL */
	 c_attr = c | LO(bx)<<8;
	 while(n--)
	    /* *(sadr++) = c_attr; */
	    WRITE_WORD(sadr++, c_attr);
      }
      else {                            /* leave attribute as it is */
	 while(n--)
	    /* *(char*)(sadr++) = c; */
	    WRITE_BYTE(sadr++, c);
      }
      set_dirty(page);
      break;
    }

  case 0x13:                    /* write string */
    {
      u_short *sadr;
      u_short attr;
      char *src;
      int n,x,y;

      page = HI(bx);
      x = LO(dx);
      y = HI(dx);
      sadr = (u_short *) SCREEN_ADR(page) + y*co + x;
      n = LWORD(ecx);
      src = SEG_ADR((char*),es,bp);
      
      if (LO(ax) < 2) {                  /* use attribute in BL */
        attr=LO(bx)<<8;
#if 1					/* 1998/03/13 sengoku@intsys.co.jp */
        x += n;
	while(n--) {
	  /* *(sadr++) = *src++ | attr; */
          if (*src <= 0x1a) {
            switch(*src) {
              case 0x0d :
                sadr = (u_short *) SCREEN_ADR(page) + y*co, x = n;
                src++;
                break;
              case 0x0a :
                sadr += co, x--, y++;
                src++;
                break;
              case 0x07 : /* beep */
                speaker_on(125, 0x637);
                x--;
                src++;
                break;
              case 0x08 : /* backspace */
                sadr--, x--;
                src++;
                break;
              default:
                WRITE_WORD(sadr++, READ_BYTE(src++) | attr);
                break;
            }
          } else {
            WRITE_WORD(sadr++, READ_BYTE(src++) | attr);
          }
        }
#else
	while(n--)
	  /* *(sadr++) = *src++ | attr; */
            WRITE_WORD(sadr++, READ_BYTE(src++) | attr);
#endif
      }
      else {                             /* use attributes in buffer */
	/*memcpy(sadr,src,n*2);*/
	MEMCPY_DOS2DOS(sadr,src,n*2);
      }

      if (LO(ax)&1) {                    /* update cursor position */
         n = x+co*y;
	 set_bios_cursor_x_position(page, n%co);
	 set_bios_cursor_y_position(page, n/co);
      }
      set_dirty(page);
    }
    break;

  case 0xe:			/* print char */
    char_out(LO(ax), READ_BYTE(BIOS_CURRENT_SCREEN_PAGE)); 
    break;

  case 0x0f:			/* get video mode */
    HI(ax) = co & 0xff;
#if USE_DUALMON
    if(IS_SCREENMODE_MDA)
      LO(ax) = READ_BYTE(BIOS_VIDEO_MODE);
    else 
#endif
    {
      unsigned m = video_mode & 0xff;

#if X_GRAPHICS
      if(config.X) {
        if(vga.mode & ~0xff) {
          /*
           * For VESA modes we look for an equivalent VGA mode number
           * and, if we don't have one, use 0x7f.
           */
          m = vga.VGA_mode == -1 ? 0x7f : vga.VGA_mode;
          if(vga.mode & 0x8000) m |= 0x80;
        }
      }
#endif
      LO(ax) = m;
      HI(bx) = READ_BYTE(BIOS_CURRENT_SCREEN_PAGE);
      v_printf(
        "INT10: get screen mode: mode = 0x%02x (%u colums, page %u)\n",
        (unsigned) LO(ax), (unsigned) HI(ax), (unsigned) HI(bx)
      );
    }
    break;

  case 0x10:			/* ega palette */
    /* Sets blinking or bold background mode.  This is important for 
     * PCTools type programs that uses bright background colors.
     */
    if(LO(ax) == 3) char_blink = LO(bx) & 1;

#if X_GRAPHICS
    /* root@zaphod */
    /* Palette register stuff. Only for the VGA emulator used by X */
    if(config.X) {
       int i, count;
       unsigned char *src;
       unsigned char index, m;
       DAC_entry rgb;

       switch(LO(ax)) {
         case 0x00:	/* Set Palette Register */
           Attr_set_entry(LO(bx), HI(bx));
           break;

         case 0x01:	/* Set Overscan Color */
           Attr_set_entry(0x11, HI(bx));
           break;

         case 0x02:	/* Set Palette & Overscan Color */
           src = SEG_ADR((unsigned char *), es, dx);
           for(i = 0; i < 0x10; i++) Attr_set_entry(i, src[i]);
           Attr_set_entry(0x11, src[i]);
           break;

         case 0x03:	/* Toggle Intensity/Blinking Bit */
           m = Attr_get_entry(0x10) & ~(1 << 3);
           m |= (LO(bx) & 1) << 3;
           Attr_set_entry(0x10, m);
           break;

         case 0x07:	/* Read Palette Register */
           HI(bx) = Attr_get_entry(LO(bx));
           break;

         case 0x08:	/* Read Overscan Color */
           HI(bx) = Attr_get_entry(0x11);
           break;

         case 0x09:	/* Read Palette & Overscan Color */
           src = SEG_ADR((unsigned char *), es, dx);
           for(i = 0; i < 0x10; i++) src[i] = Attr_get_entry(i);
           src[i] = Attr_get_entry(0x11);
           break;

         case 0x10:	/* Set Individual DAC Register */
           DAC_set_entry(LO(bx), HI(dx), HI(cx), LO(cx));
           break;

         case 0x12:	/* Set Block of DAC Registers */
           index = LO(bx);
           count = LWORD(ecx);
           src = SEG_ADR((unsigned char *), es, dx);
           for(i = 0; i < count; i++, index++)
             DAC_set_entry(index, src[3*i], src[3*i + 1], src[3*i + 2]);
           break;

         case 0x13:	/* Select Video DAC Color Page */
           m = Attr_get_entry(0x10);
           switch(LO(bx)) {
             case 0:	/* Select Page Mode */
               m &= ~(1 << 7);
               m |= (HI(bx) & 1) << 7;
               Attr_set_entry(0x10, m);
               break;
             case 1:	/* Select Page */
               if(m & (1 << 7))
                 Attr_set_entry(0x14, HI(bx) & 0xf);
               else
                 Attr_set_entry(0x14, (HI(bx) & 0x3) << 2);
               break;
           }
           break;

         case 0x15:	/* Read Individual DAC Register */
           rgb.index = LO(bx);
           DAC_get_entry(&rgb);
           HI(dx) = rgb.r; HI(cx) = rgb.g; LO(cx) = rgb.b;
           break;

         case 0x17:	/* Read Block of DAC Registers */
           index = LO(bx);
           count = LWORD(ecx);
           src = SEG_ADR((unsigned char *), es, dx);
           for(i = 0; i < count; i++, index++) {
             rgb.index = index;
             DAC_get_entry(&rgb);
             src[3*i] = rgb.r; src[3*i + 1] = rgb.g; src[3*i + 2] = rgb.b;
           }
           break;

         case 0x18:	/* Set PEL Mask */
           DAC_set_pel_mask(LO(bx));
           break;

         case 0x19:	/* Read PEL Mask */
           LO(bx) = DAC_get_pel_mask();
           break;

         case 0x1a:	/* Get Video DAC Color Page */
           LO(bx) = m = (Attr_get_entry(0x10) >> 7) & 1;
           HI(bx) = (Attr_get_entry(0x14) & 0xf) >> (m ? 0 : 2);
           break;

         case 0x1b:	/* Convert to Gray */
           for(index = LO(bx), count = LWORD(ecx); count--; index++)
             DAC_rgb2gray(index);
           break;

         default:
           break;
       }
    }
#endif
    break;

#if 0
  case 0xb:			/* palette */
  case 0xc:			/* set dot */
  case 0xd:			/* get dot */
    break;
#endif

  case 0x4:			/* get light pen */
    v_printf("ERROR: video error(no light pen)\n");
    HI(ax) = 0;   /* "light pen switch not pressed" */
                  /* This is how my VGA BIOS behaves [rz] */
    return;

  case 0x1a:			/* get display combo */
    if (LO(ax) == 0) {
      int active_dcc, alternate_dcc;
      v_printf("get display combo!\n");
      LO(ax) = 0x1a;		/* valid function=0x1a */
      get_dcc(&active_dcc, &alternate_dcc);
      LO(bx) = active_dcc;
      HI(bx) = alternate_dcc;
    }
    else {
      v_printf("set display combo not supported\n");
    }
    break;

  case 0x11:                    /* character generator functions */
    {
      int old_li = li;           /* preserve the state in case of error */
      int old_font_height = vga_font_height;

      v_printf("video character generator functions ax=0x%04x bx=0x%04x\n",
	       LWORD(eax), LWORD(ebx));
      switch (LO(ax)) {
      case 0x01:                  /* load 8x14 charset */
      case 0x11:
	vga_font_height = 14;
	goto more_lines;
	
      case 0x02:
      case 0x12:
	vga_font_height = 8;
	goto more_lines;
	
      case 0x04:
      case 0x14:
	vga_font_height = 16;
	goto more_lines;
	
	/* load a custom font */
	/* for now just ust it's size to set things */
      case 0x00:
      case 0x10:
	vga_font_height = HI(bx);
	v_printf("loaded font completely ignored except size!\n");
	/* the rest is ignored for now */
	goto more_lines;
	
      more_lines:
	{
	  if(!set_video_mode(video_mode | (1 << 16))) 
	    {
	      li = old_li; /* not that it changed */
	      vga_font_height = old_font_height;
	    }
#if 0
	  else if (old_li < li) 
	    {
	      /* The attribute is just like my Bios, weird! */
	      bios_scroll(0,old_li,co-1,li-1,0,07);
	    }
#endif
	  break;
	}
	
      case 0x30:     /* get current character generator info */
	LWORD(ecx)=vga_font_height;
	LO(dx)= READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1);
	/* LWORD(es)=LWORD(ebp)=0;*/  /* return NULL pointer */
	WRITE_SEG_REG(es, 0);   /* return NULL pointer */
	LWORD(ebp)= 0;        /* return NULL pointer */
      }
      break;
    }
      
  case 0x12:			/* video subsystem config */
    v_printf("video subsystem config ax=0x%04x bx=0x%04x\n",
	     LWORD(eax), LWORD(ebx));
    switch (LO(bx)) {
    case 0x10:
      HI(bx) = video_subsys;
      LO(bx)=3;
      v_printf("video subsystem 0x10 BX=0x%04x\n", LWORD(ebx));
      HI(cx)=0xf;  /* feature bits (no feature controller) */
      LO(cx)=READ_BYTE(BIOS_VIDEO_INFO_1) & 0xf;
      break;
    case 0x20:
      v_printf("select alternate printscreen\n");
      break;
    case 0x30:
      {  static int scanlines[3] = {200, 350, 400};
	 if ((unsigned)LO(ax)<3) {
	    text_scanlines = scanlines[LO(ax)];
            v_printf("select textmode scanlines: %d", text_scanlines);
	    LO(ax) = 0x12;  
	 }
      }
      break;
    case 0x32:          /* enable/disable cpu access to video ram */
      if (LO(ax) == 0)
	v_printf("disable cpu access to video!\n");
      else
	v_printf("enable cpu access to video!\n");
      break;
#if 0
    case 0x34:
      /* enable/disable cursor emulation */
      cursor_emulation = LO(ax);
      LO(ax)=0x12;
      break;
#endif
    case 0x36:          /* video screen ON/OFF */
      if (LO(ax) == 0)
	v_printf("turn video screen off!\n");
      else
	v_printf("turn video screen on!\n");
#if 0
      LO(ax)=0x12;  
#endif
      break;
      
    default:
      v_printf("ERROR: unrecognized video subsys config!!\n");
      show_regs(__FILE__, __LINE__);
    }
    break;
    
  case 0xcc:			/* called from NC 5.0 */
    _CX = 0; _AL = 0xff;
    break;

  case 0xfe:			/* get shadow buffer..return unchanged */
  case 0xff:			/* update shadow buffer...do nothing */
    break;

  case 0x1b:                    /* functionality/state information */
    if (LWORD(ebx) == 0) {
      v_printf("return functionality/state information");
      return_state(SEG_ADR((Bit8u *), es, di));
      LO(ax) = 0x1b;
    } else
      v_printf("unknown functionality/state request: %d", LWORD(ebx));
    break;
#if 0
  case 0x1c:                    /* return save/restore */
#endif

#if X_GRAPHICS
  case 0x4f:                    /* vesa interrupt */
    if(config.X)
      do_vesa_int();
    break;
#endif

  default:
    v_printf("new unknown video int 0x%x\n", LWORD(eax));
    break;
  }
}

#if X_GRAPHICS
void int10_new(void) /* with X but without dualmon */
{
  /* some code here is copied from Alan Cox ***************/
  int x, y, co, li;
  unsigned m, page;
  u_char c;
  us *sm;

  li= READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1) + 1;
  co= READ_WORD(BIOS_SCREEN_COLUMNS);

  if(HI(ax) > 0x0f)	// EVIL hack! Remove it later!!!
  if (debug_level('v') >= 3)
    {
      if (debug_level('v') >= 4)
	i10_msg("near %04x:%08lx\n", READ_SEG_REG(cs), REG(eip));
      if ( (LO(ax) >= ' ') && (LO(ax) < 0x7f) )
	i10_msg("AH=%02x AL=%02x '%c'\n",
		    HI(ax), LO(ax), LO(ax));
      else
	i10_msg("AH=%02x AL=%02x\n", HI(ax), LO(ax));
    }

#if 0
  i10_msg("ax %04x, bx %04x\n",LWORD(eax), LWORD(ebx));
#endif

  switch(HI(ax)) {
    case 0x00:		/* set video mode */
      i10_msg("set video mode: 0x%x\n", LO(ax));
      /* set appropriate font height for 25 lines */
      vga_font_height = text_scanlines / 25;	// ??? -> into set_video_mode()!
      if(!set_video_mode(LO(ax))) {
        i10_msg("set_video_mode() failed\n");
      }
      break;


    case 0x01:		/* set text cursor shape */
      i10_deb("set text cursor shape: %u-%u\n", HI(cx), LO(cx));
      set_cursor_shape(LWORD(ecx));
      WRITE_WORD(BIOS_CURSOR_SHAPE, LWORD(ecx));
      break;


    case 0x02:		/* set cursor pos */
      page = HI(bx);
      x = LO(dx);
      y = HI(dx);
      i10_deb("set cursor pos: page %d, x.y %d.%d\n", page, x, y);
      if(page > 7) {
        i10_msg("set cursor pos: page > 7: %d\n", page);
        return;
      }
      if (x >= co || y >= li) {
        /* some apps use this to hide the cursor,
         * we move it 1 char behind the visible part
         */
        x = co;
        y = li -1;
      }

      set_bios_cursor_x_position(page, x);
      set_bios_cursor_y_position(page, y);
      cursor_col = x;
      cursor_row = y;
      break;


    case 0x03:		/* get cursor pos/shape */
      /* output start & end scanline even if the requested page is invalid */
      LWORD(ecx) = READ_WORD(BIOS_CURSOR_SHAPE);

      page = HI(bx);
      if (page > 7) {
        LWORD(edx) = 0;
        i10_msg("get cursor pos: page > 7: %d\n", page);
      } else {
        LO(dx) = get_bios_cursor_x_position(page);
        HI(dx) = get_bios_cursor_y_position(page);
      }

      i10_deb(
        "get cursor pos: page %u, x.y %u.%u, shape %u-%u\n",
        page, LO(dx), HI(dx), HI(cx), LO(cx)
      );
      break;


    case 0x04:		/* read light pen pos */
      i10_msg("read light pen pos: NOT IMPLEMENTED\n");
      HI(ax) = 0;	/* "light pen switch not pressed" */
      			/* This is how my VGA BIOS behaves [rz] */
      break;


    case 0x05:		/* set active display page */
      page = LO(ax);
      i10_deb("set display page: from %d to %d\n", READ_BYTE(BIOS_CURRENT_SCREEN_PAGE), page);
      if(page > max_page) {
	i10_msg("set display page: bad page %d\n", page);
	break;
      }
      if (config.console_video) set_vc_screen_page(page);
      if (config.X) vga_emu_set_text_page(page, TEXT_SIZE);

      WRITE_BYTE(BIOS_CURRENT_SCREEN_PAGE, video_page = page);
      WRITE_WORD(BIOS_VIDEO_MEMORY_ADDRESS, TEXT_SIZE * page);
      screen_adr = SCREEN_ADR(page);
      screen_mask = 1 << page;
      set_dirty(page);		// ??? evil stuff: what about xdos?
      cursor_col = get_bios_cursor_x_position(page);
      cursor_row = get_bios_cursor_y_position(page);
      break;


    case 0x06:		/* scroll up */
      i10_deb(
        "scroll up: %u lines, area %u.%u-%u.%u, attr 0x%02x\n",
        LO(ax), LO(cx), HI(cx), LO(dx), HI(dx), HI(bx)
      );
      if(using_text_mode()) {
        bios_scroll(LO(cx), HI(cx), LO(dx), HI(dx), LO(ax), HI(bx));
      }
      else {
        vgaemu_scroll(LO(cx), HI(cx), LO(dx), HI(dx), LO(ax), HI(bx));
      }
      break;


    case 0x07:		/* scroll down */
      i10_deb(
        "scroll dn: %u lines, area %u.%u-%u.%u, attr 0x%02x\n",
        LO(ax), LO(cx), HI(cx), LO(dx), HI(dx), HI(bx)
      );
      if(using_text_mode()) {
        bios_scroll(LO(cx), HI(cx), LO(dx), HI(dx), -LO(ax), HI(bx));
      }
      else {
        vgaemu_scroll(LO(cx), HI(cx), LO(dx), HI(dx), -LO(ax), HI(bx));
      }
      break;


    case 0x08:		/* read character & attr at x,y */
      page = HI(bx);
      if (page > max_page) {
        i10_msg("read char: invalid page %d\n", page);
        break;
      }
      if(using_text_mode()) {
        sm = SCREEN_ADR(page);
        LWORD(eax) = sm[co * get_bios_cursor_y_position(page)
                   + get_bios_cursor_x_position(page)];
      }
      else {
        LWORD(eax) = 0;
      }
      i10_deb(
        "read char: char(%d.%d) = 0x%02x, attr 0x%02x\n",
        get_bios_cursor_x_position(page), get_bios_cursor_y_position(page),
        LO(ax), HI(ax)
      );
      break;


    case 0x09:		/* write char & attr */
    case 0x0a:		/* write char */
      if(HI(ax) == 0x0a && using_text_mode()) {
        i10_deb(
          "write char: page %u, char 0x%02x '%c'\n",
          HI(bx), LO(ax), LO(ax) > ' ' && LO(ax) < 0x7f ? LO(ax) : ' '
        );
      }
      else {
        i10_deb(
          "write char: page %u, char 0x%02x '%c', attr 0x%02x\n",
          HI(bx), LO(ax), LO(ax) > ' ' && LO(ax) < 0x7f ? LO(ax) : ' ', LO(bx)
        );
      }
      if(using_text_mode()) {
        u_short *sadr;
        u_short c_attr;
        int n;

        page = HI(bx);
        sadr = (u_short *) SCREEN_ADR(page) 
  	     + get_bios_cursor_y_position(page) * co
  	     + get_bios_cursor_x_position(page);
        n = LWORD(ecx);
        c = LO(ax);

        /* XXX - need to make sure this doesn't overrun video memory!
         * we don't really know how large video memory is yet (I haven't
         * abstracted things yet) so we'll just leave it for the
         * Great Video Housecleaning foretold in the TODO file of the
         * ancients
         */
        if(HI(ax) == 9) {		/* use attribute from BL */
  	 c_attr = c | (LO(bx) << 8);
  	 while(n--) WRITE_WORD(sadr++, c_attr);
        }
        else {				/* leave attribute as it is */
  	 while(n--) WRITE_BYTE(sadr++, c);
        }
        set_dirty(page);
        break;
      }
      else {
        unsigned
          x = get_bios_cursor_x_position(0),
          y = get_bios_cursor_y_position(0),
          n = LWORD(ecx);

        while(x < vga.text_width && n--)
          vgaemu_put_char(x++, y, LO(ax), LO(bx));
      }
      break;


    case 0x0b:         /* set palette/bg/border color */
      {
       unsigned char currentpalette = READ_BYTE(BIOS_VDU_COLOR_REGISTER);
       i10_msg("set palette or bg/border, bx=%x\n",LWORD(ebx));
       if (HI(bx) == 0) {
         currentpalette &= ~0x1f;
         currentpalette |= LO(bx) & 0x1f;
       } else if (HI(bx) == 1) {
         if (LO(bx))
           currentpalette |= 0x20;
         else
           currentpalette &= ~0x20;
       } else {
         break;
       }
       Misc_set_color_select(currentpalette);
       WRITE_BYTE(BIOS_VDU_COLOR_REGISTER, currentpalette);
      }
      break;

    case 0x0c:		/* write pixel */
      if(!using_text_mode())
        vgaemu_put_pixel(LWORD(ecx), LWORD(edx), HI(bx), LO(ax));
      break;


    case 0x0d:		/* read pixel */
      LO(ax) = 0;
      if(!using_text_mode()) {
        LO(ax) = vgaemu_get_pixel(LWORD(ecx), LWORD(edx), HI(bx));
        i10_msg("read pixel: 0x%02x\n", LO(ax));
      }
      break;

    case 0x0e:		/* print char */
      if(using_text_mode()) {
        i10_deb(
          "tty put char: page %u, char 0x%02x '%c'\n",
          HI(bx), LO(ax), LO(ax) > ' ' && LO(ax) < 0x7f ? LO(ax) : ' '
        );
        char_out(LO(ax), READ_BYTE(BIOS_CURRENT_SCREEN_PAGE)); 
      }
      else {
        i10_deb(
          "tty put char: page %u, char 0x%02x '%c', attr 0x%02x\n",
          HI(bx), LO(ax), LO(ax) > ' ' && LO(ax) < 0x7f ? LO(ax) : ' ', LO(bx)
        );
        tty_char_out(LO(ax), READ_BYTE(BIOS_CURRENT_SCREEN_PAGE), LO(bx)); 
      }
      break;


    case 0x0f:		/* get video mode */
      m = video_mode & 0xff;
      if(vga.mode & ~0xff) {
        /*
         * For VESA modes we look for an equivalent VGA mode number
         * and, if we don't have one, use 0x7f.
         */
        m = vga.VGA_mode == -1 ? 0x7f : vga.VGA_mode;
        if(vga.mode & 0x8000) m |= 0x80;
      }

      HI(ax) = co & 0xff;
      LO(ax) = m;
      HI(bx) = READ_BYTE(BIOS_CURRENT_SCREEN_PAGE);
      i10_deb(
        "get video mode: mode 0x%02x, page %u, %u colums\n",
        LO(ax), HI(bx), HI(ax)
      );
      break;


    case 0x10:		/* ega/vga palette functions */
      i10_deb("ega/vga palette: sub function 0x%02x\n", LO(ax));

      /* Sets blinking or bold background mode.  This is important for 
       * PCTools type programs that uses bright background colors.
       */
      if(LO(ax) == 3) char_blink = LO(bx) & 1;

#if X_GRAPHICS
      /* root@zaphod */
      /* Palette register stuff. Only for the VGA emulator used by X */
      if(config.X) {
         int i, count;
         unsigned char *src;
         unsigned char index, m;
         DAC_entry rgb;

         switch(LO(ax)) {
           case 0x00:	/* Set Palette Register */
             Attr_set_entry(LO(bx), HI(bx));
             break;

           case 0x01:	/* Set Overscan Color */
             Attr_set_entry(0x11, HI(bx));
             break;

           case 0x02:	/* Set Palette & Overscan Color */
             src = SEG_ADR((unsigned char *), es, dx);
             for(i = 0; i < 0x10; i++) Attr_set_entry(i, src[i]);
             Attr_set_entry(0x11, src[i]);
             break;

           case 0x03:	/* Toggle Intensity/Blinking Bit */
             m = Attr_get_entry(0x10) & ~(1 << 3);
             m |= (LO(bx) & 1) << 3;
             Attr_set_entry(0x10, m);
             break;

           case 0x07:	/* Read Palette Register */
             HI(bx) = Attr_get_entry(LO(bx));
             break;

           case 0x08:	/* Read Overscan Color */
             HI(bx) = Attr_get_entry(0x11);
             break;

           case 0x09:	/* Read Palette & Overscan Color */
             src = SEG_ADR((unsigned char *), es, dx);
             for(i = 0; i < 0x10; i++) src[i] = Attr_get_entry(i);
             src[i] = Attr_get_entry(0x11);
             break;

           case 0x10:	/* Set Individual DAC Register */
             DAC_set_entry(LO(bx), HI(dx), HI(cx), LO(cx));
             break;

           case 0x12:	/* Set Block of DAC Registers */
             index = LO(bx);
             count = LWORD(ecx);
             src = SEG_ADR((unsigned char *), es, dx);
             for(i = 0; i < count; i++, index++)
               DAC_set_entry(index, src[3*i], src[3*i + 1], src[3*i + 2]);
             break;

           case 0x13:	/* Select Video DAC Color Page */
             m = Attr_get_entry(0x10);
             switch(LO(bx)) {
               case 0:	/* Select Page Mode */
                 m &= ~(1 << 7);
                 m |= (HI(bx) & 1) << 7;
                 Attr_set_entry(0x10, m);
                 break;
               case 1:	/* Select Page */
                 if(m & (1 << 7))
                   Attr_set_entry(0x14, HI(bx) & 0xf);
                 else
                   Attr_set_entry(0x14, (HI(bx) & 0x3) << 2);
                 break;
             }
             break;

           case 0x15:	/* Read Individual DAC Register */
             rgb.index = LO(bx);
             DAC_get_entry(&rgb);
             HI(dx) = rgb.r; HI(cx) = rgb.g; LO(cx) = rgb.b;
             break;

           case 0x17:	/* Read Block of DAC Registers */
             index = LO(bx);
             count = LWORD(ecx);
             src = SEG_ADR((unsigned char *), es, dx);
             for(i = 0; i < count; i++, index++) {
               rgb.index = index;
               DAC_get_entry(&rgb);
               src[3*i] = rgb.r; src[3*i + 1] = rgb.g; src[3*i + 2] = rgb.b;
             }
             break;

           case 0x18:	/* Set PEL Mask */
             DAC_set_pel_mask(LO(bx));
             break;

           case 0x19:	/* Read PEL Mask */
             LO(bx) = DAC_get_pel_mask();
             break;

           case 0x1a:	/* Get Video DAC Color Page */
             LO(bx) = m = (Attr_get_entry(0x10) >> 7) & 1;
             HI(bx) = (Attr_get_entry(0x14) & 0xf) >> (m ? 0 : 2);
             break;

           case 0x1b:	/* Convert to Gray */
             for(index = LO(bx), count = LWORD(ecx); count--; index++)
               DAC_rgb2gray(index);
             break;

           default:
             i10_msg("ega/vga palette: invalid sub function 0x%02x\n", LO(ax));
             break;
         }
      }
#endif
      break;


    case 0x11:		/* character generator functions */
      {
        int old_li = li;	/* preserve the state in case of error */
        int old_font_height = vga_font_height;
        unsigned ofs, seg;
        unsigned rows, char_height;

        i10_deb("char gen: func 0x%02x, bx 0x%04x\n", LO(ax), LWORD(ebx));

        switch(LO(ax)) {
          case 0x03:
            /* For EGA: Select fonts xx and yy for attrib bit 3 1/0 */
            /* For VGA: Same, but using Xxx and Yyy. BL is 00XYxxyy */
            /* ***          see also vgaemu_put_char()         ***  */
            /* the X/Y bits mean 8k offset, xx/yy use 16k units     */
            /* see: sequencer, map select, data[3] ... sequemu.c    */
            Seq_set_index(3);
            Seq_write_value(LO(bx));
            i10_msg("sequencer char map select: 0x%02x\n", LO(bx));
            break;

          case 0x01:		/* load 8x14 charset */
          case 0x11:
            vga_font_height = 14;
	    vga_ROM_to_RAM(14, LO(bx));
            goto more_lines;

          case 0x02:		/* load 8x8 charset */
          case 0x12:
            vga_font_height = 8;
	    vga_ROM_to_RAM(8, LO(bx));
            goto more_lines;

          case 0x04:		/* load 8x16 charset */
          case 0x14:
            vga_font_height = 16;
	    vga_ROM_to_RAM(16, LO(bx));
            goto more_lines;

          /* load a custom font */
          case 0x00:
          case 0x10:
            vga_font_height = HI(bx);
            /* *************************************************************
             * func 00 would not change as much as func 0x10, which would  *
             * reprogram registers 9 = bh-1 (mode 7 only, max scan line),  *
             * a = bh-2 (cursor start), b = 0 (cursor end),                *
             * 12 = (rows+1) / (bh-1) (vertical display end),              *
             * 14 = bh-1 (underline loc). Recalcluates CRT buffer length.  *
             * Max character rows is also recalculated, as well as by/char *
             * ... and page 0 must be active                               *
             * ES:BP -> table CX = count of chars DX = from which char on  *
             * BL = which block to load into map2 BH = bytes / char        *
             ************************************************************* */
            vga_RAM_to_RAM(HI(bx), LO(dx), LWORD(ecx),
                REG(es), LWORD(ebp), LO(bx));
            i10_msg("some user font data loaded\n");
            goto more_lines;

          more_lines:
            /* Also activating the target bank - some programs */
            /* seem to assume this. Sigh...                    */
            
            /* FIXME: only useful when using VGAEMU, add a #define   */
            Seq_set_index(3);     /* sequencer: character map select */
            /* available: x, y, c...!?    transforming bitfields...  */
            x = (LO(bx) & 3) | ((LO(bx) & 4) << 2);
            x |= ((LO(bx) & 3) << 2) | ((LO(bx) & 4) << 3);
            Seq_write_value(x);         /* set bank N for both fonts */
            i10_msg("activated font bank %d (0x%02x)\n",
                (LO(bx) & 7), x);
            /* | (1 << 16) is "only update font stuff" */
            if(!set_video_mode(video_mode | (1 << 16))) {
              li = old_li;	/* not that it changed */
              v_printf("Problem changing font height %d->%d\n",
                  old_font_height, vga_font_height);
              vga_font_height = old_font_height;
            }
            WRITE_WORD(BIOS_FONT_HEIGHT, vga_font_height);
            break;

          case 0x20:		/* set 8x8 gfx chars */
            SETIVEC(0x1f, REG(es), LWORD(ebp));
            i10_deb("set 8x8 gfx chars: addr 0x%04x:0x%04x\n", ISEG(0x1f), IOFF(0x1f));
            break;

          case 0x21:		/* load user gfx chars */
          case 0x22:		/* load 14x8 gfx chars */
          case 0x23:		/* load 8x8 gfx chars */
          case 0x24:		/* load 16x8 gfx chars */
          /* BL=0/1/2/3 for DL/14/25/43 rows                   */
          /* CX is byte / char, ES:BP is pointer for case 0x21 */
            seg = 0xc000;
            switch(LO(ax)) {
              case 0x21:
                ofs = LWORD(ebp);
                seg = REG(es);
                char_height = LWORD(ecx);
                break;
              case 0x22:
                ofs = vgaemu_bios.font_14;
                char_height = 14;
                break;
              case 0x23:
                ofs = vgaemu_bios.font_8;
                char_height = 8;
                break;
              default:		/* case 0x24 */
                ofs = vgaemu_bios.font_16;
                char_height = 16;
                break;
            }
            rows = LO(dx);
            switch(LO(bx)) {
              case 1:
                rows = 14;
                break;
              case 2:
                rows = 25;
                break;
              case 3:
                rows = 43;
                break;
            }
            SETIVEC(0x43, seg, ofs);
            WRITE_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1, rows - 1);
            WRITE_WORD(BIOS_FONT_HEIGHT, char_height);
            i10_deb(
              "load gfx font: height %u, rows %u, addr 0x%04x:0x%04x\n",
              char_height, rows, seg, ofs
            );
            break;

          case 0x30:		/* get current character generator info */
            LWORD(ecx) = vga_font_height;
            LO(dx) = READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1);
            seg = 0xc000;
            switch((HI(bx))) {
              case 0:
                ofs = IOFF(0x1f);
                seg = ISEG(0x1f);
                break;
              case 1:
                ofs = IOFF(0x43);
                seg = ISEG(0x43);
                break;
              case 2:
                ofs = vgaemu_bios.font_14;
                break;
              case 3:
                ofs = vgaemu_bios.font_8;
                break;
              case 4:
                ofs = vgaemu_bios.font_8 + 128 * 8;
                break;
              case 5:
                ofs = vgaemu_bios.font_14_alt;
                break;
              case 6:
                ofs = vgaemu_bios.font_16;
                break;
              case 7:
                ofs = vgaemu_bios.font_16_alt;
                break;
              default:
                seg = ofs = 0;
            }
            LWORD(ebp) = ofs;
            REG(es) = seg;
            i10_deb(
              "font info: char height: %u, rows %u, font 0x%x, addr 0x%04x:0x%04x\n",
              LWORD(ecx), LO(dx) + 1, HI(bx), REG(es), LWORD(ebp)
            );
            break;

          default:
            i10_msg("char gen: func 0x%02x NOT IMPLEMENTED\n", LO(ax));
        }
      }
      break;


    case 0x12:		/* alternate function select */
      switch(LO(bx)) {
        case 0x10:	/* get ega info */
          HI(bx) = video_subsys;
          LO(bx) = 3;
          HI(cx)=0xf;	/* feature bits (no feature controller) */
          LO(cx)=READ_BYTE(BIOS_VIDEO_INFO_1) & 0xf;
          i10_deb("get ega info: bx 0x%04x, cx 0x%04x\n", LWORD(ebx), LWORD(ecx));
          break;

        case 0x20:	/* select alternate print screen */
          i10_deb("select alternate prtsc: NOT IMPLEMENTED\n");
          break;

        case 0x30:	/* select vertical resolution */
          {
            static int scanlines[4] = {200, 350, 400, 480};
            /* 480 (undocumented) (int 0x10 AH=12 BL=30)          */
            /* "will take effect on the next mode set" is         */
            /* not yet implemented                                */
            if((unsigned) LO(ax) < 4) {
              text_scanlines = scanlines[LO(ax)];
              LO(ax) = 0x12;  
              i10_deb("select vert res: %d lines", text_scanlines);
            }
            else {
              i10_deb("select vert res: invalid arg 0x%02x", LO(ax));
            }
          }
          break;

        case 0x32:	/* enable/disable cpu access to video ram */
          if(LO(ax) == 0)
            i10_deb("disable cpu access to video (ignored)\n");
          else
            i10_deb("enable cpu access to video (ignored)\n");
          break;

#if 0
        case 0x34:	/* enable/disable cursor emulation */
          cursor_emulation = LO(ax);
          LO(ax) = 0x12;
          i10_deb("cursor emulation: %s\n", cursor_emulation & 1 ? "disabled" : "enabled");
          break;
#endif

        case 0x36:	/* video screen on/off */
          /* VGAEMU oriented (do port 3c0 flag as well?)            */
          /* (probably not: port 0x3c0|=0x20 -> all overscan color) */
          /* FIXME: only useful when using VGAEMU, add a #define    */
          /* Will influence vga.config.video_off -> X_update_screen */
          Seq_set_index(1);             /* sequencer: clocking mode */
          if(LO(ax) == 0) {
            i10_deb("turn video screen on (partially ignored)\n");
             Seq_write_value(vga.seq.data[1] & ~0x20);
             /* bit 0x20 is screen refresh off */
          } else {
             i10_deb("turn video screen off (partially ignored)\n");
             Seq_write_value(vga.seq.data[1] | 0x20);
             /* bit 0x20 is screen refresh off */
          }
#if 0
          LO(ax) = 0x12;  
#endif
          break;

        default:
          i10_msg("video subsys config: function 0x%02x NOT IMPLEMENTED\n", LO(bx));
      }
      break;


    case 0x13:		/* write string */
      {
        int with_attr = (LO(ax) & 2) >> 1;
        unsigned page = HI(bx);
        unsigned char attr = LO(bx);
        unsigned len = LWORD(ecx);
        unsigned char *str = SEG_ADR((unsigned char *), es, bp);
        unsigned old_x, old_y;
        int old_cursor_col, old_cursor_row;

        old_x = get_bios_cursor_x_position(page);
        old_y = get_bios_cursor_y_position(page);
        old_cursor_col = cursor_col;
        old_cursor_row = cursor_row;

        set_bios_cursor_x_position(page, cursor_col = LO(dx));
        set_bios_cursor_y_position(page, cursor_row = HI(dx));

        i10_deb(
          "write string: page %u, x.y %d.%d, attr 0x%02x, len %u, addr 0x%04x:0x%04x\n",
          page, cursor_col, cursor_row, attr, len, REG(es), LWORD(ebp)
        );
#if DEBUG_INT10
        i10_deb("write string: str \"");
        {
          unsigned u;

          for(u = 0; u < len; u++)
            v_printf("%c", str[u] >= ' ' && str[u] < 0x7f ? str[u] : ' ');
          v_printf("\"\n");
        }
#endif

        if(with_attr) {
          while(len--) {
            tty_char_out(str[0], page, str[1]);
            str += 2;
          }
        }
        else {
          while(len--) tty_char_out(*str++, page, attr);
        }

        if(!(LO(ax) & 1)) {	/* no cursor update */
          set_bios_cursor_x_position(page, old_x);
          set_bios_cursor_y_position(page, old_y);
          cursor_col = old_cursor_col;
          cursor_row = old_cursor_row;
        }
      }
      break;

    case 0x1a:		/* get/set display combo */
      if(LO(ax) == 0) {
        int active_dcc, alternate_dcc;

        LO(ax) = 0x1a;		/* valid function=0x1a */
        get_dcc(&active_dcc, &alternate_dcc);
        LO(bx) = active_dcc;
        HI(bx) = alternate_dcc;
        i10_deb("get display combo: active 0x%02x, alternate 0x%2x\n", LO(bx), HI(bx));
      }
      else {
        i10_msg("set display combo: NOT IMPLEMENTED\n");
      }
      break;


    case 0x1b:		/* functionality/state information */
      if(LWORD(ebx) == 0) {
        i10_deb("get functionality/state info\n");
        return_state(SEG_ADR((Bit8u *), es, di));
        LO(ax) = 0x1b;
      } else {
        i10_msg("unknown functionality/state request: 0x%04x", LWORD(ebx));
      }
      break;


    case 0x1c:		/* save/restore video state */
      i10_msg("save/restore: NOT IMPLEMETED\n");
      break;


#if X_GRAPHICS
    case 0x4f:		/* vesa interrupt */
      if(config.X) do_vesa_int();
      break;
#endif


    case 0xcc:		/* called from NC 5.0 */
      _CX = 0; _AL = 0xff;
      i10_deb("obscure function 0x%02x\n", HI(ax));
      break;


    case 0xfe:		/* get shadow buffer..return unchanged */
    case 0xff:		/* update shadow buffer...do nothing */
      i10_deb("obscure function 0x%02x\n", HI(ax));
      break;


    default:
      i10_msg("unknown video int 0x%04x\n", LWORD(eax));
      break;
  }
}
#endif	/* X_GRAPHICS */

