/*
 * 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 compatible) 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: Split 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 <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>

#include "emu.h"
#include "video.h"
#include "bios.h"
#include "int.h"
#include "port.h"
#include "speaker.h"
#include "utilities.h"
#include "dos2linux.h"
#include "timers.h"
#include "vgaemu.h"
#include "vgatext.h"

/*
 * Activate some debug output.
 */
#define DEBUG_INT10	1
#define BIOS_CONFIG_SCREEN_MODE (READ_WORD(BIOS_CONFIGURATION) & 0x30)
#define IS_SCREENMODE_MDA (BIOS_CONFIG_SCREEN_MODE == 0x30)

static const int max_page = 7;
int video_mode;
int video_combo;

unsigned screen_adr(int page)
{
  /* 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)
   */
  unsigned base = IS_SCREENMODE_MDA ? MDA_VIRT_TEXT_BASE : VGA_VIRT_TEXT_BASE;
  return base + page * READ_WORD(BIOS_VIDEO_MEMORY_USED);
}

/* 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 Compatibility 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 vga_ROM_to_RAM(unsigned height, int bank);

static void crt_outw(unsigned index, unsigned value)
{
  unsigned port = READ_WORD(BIOS_VIDEO_PORT);
  port_outw(port, index | (value & 0xff00));
  port_outw(port, (index + 1) | ((value & 0xff) << 8));
}

static unsigned do_set_cursor_pos(unsigned page, int x, int y)
{
  unsigned co, old_y;

  old_y = get_bios_cursor_y_position(page);
  set_bios_cursor_x_position(page, x);
  set_bios_cursor_y_position(page, y);
  co = READ_WORD(BIOS_SCREEN_COLUMNS);
  crt_outw(0xe, READ_WORD(BIOS_VIDEO_MEMORY_ADDRESS)/2 + y * co + x);
  return old_y;
}

static void set_cursor_pos(unsigned page, int x, int y)
{
  unsigned old_y = do_set_cursor_pos(page, x, y);

  if (config.dumb_video && y > old_y) {
    int i;

    if (no_local_video && !config.tty_stderr)
      return;
    for (i = 0; i < y - old_y; i++)
      fputs("\r\n", config.tty_stderr ? stderr : stdout);
  }
}

static void set_cursor_shape(uint16_t shape)
{
   int cs,ce;
   cshape cursor_shape;
   cursor_shape.w = shape;

   WRITE_WORD(BIOS_CURSOR_SHAPE, cursor_shape.w);

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

   if (cursor_shape.w & 0x6000 || cs>ce) {
      i10_deb("no cursor\n");
      crt_outw(0xa, NO_CURSOR);
      return;
   }

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

/* 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)
 */
static void
bios_scroll(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;
  uint16_t blank = ' ' | (att << 8);
  uint16_t tbuf[MAX_COLUMNS];
  unsigned sadr;

  if (config.dumb_video)
     return;

  li= READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1) + 1;
  co= READ_WORD(BIOS_SCREEN_COLUMNS);
  sadr = screen_adr(0) + READ_WORD(BIOS_VIDEO_MEMORY_ADDRESS);

  if (sadr < VGA_PHYS_TEXT_BASE && ((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;

  if (l == 0) {			/* Wipe mode */
    for (y = y0; y <= y1; y++)
      memcpy_to_vga(sadr + 2 * (y * co + x0), tbuf, dx * 2);
    return;
  }

  if (l > 0) {
    if (dx == co)
      vga_memcpy(sadr + 2 * y0 * co, sadr + 2 * (y0 + l) * co, (dy - l) * dx * 2);
    else
      for (y = y0; y <= (y1 - l); y++)
	vga_memcpy(sadr + 2 * (y * co + x0), sadr + 2 * ((y + l) * co + x0), dx * 2);

    for (y = y1 - l + 1; y <= y1; y++)
      memcpy_to_vga(sadr + 2 * (y * co + x0), tbuf, dx * 2);
  }
  else {
    for (y = y1; y >= (y0 - l); y--)
      vga_memcpy(sadr + 2 * (y * co + x0), sadr + 2 * ((y + l) * co + x0), dx * 2);

    for (y = y0 - l - 1; y >= y0; y--)
      memcpy_to_vga(sadr + 2 * (y * co + x0), tbuf, dx * 2);
  }
}

static int using_text_mode(void)
{
  unsigned char mode = READ_BYTE(BIOS_VDU_CONTROL);
  return (!(mode & 2));
}

static int using_mono_mode(void)
{
  return ((READ_BYTE(BIOS_VDU_CONTROL) & 0xc) == 0xc);
}

/*
 * 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 xpos, ypos, co, li;
  int gfx_mode = 0;
  unsigned dst;

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

  if (config.dumb_video) {
    struct char_set_state term_state;
    t_unicode uni = dos_to_unicode_table[ch];
    unsigned char buff[MB_LEN_MAX + 1];
    int num, i;

    if (no_local_video && !config.tty_stderr)
      return;

    init_charset_state(&term_state, trconfig.output_charset);
    num = unicode_to_charset(&term_state, uni, buff, MB_LEN_MAX);
    if (num <= 0)
      return;
    for (i = 0; i < num; i++)
      fputc(buff[i], config.tty_stderr ? stderr : stdout);
  }

  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(!using_text_mode()) gfx_mode = 1;

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

  case '\n':         /* Newline */
    ypos++;
    break;

  case 8:           /* Backspace */
    if (xpos > 0) xpos--;
    break;

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

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

  default:          /* Printable character */
    if(gfx_mode) {
      vgaemu_put_char(ch, s, attr);
    }
    else
    {
      dst = screen_adr(s) + 2 * (ypos*co + xpos);
      WRITE_BYTE(dst, ch);
      if(attr != -1) WRITE_BYTE(dst + 1, attr);
    }
    xpos++;
  }

  if (xpos == co) {
    xpos = 0;
    ypos++;
  }
  if (ypos == li) {
    ypos--;
    if(gfx_mode)
      vgaemu_scroll(0, 0, co - 1, li - 1, 1, 0);
    else {
      /* Scroll with color newline */
      bios_scroll(0,0,co-1,li-1,1,
		  vga_read(screen_adr(s) + 2*(ypos*co + xpos) + 1));
    }
  }
  do_set_cursor_pos(s, xpos, 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.
 */
static void clear_screen(void)
{
  unsigned schar;
  u_short blank = ' ' | (7 << 8);
  int lx, s;

  if (config.dumb_video)
     return;

  v_printf("INT10: cleared screen: screen_adr %x\n", screen_adr(0));

  for (schar = screen_adr(0), lx = 0; lx < 16*1024;
       WRITE_WORD(schar, blank), lx++, schar+=2);

  for (s = 0; s < 8; s++)
    do_set_cursor_pos(s, 0, 0);
}

/* return number of vertical scanlines based on the bytes at
   40:88 and 40:89 */
static int get_text_scanlines(void)
{
  int info = READ_BYTE(BIOS_VIDEO_INFO_2);
  v_printf("scanlines=%x\n", info);
  if ((info & 0x80) && !(info & 0x10))
    return 200;
  if (!(info & 0x80) && !(info & 0x10))
    return 350;
  if (!(info & 0x80))
    return 400;
  return 480;
}

/* set number of vertical scanlines at the bytes at
   40:88 and 40:89 */
static void set_text_scanlines(int lines)
{
  int info = READ_BYTE(BIOS_VIDEO_INFO_2) & ~0x90;
  if (lines == 200)
    info |= 0x80;
  else {
    if (lines == 400)
      info |= 0x10;
    else if (lines == 480)
      info |= 0x90;
  }
  v_printf("scanlines=%x %d\n", info, lines);
  WRITE_BYTE(BIOS_VIDEO_INFO_2, info);
}

static int adjust_font_size(int vga_font_height)
{
  /* RBIL says:
     Recalculate: BIOS_FONT_HEIGHT, BIOS_ROWS_ON_SCREEN_MINUS_1, and
     BIOS_VIDEO_MEMORY_USED.
     Update CRTC registers 9 (for font height), A/B (cursor), 12 (display end),
     14 (underline location) */
  int li, text_scanlines;
  ioport_t port;

  if(vga_font_height == 0)
    return 0;

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

  text_scanlines = (READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1) + 1) *
    READ_WORD(BIOS_FONT_HEIGHT);
  if (text_scanlines <= 200) text_scanlines = 200;
  else if (text_scanlines <= 350) text_scanlines = 350;
  else if (text_scanlines <= 400) text_scanlines = 400;
  else text_scanlines = 480;
  li = text_scanlines / vga_font_height;
  if (li > MAX_LINES)
    return 0;
  text_scanlines = li * vga_font_height;

  port = READ_WORD(BIOS_VIDEO_PORT);
  port_outw(port, 0x12 | (((text_scanlines-1) & 0xff) << 8));
  port_outb(port, 0x14);
  port_outb(port + 1, (port_inb(port + 1) & ~0x1f) + vga_font_height);
  port_outb(port, 9);
  port_outb(port + 1, (port_inb(port + 1) & ~0x1f) + vga_font_height -1);
  WRITE_WORD(BIOS_FONT_HEIGHT, vga_font_height);

  if (using_mono_mode())
    set_cursor_shape(0x0b0d);
  else
    set_cursor_shape(0x0607);

  if (Video->setmode != NULL) {
    /* otherwise we can't change li */
    WRITE_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1, li - 1);
    WRITE_WORD(BIOS_VIDEO_MEMORY_USED,
	       TEXT_SIZE(READ_WORD(BIOS_SCREEN_COLUMNS), li));
  }
  return 1;
}

/*
 * 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
 */

bool set_video_mode(int mode)
{
  vga_mode_info *vmi;
  int clear_mem = 1;
  unsigned u;
  int co, li, vga_font_height, orig_mode;
  ioport_t port;

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

  i10_msg("set_video_mode: mode 0x%02x\n", mode);

  if((vmi = vga_emu_find_mode(mode, NULL)) == NULL) {
    i10_msg("set_video_mode: undefined video mode\n");
    return 0;
  }
  if (vmi->mode_class == TEXT) {
    vga_mode_info *vmi2 = vmi;
    vga_mode_info *vmi_best = vmi;
    int ts = get_text_scanlines();
    int delta = ts - vmi2->height;

    i10_msg("look for mode with scan=%i font_height=%i\n",
        ts, READ_BYTE(BIOS_FONT_HEIGHT));
    while ((vmi2 = vga_emu_find_mode(mode, vmi2))) {
      if (vmi2->height > ts ||
          vmi2->char_height != READ_BYTE(BIOS_FONT_HEIGHT))
        continue;
      if (delta < 0 || ts - vmi2->height < delta) {
        i10_msg("better mode found: %ix%i %ix%i scan=%i\n",
            vmi2->text_width, vmi2->text_height,
            vmi2->char_width, vmi2->char_height,
            vmi2->height);
        vmi_best = vmi2;
        delta = ts - vmi2->height;
      }
      vmi = vmi_best;
    }
  }
  if (vmi->mode_class == GRAPH && config.term) {
    error("Cannot set graphics mode under terminal!\n");
    return 0;
  }
  if (!memcheck_is_reserved(vmi->buffer_start << 4, 0x8000, 'v')) {
    error("VGA: cannot set mode %i because of UMB at 0x%x\n",
	    mode, vmi->buffer_start);
    return 0;
  }

  orig_mode = mode;

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

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

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

  if(vmi->type == TEXT_MONO) {
    WRITE_BYTE(BIOS_CONFIGURATION, READ_BYTE(BIOS_CONFIGURATION) | 0x30);
    port = 0x3b4;
  } else {
    WRITE_BYTE(BIOS_CONFIGURATION,
	       (READ_BYTE(BIOS_CONFIGURATION) & ~0x30) | 0x20);
    port = 0x3d4;
  }
  WRITE_WORD(BIOS_VIDEO_PORT, port);

  /*
   * 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);
  if (mode == 3)
    WRITE_BYTE(BIOS_VDU_CONTROL, 9);
  else if (vmi->type == TEXT_MONO)
    WRITE_BYTE(BIOS_VDU_CONTROL, 0xc);
  else if (vmi->mode_class == TEXT)
    WRITE_BYTE(BIOS_VDU_CONTROL, 8);
  else
    WRITE_BYTE(BIOS_VDU_CONTROL, 0xa);

  if (Video->setmode != NULL) {
    li = vmi->text_height;
    co = vmi->text_width;
  } else {
    li = READ_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1) + 1;
    co = READ_WORD(BIOS_SCREEN_COLUMNS);
  }

  video_mode = orig_mode;

#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(vmi->type == TEXT_MONO && config.dualmon)
    vga_emu_setmode(7, co, li);
  else
#endif

  /* setmode needs video_mode to _still have_ the memory-clear bit -- sw */
  vga_emu_setmode(mode, 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();
  if (mode == 0x6)
    WRITE_BYTE(BIOS_VDU_COLOR_REGISTER, 0x3f);
  else if (mode <= 0x7 || vmi->type == TEXT_MONO)
    WRITE_BYTE(BIOS_VDU_COLOR_REGISTER, 0x30);

  vga_font_height = vmi->char_height;
  if (li <= MAX_LINES) {
    if(using_text_mode()) {
      port_outb(port, 9);
      port_outb(port + 1, (port_inb(port + 1) & ~0x1f) + vga_font_height -1);
      /* adjust number of scanlines in the CRT; setmode set it at 400 */
      if (vmi->height == 200) {
	/* just set doublescan */
	port_outb(port + 1, 0x80 | port_inb(port + 1));
      } else if (vmi->height != 400) {
	/* adjust display end */
	port_outw(port, 0x12 | (((vmi->height-1) & 0xff) << 8));
      }
      WRITE_WORD(BIOS_VIDEO_MEMORY_USED, TEXT_SIZE(co, li));
    } else {
      unsigned page_size = roundUpToNextPowerOfTwo(
	    (vga.scan_len * vga.height) | 0xfff);
      if (page_size > vga.mem.bank_pages * 4096)
        page_size = vga.mem.bank_pages * 4096;
      WRITE_WORD(BIOS_VIDEO_MEMORY_USED, page_size);
    }
    WRITE_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1, li - 1);
    WRITE_WORD(BIOS_SCREEN_COLUMNS, co);
  }

  switch(vga_font_height) {
    case 14:
      u = vgaemu_bios.font_14;
      break;
    case 16:
      u = vgaemu_bios.font_16;
      break;
    default:
      u = vgaemu_bios.font_8;
  }
  SETIVEC(0x43, 0xc000, u);
  WRITE_WORD(BIOS_FONT_HEIGHT, vga_font_height); // before set_cursor_shape()
  set_cursor_shape(vmi->type == TEXT_MONO ? 0x0b0d : 0x0607);

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

  return 1;
}

/* get the active and alternate display combination code */
static void get_dcc(int *active_dcc, int *alternate_dcc)
{
  int cur_video_combo = READ_BYTE(BIOS_VIDEO_COMBO);

#if USE_DUALMON
  if (config.dualmon) {
    if (IS_SCREENMODE_MDA) {
      *active_dcc = MDA_VIDEO_COMBO;  /* active display */
      *alternate_dcc = cur_video_combo;
    }
    else {
      *active_dcc = cur_video_combo;     /* active display */
      *alternate_dcc = MDA_VIDEO_COMBO;
    }
    return;
  }
#endif
  *active_dcc = cur_video_combo;	/* active display */
  *alternate_dcc = 0;		/* no inactive display */
}

/* INT 10 AH=1B - FUNCTIONALITY/STATE INFORMATION (PS,VGA/MCGA) */
static void return_state(unsigned int statebuf) {
	int active_dcc, alternate_dcc;

	WRITE_WORD(statebuf, vgaemu_bios.functionality - 0xc0000);
	WRITE_WORD(statebuf + 2, 0xc000);

	/* store bios 0:449-0:466 at ofs 0x04 */
	MEMCPY_DOS2DOS(statebuf + 0x04, 0x449, 0x466 - 0x449 + 1);
	/* store bios 0:484-0:486 at ofs 0x22 */
	MEMCPY_DOS2DOS(statebuf + 0x22, 0x484, 0x486 - 0x484 + 1);
	/* correct number of rows-1 to number of rows at offset 0x22 */
	WRITE_BYTE(statebuf + 0x22, READ_BYTE(statebuf + 0x22) + 1);
	get_dcc(&active_dcc, &alternate_dcc);
	WRITE_BYTE(statebuf + 0x25, active_dcc);
	WRITE_BYTE(statebuf + 0x26, alternate_dcc);
	WRITE_BYTE(statebuf + 0x27, 16); /* XXX number of colors, low byte */
	WRITE_BYTE(statebuf + 0x28, 0);  /* XXX number of colors, high byte */
	WRITE_BYTE(statebuf + 0x29, 8); /* XXX number of pages supported */
	WRITE_BYTE(statebuf + 0x2a, 2); /* XXX number of scanlines 0-3=200,350,400,480 */
	WRITE_BYTE(statebuf + 0x2b, 0); /* XXX primary character block */
	WRITE_BYTE(statebuf + 0x2c, 0); /* XXX secondary character block */
	WRITE_BYTE(statebuf + 0x31, 3); /* video memory: 3 = 256K */
	WRITE_BYTE(statebuf + 0x32, 0); /* XXX save pointer state flags */
	MEMSET_DOS(statebuf + 0x33, 0, 13);
}

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

static void vga_RAM_to_RAM(unsigned height, unsigned char chr, unsigned count,
                           unsigned seg, unsigned ofs, int bank)
{
  unsigned 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 - chr) * height, height);
    if (height < 32)
      memset(dst + i * 32 + height, 0, 32 - height);
  }
  vga.reconfig.mem = 1;
}

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);
  memcpy(vga.backup_font, vga.mem.base + 0x20000, 256 * 32);
}

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

/* the actual int10 handler */

int int10(void) /* with dualmon */
{
  /* some code here is copied from Alan Cox ***************/
  int x, y, co, li;
  unsigned page, page_size, address;
  unsigned sm;

#if 0 && USE_DUALMON
  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->update_screen = NULL;
    else Video->update_screen = Video_default->update_screen;
  }
#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)
	i10_msg("near %04x:%08x\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));
      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));
      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 1;
      }
      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_cursor_pos(page, x, 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 */
#if USE_DUALMON
      if (config.dualmon && IS_SCREENMODE_MDA) break;
#endif
      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;
      }
      page_size = READ_WORD(BIOS_VIDEO_MEMORY_USED);
      address = page_size * page;
      crt_outw(0xc, address/(using_text_mode() ? 2 : 1));

      WRITE_BYTE(BIOS_CURRENT_SCREEN_PAGE, page);
      WRITE_WORD(BIOS_VIDEO_MEMORY_ADDRESS, address);
      x = get_bios_cursor_x_position(page);
      y = get_bios_cursor_y_position(page);
      set_cursor_pos(page, x, y);
      break;


    case 0x06:		/* scroll up */
      reset_idle(0);
      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 */
      reset_idle(0);
      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) = vga_read_word(sm + (co * get_bios_cursor_y_position(page)
				   + get_bios_cursor_x_position(page)) * 2);
      }
      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;


    /* 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 0x09:		/* write char & attr */
      reset_idle(0);
      i10_deb(
          "rep 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 (config.dumb_video) {
        FILE *f = config.tty_stderr ? stderr : stdout;
        int i;

        if (no_local_video && !config.tty_stderr)
          break;
        for (i = 0; i < LWORD(ecx); i++)
          fputc(LO(ax), f);
        /* cursor must not move when printing */
        fputc('\r', f);
      } else {
        vgaemu_repeat_char_attr(LO(ax), HI(bx), LO(bx), LWORD(ecx));
      }
      break;

    case 0x0a:		/* write char */
      reset_idle(0);
      i10_deb(
          "rep char: page %u, char 0x%02x '%c'\n",
          HI(bx), LO(ax), LO(ax) > ' ' && LO(ax) < 0x7f ? LO(ax) : ' '
      );
      vgaemu_repeat_char(LO(ax), HI(bx), LO(bx), LWORD(ecx));
      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 */
      reset_idle(0);
      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 */
      reset_idle(0);
      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) : ' '
        );
        tty_char_out(LO(ax), READ_BYTE(BIOS_CURRENT_SCREEN_PAGE), -1);
      }
      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 */
/*        EGA, VGA, and UltraVision return either AL=03h (color) or AL=07h
          (monochrome) in all extended-row text modes */
      if (using_text_mode() && li > 25)
        LO(ax) = (using_mono_mode() ? 7 : 3);
      else
        LO(ax) = READ_BYTE(BIOS_VIDEO_MODE) |
	    (READ_BYTE(BIOS_VIDEO_INFO_0) & 0x80);
      HI(ax) = co & 0xff;
      HI(bx) = READ_BYTE(BIOS_CURRENT_SCREEN_PAGE);
      i10_deb(
        "get video mode: mode 0x%02x, page %u, %u columns\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));

      /* root@zaphod */
      /* Palette register stuff. Only for the VGA emulator */
      {
         int i, count;
         unsigned int 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 = SEGOFF2LINEAR(SREG(es), LWORD(edx));
             for(i = 0; i < 0x10; i++) Attr_set_entry(i, READ_BYTE(src + i));
             Attr_set_entry(0x11, READ_BYTE(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 = SEGOFF2LINEAR(SREG(es), LWORD(edx));
             for(i = 0; i < 0x10; i++) WRITE_BYTE(src + i, Attr_get_entry(i));
             WRITE_BYTE(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 = SEGOFF2LINEAR(SREG(es), LWORD(edx));
             for(i = 0; i < count; i++, index++)
               DAC_set_entry(index, READ_BYTE(src + 3*i),
			    READ_BYTE(src + 3*i + 1), READ_BYTE(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 */
             DAC_get_entry(&rgb, LO(bx));
             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 = SEGOFF2LINEAR(SREG(es), LWORD(edx));
             for(i = 0; i < count; i++, index++) {
               DAC_get_entry(&rgb, index);
               WRITE_BYTE(src + 3*i, rgb.r);
               WRITE_BYTE(src + 3*i + 1, rgb.g);
               WRITE_BYTE(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;
         }
      }
      break;


    case 0x11:		/* character generator functions */
      {
	int vga_font_height;
        unsigned ofs, seg;
        unsigned rows, char_height;

        i10_msg("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),
                SREG(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...                    */

            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);
	    if(LO(ax) >= 0x10 && !adjust_font_size(vga_font_height))
	      v_printf("Problem changing font height %d->%d\n",
		       READ_WORD(BIOS_FONT_HEIGHT), vga_font_height);
	    break;

          case 0x20:		/* set 8x8 gfx chars */
            SETIVEC(0x1f, SREG(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 = SREG(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); /* gets changed for BL != 0 now: */
            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);
            /* Does NOT load any video font RAM                  */
            /* This is for GRAPHICS mode only. No vga_R*...      */
            /* No setting of the CRTC font size either...        */
            i10_msg(
              "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) = READ_WORD(BIOS_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;
            SREG(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), SREG(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) = READ_WORD(BIOS_VIDEO_PORT) == 0x3b4;
          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 */
          /* Would install a handler of the video               */
          /* BIOS that can do more modes than the main BIOS     */
          /* handler (which is often limited to 80x25 text)     */
          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)          */
            if((unsigned) LO(ax) < 4) {
	      int text_scanlines = scanlines[LO(ax)];
	      set_text_scanlines(text_scanlines);
              LO(ax) = 0x12;
              i10_deb("select vert res: %d lines", text_scanlines);
            }
            else {
              i10_msg("select vert res: invalid arg 0x%02x", LO(ax));
            }
          }
          break;

        case 0x32:	/* enable/disable cpu access to video ram */
          /* FIXME: implement this XXX */
          /* would probably modify port 0x3c2, misc output, which */
          /* also is responsible for 3b4<->3d4, pixelclock, sync  */
          /* polarity, odd/even page selection, ...               */
          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 int str = SEGOFF2LINEAR(SREG(es), LWORD(ebp));
        unsigned old_x, old_y;

        reset_idle(0);
        old_x = get_bios_cursor_x_position(page);
        old_y = get_bios_cursor_y_position(page);

	set_cursor_pos(page, LO(dx), HI(dx));

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

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

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

        if(!(LO(ax) & 1)) {	/* no cursor update */
	  set_cursor_pos(page, old_x, old_y);
        }
      }
      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(SEGOFF2LINEAR(SREG(es), LWORD(edi)));
        LO(ax) = 0x1b;
      } else {
        i10_msg("unknown functionality/state request: 0x%04x", LWORD(ebx));
      }
      break;


    case 0x1c:	{	/* save/restore video state */
      unsigned base = _BX;
      if (LO(ax) > 2)
	break;
      switch(LO(ax)) {
      case 0: {
	unsigned size = 0;
	i10_msg("save/restore: return state buffer size, cl=%x\n", LO(cx));
	if (LO(cx) & 1) {
	  /* video hardware */
	  size += 0x46;
	}
	if (LO(cx) & 2) {
	  /* BIOS */
	  size += 96;
	}
	if (LO(cx) & 4) {
	  /* DAC */
	  size += 0x304;
	}
	LWORD(ebx) = (size + 63)/64;
	break;
      }
      case 1:
	i10_msg("save/restore: save state, cl=%x\n", LO(cx));
	if (LO(cx) & 1) {
	  unsigned char buf[0x46];
	  unsigned crtc, ind;

	  /* select crtc base address */
	  crtc = (port_inb(MISC_OUTPUT_R) & 1) ? 0x3d4 : 0x3b4;

	  buf[0x0] = port_inb(SEQUENCER_INDEX);
	  buf[0x1] = port_inb(crtc);
	  buf[0x2] = port_inb(GFX_INDEX);
	  /* feature control */
	  buf[0x4] = port_inb(FEATURE_CONTROL_R);

	  for (ind = 1; ind < 5; ind++) {
	    port_outb(SEQUENCER_INDEX, ind);
	    buf[0x4+ind] = port_inb(SEQUENCER_DATA);
	  }
	  port_outb(SEQUENCER_INDEX, 0);
	  buf[0x9] = port_inb(SEQUENCER_DATA);

	  for (ind = 0; ind < 25; ind++) {
	    port_outb(crtc, ind);
	    buf[0x0a+ind] = port_inb(crtc + 1);
	  }

	  /* reset flipflop ! */
	  port_inb(crtc + 0x6);
	  buf[0x3] = port_inb(ATTRIBUTE_INDEX);
	  for (ind = 0; ind < 20; ind++) {
	    port_inb(crtc + 0x6);
	    port_outb(ATTRIBUTE_INDEX, ind);
	    buf[0x23 + ind] = port_inb(ATTRIBUTE_DATA);
	  }
	  port_inb(crtc + 0x6);
	  port_outb(ATTRIBUTE_INDEX, buf[0x3]);
	  port_inb(crtc + 0x6);

	  for (ind = 0; ind < 9; ind++) {
	    port_outb(GFX_INDEX, ind);
	    buf[0x37+ind] = port_inb(GFX_DATA);
	  }

	  buf[0x40] = crtc & 0xff;
	  buf[0x41] = crtc >> 8;
	  /* VGA latches */
	  memcpy(&buf[0x42], vga.latch, 4);
	  MEMCPY_2DOS(SEGOFF2LINEAR(_ES, base), buf, sizeof(buf));
	  base += sizeof(buf);
	}
	if (LO(cx) & 2) {
	  MEMCPY_DOS2DOS(SEGOFF2LINEAR(_ES, base), 0x449, 96);
	  base += 96;
	}
	if (LO(cx) & 4) {
	  unsigned char buf[0x304];
	  unsigned ind;
	  buf[0] = port_inb(DAC_STATE);
	  buf[1] = port_inb(DAC_WRITE_INDEX);
	  buf[2] = port_inb(DAC_PEL_MASK);
	  port_outb(DAC_READ_INDEX, 0x00);
	  for(ind = 0; ind < 768; ind++)
	    buf[0x3 + ind] = port_inb(DAC_DATA);
	  buf[0x303] = port_inb(COLOR_SELECT);
	  MEMCPY_2DOS(SEGOFF2LINEAR(SREG(es), base), buf, sizeof(buf));
	  base += sizeof(buf);
	}
	break;
      case 2:
	i10_msg("save/restore: restore state, cl=%x\n", LO(cx));
	if (LO(cx) & 1) {
	  unsigned char buf[0x46];
	  unsigned crtc, ind;
	  MEMCPY_2UNIX(buf, SEGOFF2LINEAR(SREG(es), base), sizeof(buf));
	  base += sizeof(buf);
	  crtc = buf[0x40] | (buf[0x41] << 8);
	  for (ind = 1; ind < 5; ind++)
	    port_outw(SEQUENCER_INDEX, ind | (buf[0x04+ind] << 8));
	  port_outw(SEQUENCER_INDEX, buf[0x09] << 8);
	  /* disable write protection to index 0-7 */
	  port_outw(crtc, 0x0011);
	  for (ind = 0; ind < 25; ind++)
	    port_outw(crtc, ind | (buf[0x0a+ind] << 8));
	  /* select crtc base address */
	  port_outb(MISC_OUTPUT_W, (port_inb(MISC_OUTPUT_R) & ~0x01) | (crtc == 0x3d4));
	  /* reset flipflop ! */
	  port_inb(crtc + 0x6);
	  for (ind = 0; ind < 20; ind++) {
	    port_outb(ATTRIBUTE_INDEX, ind);
	    port_outb(ATTRIBUTE_INDEX, buf[0x23+ind]);
	  }
	  port_outb(ATTRIBUTE_INDEX, buf[0x3]);
	  port_inb(crtc + 0x6);
	  for (ind = 0; ind < 9; ind++)
	    port_outw(GFX_INDEX, ind | (buf[0x37+ind] << 8));

	  port_outb(SEQUENCER_INDEX, buf[0x0]);
	  port_outb(crtc, buf[0x1]);
	  port_outb(GFX_INDEX, buf[0x2]);
	  /* feature control */
	  port_outb(crtc + 0x6, buf[0x4]);
	  /* VGA latches */
	  memcpy(vga.latch, &buf[0x42], 4);
	}
	if (LO(cx) & 2) {
	  MEMCPY_DOS2DOS(0x449, SEGOFF2LINEAR(_ES, base), 96);
	  base += 96;
	}
	if (LO(cx) & 4) {
	  unsigned char buf[0x304];
	  unsigned ind;
	  MEMCPY_2UNIX(buf, SEGOFF2LINEAR(_ES, base), sizeof(buf));
	  base += sizeof(buf);
	  port_outb(DAC_PEL_MASK, buf[2]);
	  port_outb(DAC_WRITE_INDEX, 0x00);
	  for(ind = 0; ind < 768; ind++)
	    port_outb(DAC_DATA, buf[0x3 + ind]);
	  port_outb(COLOR_SELECT, buf[0x303]);
	  if (buf[0] & 3)
	    port_outb(DAC_READ_INDEX, buf[1]);
	  else
	    port_outb(DAC_WRITE_INDEX, buf[1]);
	}
	break;
      }
      LO(ax) = 0x1c;
      break;
    }


    case 0x4f:		/* vesa interrupt */
      do_vesa_int();
      break;


    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;
  }
  return 1;
}

void video_mem_setup(void)
{
  int co, li;

  WRITE_BYTE(BIOS_CURRENT_SCREEN_PAGE, 0);

  li = LI;
  co = CO;
  if (config.term)
    gettermcap(0, &co, &li);

  WRITE_WORD(BIOS_SCREEN_COLUMNS, co);     /* chars per line */
  WRITE_BYTE(BIOS_ROWS_ON_SCREEN_MINUS_1, li - 1); /* lines on screen - 1 */
  WRITE_WORD(BIOS_VIDEO_MEMORY_USED, TEXT_SIZE(co,li));   /* size of video regen area in bytes */

  WRITE_WORD(BIOS_CURSOR_SHAPE, (bios_configuration&MDA_CONF_SCREEN_MODE)?0x0A0B:0x0607);
#if 0
  /* This is needed in the video stuff. Grabbed from boot(). */
  if ((bios_configuration & MDA_CONF_SCREEN_MODE) == MDA_CONF_SCREEN_MODE) {
    WRITE_WORD(BIOS_VIDEO_PORT, 0x3b4);	/* base port of CRTC - IMPORTANT! */
    video_mode = 7;
  } else {
    WRITE_WORD(BIOS_VIDEO_PORT, 0x3d4);	/* base port of CRTC - IMPORTANT! */
    video_mode = 3;
  }
  WRITE_BYTE(BIOS_VIDEO_MODE, video_mode);

  WRITE_BYTE(BIOS_VDU_CONTROL, 9);	/* current 3x8 (x=b or d) value */

  WRITE_WORD(BIOS_VIDEO_MEMORY_ADDRESS, 0);/* offset of current page in buffer */

  WRITE_WORD(BIOS_FONT_HEIGHT, 16);
#endif
  /* XXX - these are the values for VGA color!
     should reflect the real display hardware. */
  WRITE_BYTE(BIOS_VIDEO_INFO_0, 0x60);
  WRITE_BYTE(BIOS_VIDEO_INFO_1, 0xF9);
  WRITE_BYTE(BIOS_VIDEO_INFO_2, 0x51);

  /* XXX - This usage isn't quite correct: this byte should be an index
     into a table. Requires modification of our BIOS, and setting up
     lots of tables*/
  WRITE_BYTE(BIOS_VIDEO_COMBO, video_combo);

  if (!config.vga) {
    WRITE_DWORD(BIOS_VIDEO_SAVEPTR, 0);		/* pointer to video table */
    /* point int 1f to the default 8x8 graphics font for high characters */
    SETIVEC(0x1f, 0xc000, vgaemu_bios.font_8 + 128 * 8);
  }
  else if (!config.vbios_post) {
    Bit32u p, q;
    Bit16u vc;

    i10_msg("Now initialising 0x40:a8-ab\n");
    WRITE_DWORD(BIOS_VIDEO_SAVEPTR, int_bios_area[BIOS_VIDEO_SAVEPTR/4]);

    /* many BIOSes use this: take as fallback value */
    WRITE_BYTE(BIOS_VIDEO_COMBO, 0xb);
    /* correct BIOS_VIDEO_COMBO value */
    p = READ_DWORD(BIOS_VIDEO_SAVEPTR) + 0x10;
    /* [VGA only] ptr to Secondary Save Pointer Table, must be valid */
    p = rFAR_PTR(Bit32u, p);
    p = READ_DWORD(p) + 0x2;
    /* ptr to Display Combination Code Table, must be valid */
    p = rFAR_PTR(Bit32u, p);
    p = READ_DWORD(p) + 0x4;
    /* Each pair of bytes gives a valid display combination */
    q = p = rFAR_PTR(Bit32u, p);
    do {
      vc = READ_WORD(q);
      if (vc == video_combo || vc == (video_combo << 8)) {
	WRITE_BYTE(BIOS_VIDEO_COMBO, (q-p)/2);
	i10_msg("found video_combo: %x\n", (q-p)/2);
	break;
      }
      q += 2;
    } while ((vc & 0xff) < 0xd && vc < 0xd00);
  }
}
