/*
 *  TEX Device Driver  ver 2.02-
 *  copyright(c) 1988,89 by TSG, 1990-93 SHIMA
 *
 *  PREVIEWER for IBM-PC with VGA ver 1.00
 *  Modified by SOLITON
 *  dev_ibm.c : previewer module for IBM-PC
 *             1st edition
 *
 *	modified to dev_dosv.c by sempa
 *
 *	modified to dev_vhi: for SuperVGA by hero.h
 *
 *  Support EMS by SHIMA, Dec 1992
 *
 *	integrate into DOS/V by hero.h, Jan 1993
 *
 *  Of course, this module is device-dependent.
 */

#define	TITLE_COMMENT	""

#include <stdio.h>
#include <stdlib.h>
#define _DEF_STDIO_H_
#include <dos.h>
#include <mem.h>
#include <alloc.h>
#define _DEF_DOS_H_
#include "dd.h"
#include <conio.h>
#include <time.h>

#include "device.h"
#include "dev_dosv.h"
#include "err.h"

extern int unit_pages;
extern int slow_fact;
extern int slow_fact2;
extern int slow_page;
extern int v_shift0;
static int v_shift1 = 1;

extern BOOL f_use_new_size_option;
extern BOOL f_resume;
extern BOOL f_reverse;
extern int num_view;
extern int num_view_buf;
extern uint resume_info;

#ifndef	NEMS
extern int system_EMS_end;
int view_EMS_top;
int view_EMS_unit;
int set_ems(int);

#endif

const char *const title_comment =
TITLE_COMMENT;

long view_buf_size;

BOOL f_soft_scroll = FALSE;	/* hardware scroll */
BOOL f_packed_pixel = FALSE;	/* plane layer mode */
BOOL f_ramdac = FALSE;	/* use palette */

struct PALETTE s_color =
{	   /* analog color initial value */
	{63, 63, 63},	/* foreground color */
	{63, 63, 63},	/* background color */
};

int f_machine_unique = -1;	/* graphic mode is unknown */
int disp_reso = RES_640_480;	/* resolution is 640 by 480 */
int video_chip = CHIP_VGA;	/* adapter is VGA */

int s_max_width = CRT_DPI * 8;
int s_max_height = CRT_DPI * 11;
int x_shift = 0;
int y_shift = 0;

static struct VIEW view;

static unsigned int key_data[] =
{
	0x0008, 0x0020,
	0x0050, 0x004e,
	0x000d,
	0x001b,
	0x0051,
	0x0042,
	0x0056,
	0x0043,
	0x0052,
	0x0048,
	0x4700,
	0x004d,
	0x0047,
	0x5000, 0x4800, 0x4b00, 0x4d00,
	0x50ff, 0x48ff, 0x4bff, 0x4dff,
	0x001b
};

/* SVGA チップ <-> 800x600 画面モード 対応テーブル */
static struct CHIP_MODES modes_chip_800[] =
{
	{CHIP_ET4000, VMODE_ET4000_800},
	{CHIP_WD90C00, VMODE_WD90C00_800},
	{CHIP_T8900, VMODE_T8900_800},
	{CHIP_86C911, VMODE_86C911_800},
	{CHIP_MACH, VMODE_MACH_800},
	{0, 0},
};

/* SVGA チップ <-> 1024x768 画面モード 対応テーブル */
static struct CHIP_MODES modes_chip_1024[] =
{
	{CHIP_ET4000, VMODE_ET4000_1K},
	{CHIP_WD90C00, VMODE_WD90C00_1K},
	{CHIP_T8900, VMODE_T8900_1K},
	{CHIP_86C911, VMODE_86C911_1K},
	{CHIP_MACH, VMODE_MACH_1K},
	{0, 0},
};

/* VBE 識別文字列 */
char vesa_id[] = "VESA";

/* 解像度 <-> VESA 画面モード 対応テーブル */
static struct VESA_MODES modes_vesa[] =
{
	{RES_800_600, 0x0102},
	{RES_1024_768, 0x0104},
	{RES_1280_1024, 0x0106},
	{0, 0},
};

/* 解像度 文字列テーブル */
static struct OPT_NAMES reso_name[] =
{
	{0, "640x480"},
	{1, "800x600"},
	{2, "1024x768"},
	{3, "1280x1024"},
	{-1, ""},
};

static char unknown_name[] = "Unknown";

/* ビデオコントローラ名テーブル */
static struct OPT_NAMES chip_name[] =
{
	{0, "VGA"},
	{1, "ET4000"},
	{2, "86C80X/9XX"},
	{3, "T8900"},
	{4, "WD90C00"},
	{5, "WD100X"},
	{6, "MACH32"},
	{9, "VESA"},
	{-1, ""},
};

static unsigned char SAVE_PAL[17];
static unsigned char SAVE_REV_PAL[17];
static unsigned char SAVE_RAMDAC[3 * 3];

static struct DIVSCAN divscan_tbl[DIVSCAN_MAX];

static HUGE_BUF *map_top_ptr;	/* pointer of bitmap buffer */

static PIXEL vmax, hmax, hmaxb;	/* size to output. hmaxb is byte-size. */
static ulong g_top_ptr;	/* head pointer of graphic vram */
static unsigned video_logical_width;	/* logical width of graphic plane */
static unsigned video_isr;	/* video input status register */
static unsigned video_crtc;	/* video crt control register */
static unsigned org_video_mode;	/* original video mode */

static int f_div;
static int f_div2;
static int f_box;
static int v_x_shift;
static int v_x_shift2;
static int v_y_shift;
static int v_y_shift2;
static int view_page;

static BUFFER *v_top;
static BUFFER *v_top2;
static ulong v_top_addr;
static ulong v_top_addr2;

static HUGE_BUF *view_buf;

static uint g_width_b;
static uint g_height;

static int vga_mode;
static BOOL f_hitext_driver = FALSE;
static BOOL f_bios_out = TRUE;
static uint div_fact1;
static uint div_fact2;
static uint sh_fact1;
static uint sh_fact2;
static uint sh_h_fact;
static uint sh_v_fact;
static uint x_org_b = 5;
static uint y_org = 2;

/* チップ依存関数へのポインタ */
static BUFFER *(*rd_bank) (ulong);	/* リードバンク設定用 */
static BUFFER *(*wr_bank) (ulong);	/* ライトバンク設定用 */
static void (*st_disp) (ulong);	/* 表示開始アドレス設定用 */

/* 実行時 VBE 情報テーブル */
static struct VBE_ACT_INFO vbe_act;

/* VBE ウィンドウ操作関数ポインタ */
static void (far * vbe_win_func) (void);

/* Proto-types */

/*  buffer.c */
ulong leftbuffer();

void device_init(DIMENSION *);
void device_end(void);

/* (in fact, DIMENSION information is given) */
void device_cont(void);
void device_pause(void);
void device_clear(OUTPUT_INFO *);
void pr_new_page(void);
NEXT_ACTION device_out(OUTPUT_INFO *, DIMENSION *);

/* (in fact, OUTPUT_INFO information is given also to this) */
KeyInput extra_inkey(void);
static void first_draw(struct SCROLL *, int, int, int);
static void draw_screen(struct SCROLL *);
static void move_screen(struct SCROLL *, KeyInput, int);

static void s_up(struct SCROLL *, DIMENSION *, int);
static void s_down(struct SCROLL *, DIMENSION *, int);
static void s_right(struct SCROLL *, DIMENSION *, int);
static void s_left(struct SCROLL *, DIMENSION *, int);

static void scroll_up(struct SCROLL *, DIMENSION *, BOOL);
static void scroll_down(struct SCROLL *, DIMENSION *, BOOL);
static void scroll_right(struct SCROLL *, DIMENSION *, BOOL);
static void scroll_left(struct SCROLL *, DIMENSION *, BOOL);

static void clear_text_screen(void);
static void program_screen_mode(int);
static void cursor_mode(int);

static void pallet(int);
static void save_palette(unsigned char *);
static void restore_palette(unsigned char *);

static void save_a_ramdac(uint, unsigned char *);
static void save_ramdac(unsigned char *);
static void restore_a_ramdac(uint, unsigned char *);
static void restore_ramdac(unsigned char *);
static void analog_color(struct PALETTE *);

static void flskey(void);
static void cls(int);

static void s_view_hi(int);
static void s_view4_low(void);
static void s_view8_low(void);

static void msg_help(void);
static int  get_view(int, int, struct SCROLL *);

static void div_scanline(struct DIVSCAN far *, ulong, uint, uint);
static void draw_line_banked(HUGE_BUF *, ulong, struct DIVSCAN far *, struct SCROLL *);
static void draw_line(HUGE_BUF *, ulong, int, struct SCROLL *);
static void draw_block(ulong, struct DIVSCAN far *, struct SCROLL *);

static void enable_wd_ext(void);
static void paradise_on(void);
static void fix_wd_disp(void);

static void wait_vret(void);

static BUFFER *rd_bank_vga(ulong);
static BUFFER *wr_bank_vga(ulong);
static void st_disp_vga(ulong);

static BUFFER *rd_bank_et4000(ulong);
static BUFFER *wr_bank_et4000(ulong);
static void st_disp_et4000(ulong);

static BUFFER *rd_bank_wd90c00(ulong);
static BUFFER *wr_bank_wd90c00(ulong);
static void st_disp_wd90c00(ulong);

static BUFFER *rd_bank_t8900(ulong);
static BUFFER *wr_bank_t8900(ulong);
static void st_disp_t8900(ulong);

static BUFFER *rd_bank_86c911(ulong);
static BUFFER *wr_bank_86c911(ulong);
static void st_disp_86c911(ulong);

static BUFFER *rd_bank_mach(ulong);
static BUFFER *wr_bank_mach(ulong);
static void st_disp_mach(ulong);

static BUFFER *rd_bank_vbe(ulong);
static BUFFER *wr_bank_vbe(ulong);
static void st_disp_vbe(ulong);

static int get_vbe_info(struct VBE_INFO far *);
static int get_vbe_mode_info(uint, struct VBE_MODE_INFO far *);
static BOOL vbe_sign(char far *);
static uint reso2vesa(int);
static uint get_vbe_funcs(uint);

static char *iopt2name(struct OPT_NAMES *, int);

/* INTEGER オプションを実装名に変換する */
static char *iopt2name(struct OPT_NAMES *tp, int option)
{
	char *name = unknown_name;

	while (tp->opt != -1) {
		if (tp->opt == option) {
			name = tp->name;
			break;
		}
		tp++;
	}
	return name;
}

static void s_up(struct SCROLL *s_dat, DIMENSION *dim, int v_shift)
	/* スムース・スクロール・アップ by v_shift dots */
{
	unsigned char back[G_WIDTH_B_MAX];
	HUGE_BUF *mptr;
	ulong gadr;
	int i;

#if defined(LOOK_SMOOTH)
	int j;

#endif

	for (i = 0; i < g_width_b; i++) {
		back[i] = 0;
	}
	mptr = s_dat->mptr + (ulong)hmaxb *(ulong)g_height;
	gadr = s_dat->offset + (ulong)g_height *(ulong)g_width_b;

	for (i = 0; i < v_shift; i++) {
		movedata(FP_SEG(mptr), FP_OFF(mptr),
				 FP_SEG(back), FP_OFF(back) + s_dat->h_spc, s_dat->gw_act);
		draw_line((HUGE_BUF *)back, gadr, g_width_b, s_dat);
		mptr += (ulong)hmaxb;
		gadr += g_width_b;
#if defined( LOOK_SMOOTH)
		if (f_soft_scroll) {
			for (j = 0; j < WEIGHT; j++);	/*  insert weight to look smoother */
		}
#endif
		s_dat->offset += g_width_b;
		st_disp(s_dat->offset);
	}

}

static void s_down(struct SCROLL *s_dat, DIMENSION *dim, int v_shift)
	/* スムース・スクロール・ダウン by v_shift dots */
{
	unsigned char back[G_WIDTH_B_MAX];
	HUGE_BUF *mptr;
	ulong gadr;
	int i;

#if defined(LOOK_SMOOTH)
	int j;

#endif

	for (i = 0; i < g_width_b; i++) {
		back[i] = 0;
	}
	mptr = s_dat->mptr - (ulong)hmaxb *v_shift;

	gadr = s_dat->offset - g_width_b * v_shift;

	for (i = 0; i < v_shift; i++) {
		movedata(FP_SEG(mptr), FP_OFF(mptr),
				 FP_SEG(back), FP_OFF(back) + s_dat->h_spc, s_dat->gw_act);
		draw_line((HUGE_BUF *)back, gadr, g_width_b, s_dat);
		mptr += (ulong)hmaxb;
		gadr += g_width_b;
#if defined(LOOK_SMOOTH)
		if (f_soft_scroll) {
			for (j = 0; j < WEIGHT; j++);	/*  insert weight to look smoother */
		}
#endif
		s_dat->offset -= g_width_b;
		st_disp(s_dat->offset);
	}
}

static void s_right(struct SCROLL *s_dat, DIMENSION *dim, int h_shift)
	/* 左方向　スムース・スクロール by 16 dots */
{
	HUGE_BUF *mptr;
	HUGE_BUF *mp;
	ulong gadr, gp;
	int i;
	int dh, eh;

	if (dim->buf_height < g_height) {
		dh = dim->buf_height;
		eh = g_height - dh;
	}
	else {
		dh = g_height;
		eh = 0;
	}

	s_dat->offset -= h_shift;

	mptr = s_dat->mptr;
	gadr = s_dat->offset;

	while (dh--) {
		draw_line(mptr, gadr, h_shift, s_dat);
		mptr += (ulong)hmaxb;
		gadr += g_width_b;
	}

	while (eh--) {
		gp = gadr;
		for (i = h_shift; i; i--) {
			*wr_bank(gp++) = 0;
		}
		gadr += g_width_b;
	}

	st_disp(s_dat->offset);
}

static void s_left(struct SCROLL *s_dat, DIMENSION *dim, int h_shift)
	/* 右方向　スムース・スクロール by 16 dots */
{
	HUGE_BUF *mptr;
	HUGE_BUF *mp;
	ulong gadr, gp;
	int i;
	int dh, eh;

	if (dim->buf_height < g_height) {
		dh = dim->buf_height;
		eh = g_height - dh;
	}
	else {
		dh = g_height;
		eh = 0;
	}

	s_dat->offset += h_shift;

	mptr = s_dat->mptr + (g_width_b - h_shift);
	gadr = s_dat->offset + (g_width_b - h_shift);

	while (dh--) {
		draw_line(mptr, gadr, h_shift, s_dat);
		mptr += (ulong)hmaxb;
		gadr += g_width_b;
	}

	while (eh--) {
		gp = gadr;
		for (i = h_shift; i; i--) {
			*wr_bank(gp++) = 0;
		}
		gadr += g_width_b;
	}

	st_disp(s_dat->offset);
}

KeyInput
extra_inkey(void)
	/* realtime key scanning. waiting until one of KeyInput is pressed.
     * and if the key is a cursor-key, I check at the same time whether Shift-
     * key is hit or not.
     */
{
	int num = 0;
	int f_num = 0;
	unsigned int k_dat;
	KeyInput key_d;

	union REGS regs;

	while (TRUE) {
		regs.h.ah = 0;
		int86(0x16, &regs, &regs);
		k_dat = regs.x.ax;
/*		k_dat = bioskey(0);	*/
		if ((k_dat & 0xff) != 0) {
			k_dat = toupper(0xff & k_dat);
		}
		else {					/* extended keyboad code */
			regs.h.ah = 2;
			int86(0x16, &regs, &regs);
			if ((regs.x.ax & 0x03) != 0)
/*			if ((bioskey(2) & 0x03) != 0)	*/
				k_dat |= 0xff;
		}
		switch (k_dat) {
		  case 0x4900:
			  k_dat = (int)'P';	/* PagUp */
			  break;
		  case 0x5100:
			  k_dat = (int)'N';	/* PagDn */
			  break;
		}
		if (k_dat >= '0' && k_dat <= '9') {
			f_num = 1;
			num -= (num / 100) * 100;
			num = num * 10 + k_dat - '0';
			if (num >= 1)
				unit_pages = num;
		}
		else {
			for (key_d = (KeyInput) 0; key_d != LoopEnd; ++key_d)
				if (key_data[(int)key_d] == k_dat)
					break;
			if (key_d == LoopEnd)
				continue;
			if ((key_d == Key_M || key_d == Key_G) && f_num == 0)
				continue;
			break;
		}
	}
	if (key_d == Key_Q)
		key_d = EscapeKey;
	if (key_d == Key_B)
		key_d = Key_P;
	return (key_d);
}

static void scroll_up(struct SCROLL *s_dat, DIMENSION *dim, BOOL f_accell)
{
	int shift;

	if ((shift = vmax - s_dat->y - g_height) > 0) {
		if (f_accell) {
			if (shift > sh_v_fact)
				shift = sh_v_fact;
		}
		else {
			if (shift > v_shift1)
				shift = v_shift1;
		}
		s_up(s_dat, dim, shift);
		s_dat->mptr += (ulong)hmaxb *shift;

		s_dat->y += shift;
	}
}

static void scroll_down(struct SCROLL *s_dat, DIMENSION *dim, BOOL f_accell)
{
	int shift;

	if ((shift = s_dat->y) > 0) {
		if (f_accell) {
			if (shift > sh_v_fact)
				shift = sh_v_fact;
		}
		else {
			if (shift > v_shift1)
				shift = v_shift1;
		}
		s_down(s_dat, dim, shift);
		s_dat->mptr -= (ulong)hmaxb *shift;

		s_dat->y -= shift;
	}
}

static void scroll_right(struct SCROLL *s_dat, DIMENSION *dim, BOOL f_accell)
{
	int shift;
	int h_mrg;

	if (f_accell) {
		shift = sh_h_fact;
	}
	else {
		shift = 2;
	}
	if (s_dat->x > 0) {
		if (s_dat->x < shift) {
			shift = s_dat->x;
		}
		s_dat->mptr -= shift;
		s_dat->x -= shift;
		s_right(s_dat, dim, shift);
	}
}

static void scroll_left(struct SCROLL *s_dat, DIMENSION *dim, BOOL f_accell)
{
	int shift;
	int h_mrg;

	if (f_accell) {
		shift = sh_h_fact;
	}
	else {
		shift = 2;
	}
	if ((h_mrg = hmaxb - s_dat->x - g_width_b) > 0) {
		if (h_mrg < shift) {
			shift = h_mrg;
		}
		s_dat->mptr += shift;
		s_dat->x += shift;
		s_left(s_dat, dim, shift);
	}
}

void pr_new_page()
{
}

static void save_palette(unsigned char *palette)
{
	struct SREGS sregs;
	union REGS regs;

	regs.x.ax = 0x1009;
	sregs.es = FP_SEG(palette);
	regs.x.dx = FP_OFF(palette);
	int86x(0x10, &regs, &regs, &sregs);
}

static void save_a_ramdac(uint number, unsigned char *p)
{
	struct SREGS sregs;
	union REGS regs;

	regs.x.ax = 0x1017;
	regs.x.bx = number;
	regs.x.cx = 1;
	sregs.es = FP_SEG(p);
	regs.x.dx = FP_OFF(p);
	int86x(0x10, &regs, &regs, &sregs);
}

static void save_ramdac(unsigned char *ramdac)
{
	save_a_ramdac(0x0000, ramdac);
	save_a_ramdac(0x0007, ramdac + 3);
	save_a_ramdac(0x000F, ramdac + 3 * 2);
}

static void restore_palette(unsigned char *palette)
{
	struct SREGS sregs;
	union REGS regs;

	regs.x.ax = 0x1002;
	sregs.es = FP_SEG(palette);
	regs.x.dx = FP_OFF(palette);
	int86x(0x10, &regs, &regs, &sregs);
}

static void restore_a_ramdac(uint number, unsigned char *p)
{
	struct SREGS sregs;
	union REGS regs;

	regs.x.ax = 0x1012;
	regs.x.bx = number;
	regs.x.cx = 1;
	sregs.es = FP_SEG(p);
	regs.x.dx = FP_OFF(p);
	int86x(0x10, &regs, &regs, &sregs);
}

static void restore_ramdac(unsigned char *ramdac)
{
	restore_a_ramdac(0x0000, ramdac);
	restore_a_ramdac(0x0007, ramdac + 3);
	restore_a_ramdac(0x000F, ramdac + 3 * 2);
}

static void set_a_ramdac(uint pal, struct COLOR_ELEMENT *cp)
{
	union REGS regs;

	/* assign palette */
	regs.x.ax = 0x1000;
	regs.h.bl = pal;
	regs.h.bh = pal;
	int86(V_BIOS, &regs, &regs);
	/* set color element */
	regs.x.ax = 0x1010;
	regs.x.bx = pal;
	regs.h.ch = cp->g;
	regs.h.cl = cp->b;
	regs.h.dh = cp->r;
	int86(V_BIOS, &regs, &regs);
}

static void analog_color(struct PALETTE *color)
{
	union REGS regs;

	/* set foreground color */
	set_a_ramdac(0x0007, &color->fg);
	set_a_ramdac(0x000F, &color->fg);
	/* set background color */
	set_a_ramdac(0x0000, &color->bg);
}

static uint get_vmode_org(void)
{
	union REGS regs;
	uint disp_columns;
	uchar disp_rows;
	uchar video_mode;

	/* get BIOS video mode */
	regs.h.ah = 0x0f;
	int86(V_BIOS, &regs, &regs);
	video_mode = regs.h.al;

	/* read original display dimension */
	disp_columns = peek(BIOS_WORK, 0x4A);
	disp_rows = peekb(BIOS_WORK, 0x84) + 1;

	if (video_mode == 0x03 && disp_columns != 80 && disp_rows != 25) {
		video_mode = VMODE_HITEXT;
	}
	return video_mode;
}

static void resume_page(DIMENSION *dim)
{
	if (f_resume) {				/* Check resume information */
		if (peekb(BIOS_WORK, RESUME_BASE) == 'R'
			&& peek(BIOS_WORK, RESUME_BASE + 1) == resume_info)
			dim->start_page = peek(BIOS_WORK, RESUME_BASE + 3);
		pokeb(BIOS_WORK, RESUME_BASE, 'R');
		poke(BIOS_WORK, RESUME_BASE + 1, resume_info);
	}
}

/* SVGA チップ依存の画面モード取得 */
static uint get_vmode_chip(struct CHIP_MODES *vp, uint video_chip)
{
	uint vmode = VMODE_UNKNOWN;

	while (vp->chip) {
		if (vp->chip == video_chip) {
			vmode = vp->vmode;
			break;
		}
		vp++;
	}
	return vmode;
}

/* プレビュー用画面モードの取得 */
static uint get_vmode_preview(void)
{
	uint vmode = VMODE_UNKNOWN;
	struct VESA_MODES *vp;

	/* チップ依存の画面モードで対応するものが在るか調べる */
	switch (video_chip) {
	  case CHIP_ET4000:
	  case CHIP_WD90C00:
	  case CHIP_T8900:
	  case CHIP_86C911:
	  case CHIP_MACH:
		  switch (disp_reso) {
			case RES_640_480:
				vmode = VMODE_VGA;
				break;

			case RES_800_600:
				vmode = (uint)f_machine_unique;
				if (vmode == VMODE_UNKNOWN) {
					vmode = get_vmode_chip(modes_chip_800, video_chip);
				}
				break;

			case RES_1024_768:
				vmode = (uint)f_machine_unique;
				if (vmode == VMODE_UNKNOWN) {
					vmode = get_vmode_chip(modes_chip_1024, video_chip);
				}
				break;

			default:
				break;
		  }
		  break;

	  case CHIP_VBE:
		  vmode = (uint)f_machine_unique;
		  if (vmode == VMODE_UNKNOWN) {
			  if ((vmode = reso2vesa(disp_reso)) != VMODE_UNKNOWN) {
				  /* 指定された解像度を VBE でサポートしているか調べる */
				  vmode = get_vbe_funcs(vmode);
			  }
		  }
		  f_soft_scroll = TRUE;	/* H/W スクロールは不可 */
		  break;

	  default:
		  switch (disp_reso) {
			case RES_640_480:
				vmode = VMODE_VGA;
				break;
			case RES_800_600:
				vmode = (uint)f_machine_unique;
				break;
		  }
		  break;
	}
	return vmode;
}

/* プレビュー用画面モードの設定 */
static void set_vmode_preview(uint vmode)
{
	union REGS regs;

	vmode &= 0x7F7F;
	if (vmode > 0x007F) {
		regs.x.ax = 0x4F02;		/* use VESA BIOS function */
		regs.x.bx = vmode;
		int86(V_BIOS, &regs, &regs);
	}
	else {
		regs.h.ah = 0x00;		/* use basic BIOS function */
		regs.h.al = vmode;
		int86(V_BIOS, &regs, &regs);
	}

	/* set SVGA miscellaneous registers */
	switch (video_chip) {
	  case CHIP_86C911:
		  /* outport(0x3c4, 0x4838); *//* interlaced mode */
		  break;
	  case CHIP_WD90C00:
		  switch (disp_reso) {
			case RES_800_600:
				if (!f_soft_scroll) {
					fix_wd_disp();
				}
				break;
			default:
				break;
		  }
		  break;
	  case CHIP_WD100X:
		  switch (disp_reso) {
			case RES_800_600:
				if (!f_soft_scroll) {
					paradise_on();
				}
				break;
			default:
				break;
		  }
		  break;
	  case CHIP_MACH:
		  f_soft_scroll = TRUE;
		  break;
	  default:
		  break;
	}
}

void set_disp_reso(void)
{
	int args;

	switch (disp_reso) {
	  case RES_640_480:
		  g_width_b = G_WIDTH_B_640;
		  g_height = G_HEIGHT_480;
		  div_fact1 = DIV_F1_LOW;
		  div_fact2 = DIV_F2_LOW;
		  sh_fact1 = SHIFT_F1_LOW;
		  sh_fact2 = SHIFT_F2_LOW;
		  sh_h_fact = 16;
		  sh_v_fact = 24;
		  x_org_b = X_ORG_B_640;
		  y_org = Y_ORG_480;
		  break;
	  case RES_800_600:
		  g_width_b = G_WIDTH_B_800;
		  g_height = G_HEIGHT_600;
		  div_fact1 = DIV_F1_LOW;
		  div_fact2 = DIV_F2_LOW;
		  sh_fact1 = SHIFT_F1_LOW;
		  sh_fact2 = SHIFT_F2_LOW;
		  sh_h_fact = 16;
		  sh_v_fact = 24;
		  x_org_b = X_ORG_B_800;
		  y_org = Y_ORG_600;
		  break;
	  case RES_1024_768:
		  g_width_b = G_WIDTH_B_1024;
		  g_height = G_HEIGHT_768;
		  div_fact1 = DIV_F1_HI;
		  div_fact2 = DIV_F2_HI;
		  sh_fact1 = SHIFT_F1_HI;
		  sh_fact2 = SHIFT_F2_HI;
		  sh_h_fact = 16;
		  sh_v_fact = 32;
		  x_org_b = X_ORG_B_1024;
		  y_org = Y_ORG_768;
		  break;
	  case RES_1280_1024:
		  g_width_b = G_WIDTH_B_1280;
		  g_height = G_HEIGHT_1024;
		  div_fact1 = DIV_F1_HI;
		  div_fact2 = DIV_F2_HI;
		  sh_fact1 = SHIFT_F1_HI;
		  sh_fact2 = SHIFT_F2_HI;
		  sh_h_fact = 16;
		  sh_v_fact = 32;
		  x_org_b = X_ORG_B_1280;
		  y_org = Y_ORG_1024;
		  break;
	}
	view_buf_size = (ulong)g_width_b *(ulong)g_height / 2;
}

/* チップ依存の処理関数ポインタ設定 */
void set_chip_funcs(int chip)
{
	/* ディフォルトは VGA */
	rd_bank = rd_bank_vga;
	wr_bank = wr_bank_vga;
	st_disp = st_disp_vga;

	switch (chip) {
	  case CHIP_ET4000:
		  rd_bank = rd_bank_et4000;
		  wr_bank = wr_bank_et4000;
		  st_disp = st_disp_et4000;
		  break;
	  case CHIP_VBE:
		  rd_bank = rd_bank_vbe;
		  wr_bank = wr_bank_vbe;
		  st_disp = st_disp_vbe;
		  break;
	  default:
		  if (RES_800_600 < disp_reso) {
			  switch (chip) {
				case CHIP_WD90C00:
					rd_bank = rd_bank_wd90c00;
					wr_bank = wr_bank_wd90c00;
					st_disp = st_disp_wd90c00;
					break;
				case CHIP_T8900:
					rd_bank = rd_bank_t8900;
					wr_bank = wr_bank_t8900;
					st_disp = st_disp_t8900;
					break;
				case CHIP_86C911:
					rd_bank = rd_bank_86c911;
					wr_bank = wr_bank_86c911;
					st_disp = st_disp_86c911;
					break;
				case CHIP_MACH:
					rd_bank = rd_bank_mach;
					wr_bank = wr_bank_mach;
					st_disp = st_disp_mach;
					break;
			  }
		  }
	}
	if (f_soft_scroll) {
		st_disp = st_disp_vbe;
	}
}

void device_init(DIMENSION *dim)
	/* initialize graphic screen */
{
	uint x, x2, y, y2, i;

	save_palette(SAVE_PAL);
	save_ramdac(SAVE_RAMDAC);

	resume_page(dim);

	org_video_mode = get_vmode_org();	/* save current video mode */

	vga_mode = get_vmode_preview();	/* get video mode for preview */
	switch (vga_mode) {
	  case VMODE_UNKNOWN:
		  error(ILLEGAL_ARGS,
#ifdef JAPANESE
				"%s,%s の画面モードを決定出来ません",
#else
				"I don't know the video mode for %s,%s",
#endif
				iopt2name(chip_name, video_chip),
				iopt2name(reso_name, disp_reso));
		  exit(1);
	  case VMODE_ERR_VESA:
		  error(PROGRAM_STOP,
#ifdef JAPANESE
				"指定された解像度(%s)はサポートされていません",
#else
				"This platform can't handle the VESA BIOS resolution %s",
#endif
				iopt2name(reso_name, disp_reso));
		  exit(1);
	  case VMODE_ERR_HIRESO:
		  error(ILLEGAL_ARGS,
#ifdef JAPANESE
				"SVGA チップが指定されていません [例:-chip=1 (ET4000)]"
#else
				"Please set SVGA chip ``-chip???''\n"
				" to access SVGA hardware.\n"
				" example: `-chip1' for ET4000"
#endif
			  );
		  exit(1);
	  default:
		  break;
	}

	video_isr = 0x03da;
	video_crtc = video_isr - 6;

	set_chip_funcs(video_chip);	/* バンク関数の設定 */

	set_vmode_preview(vga_mode);/* 画面モードの設定 */

	cursor_mode(OFF);
	if (f_ramdac)
		analog_color(&s_color);
	else if (f_reverse)
		pallet(1);

	if (slow_fact < 0)
		slow_fact = 0;
	if (v_shift0 < 0) {
		if ((v_shift1 = -v_shift0) <= 1)
			v_shift1 = 1;
		if (v_shift1 > 8)
			v_shift1 = 8;
		v_shift0 = 0;
	}
	v_shift1 *= 4;

	if (dim->split == 1) {
		x = x_shift + dim->text_width;
		if (x < s_max_width)
			x = s_max_width;
		if (x_shift < 0)
			x -= x_shift;

		y = y_shift + dim->text_height;
		if (y < s_max_height)
			y = s_max_height;
		if (y_shift < 0)
			y -= y_shift;
	}
	else
		return;

	num_view_buf = (leftbuffer() - 0x80L) / view_buf_size;

	f_div = 0;
	if (x < (g_width_b - 12) * 8 * div_fact1 && y < (g_height - 4) * div_fact1) {
		f_div = div_fact1;
	}
	else if (x < (g_width_b - 12) * 8 * div_fact2 && y < (g_height - 4) * div_fact2) {
		f_div = div_fact2;
	}
	if (f_div > 0 && x > (g_width_b - 6) * 4 * f_div) {
		if (f_div == div_fact1)
			f_div2 = div_fact2;
	}
	else
		f_div2 = f_div;

	if (!f_use_new_size_option && s_max_height > 0 && s_max_width > 0)
		f_box = 1;

	if (num_view_buf > MAX_VIEW)
		num_view_buf = MAX_VIEW;
	if (num_view == 0)
		num_view = num_view_buf;
	if (f_div) {
		v_x_shift = x_shift >> sh_fact1;
		v_x_shift2 = x_shift >> sh_fact2;
		v_y_shift = y_shift >> sh_fact1;
		v_y_shift2 = y_shift >> sh_fact2;
		x = x2 = x_org_b;
		y = y2 = y_org;
		if (v_x_shift > 0) {
			x += (v_x_shift >> 3);
			x2 += (v_x_shift2 >> 3);
		}
		if (v_y_shift > 0) {
			y += v_y_shift;
			y2 += v_y_shift2;
		}
		v_top_addr = (ulong)x + (ulong)g_width_b *y;
		v_top_addr2 = (ulong)x2 + (ulong)g_width_b *y2;

		v_top = (BUFFER *)G_TOP + x + g_width_b * y;
		v_top2 = (BUFFER *)G_TOP + x2 + g_width_b * y2;
		if (!f_div2)
			goto nv;
#ifndef	NEMS
		if (num_view < 0 && num_view >= -MAX_VIEW) {
			num_view_buf = -num_view;
			view_EMS_top = system_EMS_end;
			max_user_page -= view_EMS_unit * num_view_buf;
			system_EMS_end += view_EMS_unit * num_view_buf;
			view_buf = pf_addr;
			return;
		}
#endif
		if (num_view > 0) {
			view_buf =
				(HUGE_BUF *)farmalloc(view_buf_size * (num_view = num_view_buf));
		}
	}
	else
	  nv:num_view_buf = 0;
}

void device_clear(OUTPUT_INFO *out)
{
	map_top_ptr = out->bitmap_ptr;
	hmax = out->width;
	vmax = out->height;
	hmaxb = out->byte_width;
	program_screen_mode(ON);
	clear_text_screen();
	cursor_mode(OFF);
}

void device_end()
{
	union REGS regs;
	struct SREGS sregs;

	regs.h.ah = 0x00;
	regs.h.al = org_video_mode & 0xFF;
	int86(V_BIOS, &regs, &regs);
	clear_text_screen();
	program_screen_mode(OFF);
	cursor_mode(ON);
	flskey();
	restore_palette(SAVE_PAL);
	restore_ramdac(SAVE_RAMDAC);
}

void slow(int fact)
{
	int i, j;

	i = (fact < 6) ? 5 : fact;
	while (fact-- > 0) {
		for (j = 0; j < 6 * i; j++)
			slow(0);
	}
}

NEXT_ACTION device_out(OUTPUT_INFO *out, DIMENSION *dim)
{
	static struct SCROLL s_dat;
	KeyInput key;
	ulong i;
	int j;

	s_dat.mptr = map_top_ptr;

	cls(0);
	first_draw(&s_dat, out->page, out->split, dim->split);
	slow(slow_page);

	for (;;) {
		switch (key = extra_inkey()) {
		  case Key_V:
		  case Key_C:
			  if (!f_div)
				  break;
			  cls(0);
			  st_disp(0);
			  i = s_dat.offset;
			  s_dat.offset = 0;
			  if (f_div == div_fact1 && key != Key_C) {
				  switch (disp_reso) {
					case RES_640_480:
					case RES_800_600:
						s_view4_low();
						break;
					case RES_1024_768:
					case RES_1280_1024:
						s_view_hi(2);
						break;
				  }
				  j = get_view(out->page, div_fact1, &s_dat);
			  }
			  else {
				  switch (disp_reso) {
					case RES_640_480:
					case RES_800_600:
						s_view8_low();
						break;
					case RES_1024_768:
					case RES_1280_1024:
						s_view_hi(4);
						break;
				  }
				  j = get_view(out->page, div_fact2, &s_dat);
			  }
			  s_dat.offset = i;
			  cls(0);
			  if (j) goto disp_m;
disp_0:		  draw_screen(&s_dat);
			  slow(slow_page);
			  break;
		  case Key_R:
			  if (!f_ramdac)
				  pallet((f_reverse = TRUE - f_reverse) == TRUE ? 1 : 0);
			  slow(slow_fact);
			  break;
		  case Key_H:
			  st_disp(0);
			  msg_help();
			  st_disp(s_dat.offset);
			  goto disp_0;
		  case Key_G:
		  case Key_M:
disp_m:		  unit_pages -= out->page;
			  s_dat.mptr = map_top_ptr;
			  first_draw(&s_dat, out->page, out->split, dim->split);
			  return ((NEXT_ACTION)Key_N);
		  case ArrowLeft:
			  if (f_soft_scroll) {
				  move_screen(&s_dat, key, sh_h_fact);
				  flskey();
			  }
			  else {
				  scroll_right(&s_dat, dim, FALSE);
			  }
			  slow(v_shift0);
			  break;
		  case ArrowRight:
			  if (f_soft_scroll) {
				  move_screen(&s_dat, key, sh_h_fact);
				  flskey();
			  }
			  else {
				  scroll_left(&s_dat, dim, FALSE);
			  }
			  slow(slow_fact2);
			  break;
		  case ArrowUp:
			  if (f_soft_scroll) {
				  move_screen(&s_dat, key, sh_v_fact);
				  flskey();
			  }
			  else {
				  scroll_down(&s_dat, dim, FALSE);
			  }
			  slow(v_shift0);
			  break;
		  case ArrowDown:
			  if (f_soft_scroll) {
				  move_screen(&s_dat, key, sh_v_fact);
				  flskey();
			  }
			  else {
				  scroll_up(&s_dat, dim, FALSE);
			  }
			  slow(slow_fact2);
			  break;
		  case ShiftUp:
			  if (f_soft_scroll) {
				  move_screen(&s_dat, key, 3);
				  flskey();
			  }
			  else {
				  scroll_down(&s_dat, dim, TRUE);
			  }
			  slow(slow_fact);
			  break;
		  case ShiftDown:
			  if (f_soft_scroll) {
				  move_screen(&s_dat, key, 3);
				  flskey();
			  }
			  else {
				  scroll_up(&s_dat, dim, TRUE);
			  }
			  slow(slow_fact);
			  break;
		  case ShiftLeft:
			  if (f_soft_scroll) {
				  move_screen(&s_dat, key, 3);
				  flskey();
			  }
			  else {
				  scroll_right(&s_dat, dim, TRUE);
			  }
			  slow(slow_fact);
			  break;
		  case ShiftRight:
			  if (f_soft_scroll) {
				  move_screen(&s_dat, key, 3);
				  flskey();
			  }
			  else {
				  scroll_left(&s_dat, dim, TRUE);
			  }
			  slow(slow_fact);
			  break;
		  default:
			  s_dat.mptr = map_top_ptr;
			  first_draw(&s_dat, out->page, out->split, dim->split);
			  return ((NEXT_ACTION)key);
		}
	}
}

static void first_draw(struct SCROLL *s_dat, int page, int split_page, int split)
	/* at first, set variables and  put to screen */
{
	if (f_resume)
		poke(BIOS_WORK, RESUME_BASE + 3, page);

	s_dat->x_lock = s_dat->y_lock = FALSE;
	s_dat->offset = 0;
	if (hmaxb < g_width_b) {	/* 横スクロールしない */
		s_dat->gw_act = hmaxb;
		s_dat->x_lock = TRUE;
	}
	else {
		s_dat->gw_act = g_width_b;
	}

	if (vmax < g_height) {		/* 縦スクロールしない */
		s_dat->gh_act = vmax;
		s_dat->y_lock = TRUE;
	}
	else {
		s_dat->gh_act = g_height;
	}

	/* センタリング用オフセット値の算出 */
	s_dat->h_spc = (g_width_b - s_dat->gw_act) / 2;
	s_dat->v_spc = (split == 1) ? (g_height - s_dat->gh_act) / 2 : 0;

	g_top_ptr = (ulong)s_dat->v_spc * g_width_b;
	s_dat->x = -s_dat->h_spc;
	s_dat->y = -s_dat->v_spc;

	st_disp(s_dat->offset);
	draw_screen(s_dat);

	if (f_bios_out) {
		fprintf(stderr, "\x1b[1;1H");
		fprintf(stderr, ((split > 1) ? "[%d-%d]" : "[%d]"), page, split_page);
	}
}


static int get_divscan(
						struct DIVSCAN far ** dvp,
						long length,
						uint width
)
{
	int lines;
	uint mod1, mod2;

	lines = length / width;
	if (lines) {
		(*dvp)->div = FALSE;
		(*dvp)->lines = lines;
		(*dvp)->len1 = width;
		(*dvp)->len2 = 0;
		(*dvp)++;
	}
	mod1 = length % width;
	mod2 = mod1 ? (width - mod1) : 0;
	if (mod1) {
		(*dvp)->div = TRUE;
		(*dvp)->lines = 1;
		(*dvp)->len1 = mod1;
		(*dvp)->len2 = mod2;
		(*dvp)++;
	}
	return mod2;
}

/* スキャンラインをバンク毎にグループ分けする */
static void div_scanline(
							struct DIVSCAN far * dvp,
							ulong start,
							uint width,
							uint height
)
{
	ulong end;
	long len, s_bank_len, e_bank_len;
	uint s_bank, e_bank, modulo;
	int banks;
	BOOL f_bank = FALSE;

	len = (long)width *height;
	if (0 < len) {
		end = start + len - 1;
		s_bank = start / SIZE64KB;
		e_bank = end / SIZE64KB;

		if (s_bank+1 <= e_bank) {
			s_bank_len = ((s_bank + 1) * SIZE64KB - start) % SIZE64KB;
			e_bank_len = (end + 1) % SIZE64KB;
			banks = (len - s_bank_len - e_bank_len) / SIZE64KB;
		}
		else {
			s_bank_len = len;
		}

		modulo = 0;
		if (s_bank_len) {
			modulo = get_divscan( &dvp, s_bank_len, width);
		}
		if (s_bank+1 <= e_bank) {
			while (banks--) {
				modulo = get_divscan( &dvp, SIZE64KB - modulo, width);
			}
			modulo = get_divscan( &dvp, e_bank_len - modulo, width);
		}
	}
	dvp->div = FALSE;
	dvp->lines = 0;
	dvp->len1 = 0;
	dvp->len2 = 0;
}

/* スキャンライン・グループに対応する VRAM を消去する
 */
static void clear_block(ulong gadr, struct DIVSCAN far * tp, int lines)
{
	uint i, j;
	BUFFER *p;

	while (tp->lines) {
		p = wr_bank(gadr);
		if (tp->div) {
			/* スキャンライン分割有り */
			for (i = tp->len1; i--;) {
				*p++ = 0;
			}
			p = wr_bank(gadr + tp->len1);
			for (i = tp->len2; i--;) {
				*p++ = 0;
			}
		}
		else {
			/* スキャンライン分割無し */
			for (i = tp->lines; i--;) {
				for (j = tp->len1; j--;) {
					*p++ = 0;
				}
			}
		}
		gadr += (ulong)g_width_b *tp->lines;

		tp++;
	}
}

/* スキャンライン描画
 */
static void draw_line(HUGE_BUF *src, ulong dst, int len, struct SCROLL *s_dat)
{
	struct DIVSCAN far *tp = divscan_tbl;
	BUFFER *gp;

	div_scanline(tp, dst, len, 1);
	if (tp->div) {
		draw_line_banked(src, dst, tp, s_dat);
	}
	else {
		gp = wr_bank(dst);
		movedata(FP_SEG(src), FP_OFF(src),
				 G_SEG, FP_OFF(gp), tp->len1);
	}
}

/* バンクにまたがるスキャンライン上にビットマップを展開する
 */
static void draw_line_banked(
								HUGE_BUF *mp,
								ulong gadr,
								struct DIVSCAN far * tp,
								struct SCROLL *s_dat
)
{
	BUFFER *gp;
	HUGE_BUF *bp;
	uint i;
	uchar buff[G_WIDTH_B_MAX];

	bp = buff;
	if (s_dat->x_lock) {
		for (i = s_dat->h_spc; i--;) {
			*bp++ = 0;
		}
	}
	movedata(FP_SEG(mp), FP_OFF(mp), FP_SEG(bp), FP_OFF(bp), s_dat->gw_act);
	if (s_dat->x_lock) {
		bp += s_dat->gw_act;
		for (i = g_width_b - (s_dat->gw_act + s_dat->h_spc); i--;) {
			*bp++ = 0;
		}
	}
	bp = buff;
	gp = wr_bank(gadr);
	movedata(FP_SEG(bp), FP_OFF(bp), FP_SEG(gp), FP_OFF(gp), tp->len1);
	bp += tp->len1;
	gadr += tp->len1;
	gp = wr_bank(gadr);
	movedata(FP_SEG(bp), FP_OFF(bp), FP_SEG(gp), FP_OFF(gp), tp->len2);
}

/* スキャンライン・グループに対応する VRAM へ
 * ビットマップ・バッファの内容を転送する
 */
static void draw_block(ulong gadr, struct DIVSCAN far * tp, struct SCROLL *s_dat)
{
	HUGE_BUF *mp = s_dat->mptr;
	BUFFER *gp;
	uint i, j;
	uint rmargin = 0;

	/* 横スクロール・ロック時の右余白長算出 */
	if (s_dat->x_lock) {
		rmargin = g_width_b - (s_dat->h_spc + s_dat->gw_act);
	}

	while (tp->lines) {
		if (tp->div) {			/* バンクに跨るスキャンライン ? */
			draw_line_banked(mp, gadr, tp, s_dat);
			mp += hmaxb;
		}
		else {
			gp = wr_bank(gadr);	/* VRAM バンクを設定 */
			if (s_dat->x_lock) {
				for (i = tp->lines; i--;) {
					/* 左余白消去 */
					for (j = s_dat->h_spc; j--;) {
						*gp++ = 0;
					}
					/* ビットマップ転送 */
					movedata(FP_SEG(mp), FP_OFF(mp),
							 FP_SEG(gp), FP_OFF(gp), s_dat->gw_act);
					gp += s_dat->gw_act;
					/* 右余白消去 */
					for (j = rmargin; j--;) {
						*gp++ = 0;
					}
					mp += hmaxb;
				}
			}
			else {
				/* ビットマップ転送 */
				for (i = tp->lines; i--;) {
					movedata(FP_SEG(mp), FP_OFF(mp),
							 FP_SEG(gp), FP_OFF(gp), g_width_b);
					gp += g_width_b;
					mp += hmaxb;
				}
			}
		}
		gadr += (ulong)g_width_b *tp->lines;

		tp++;
	}
}

static void draw_screen(struct SCROLL *s_dat)
{
	ulong gadr = g_top_ptr;
	struct DIVSCAN far *tp = divscan_tbl;
	int amount_h;

	if (!f_soft_scroll) {
		gadr += s_dat->offset;
	}

	/* 上側余白部の消去 */
	if (s_dat->y_lock) {
		div_scanline(tp, gadr, g_width_b, s_dat->v_spc);
		clear_block(gadr, tp, s_dat->v_spc);
		gadr += (ulong)g_width_b *s_dat->v_spc;
	}

	/* ビットマップバッファの内容を VRAM に展開する */
	div_scanline(tp, gadr, g_width_b, s_dat->gh_act);
	draw_block(gadr, tp, s_dat);
	gadr += (ulong)g_width_b *s_dat->gh_act;

	/* 下側余白部の消去 */
	if (s_dat->y_lock) {
		amount_h = g_height - (s_dat->v_spc + s_dat->gh_act);
		div_scanline(tp, gadr, g_width_b, amount_h);
		clear_block(gadr, tp, amount_h);
	}

	if (!f_soft_scroll) {
		st_disp(s_dat->offset);
	}
}

void device_pause()
{
}

void device_cont()
{
}

static void clear_text_screen(void)
	/* clear text screen */
{
	if (f_bios_out) {
		fprintf(stderr, "\x1b" "*");
	}
}

static void program_screen_mode(int mode)
	/* to use the bottom line for user */
{
}

static void cursor_mode(int flg)
	/* Cursor on or off */
{
	_setcursortype(flg ? _NORMALCURSOR : _NOCURSOR);
}

static uchar bit_mask[8] =
{
	0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
};

static uchar view8_hi_mask[8] =
{
	0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f
};

static uchar view4_hi_mask[8] =
{
	0xc0, 0x30, 0x0c, 0x03, 0xc0, 0x30, 0x0c, 0x03
};

/* 表示枠描画 (低解像度用) */
static void v_box_low(BUFFER *orig, int x, int y, int w, int h)
	/*	オリジナルdvioutのまま 	*/
{
	BUFFER *ptr;
	int i, b, ch, dif;

	orig += (x + g_width_b * 8) / 8 + g_width_b * (y - 1);
	b = (x & 7);

	ch = (1 << (7 - b));
	ptr = orig;
	for (i = h; i > 0; ptr += g_width_b, i--)	/* draw left line */
		*ptr |= ch;

	dif = g_width_b * (h - 1);
	ch = (0xff >> b);
	ptr = orig;
	*(ptr + dif) |= ch;
	*ptr |= ch;

	ptr++;
	for (i = w + b - 8; i > 8; ptr++, i -= 8) {	/* draw top and botom line */
		*ptr |= 0xff;
		*(ptr + dif) |= 0xff;
	}
	ch = (0xff << (8 - i));
	*ptr |= ch;
	*(ptr + dif) |= ch;

	ch = (1 << (8 - i));
	for (i = h; i > 0; ptr += g_width_b, i--)	/* draw right line */
		*ptr |= ch;
}

/* 表示枠描画 (高解像度用) */
static void v_box_hi(ulong orig, int x, int y, int w, int h)
{
	ulong temp, dif;
	int i, b, ch;
	uchar vdata;

	orig += (x + (ulong)g_width_b * 8) / 8 + (ulong)g_width_b *(y - 1);

	b = (x & 7);

	ch = (1 << (7 - b));
	temp = orig;
	for (i = h; i > 0; temp += g_width_b, i--) {	/* draw left line */
		*wr_bank(temp) = *rd_bank(temp) | ch;
	}

	dif = (ulong)g_width_b *(h - 1);

	ch = (0xff >> b);
	temp = orig;
	*wr_bank(temp) = *rd_bank(temp) | ch;
	*wr_bank(temp + dif) = *rd_bank(temp + dif) | ch;

	temp++;
	for (i = w + b - 8; i > 8; temp++, i -= 8) {	/* draw top and botom line */
		*wr_bank(temp) = *rd_bank(temp) | 0xff;
		*wr_bank(temp + dif) = *rd_bank(temp + dif) | 0xff;
	}
	ch = (0xff << (8 - i));
	*wr_bank(temp) = *rd_bank(temp) | ch;
	*wr_bank(temp + dif) = *rd_bank(temp + dif) | ch;

	ch = (1 << (8 - i));
	for (i = h; i > 0; temp += g_width_b, i--) {	/* draw right line */
		*wr_bank(temp) = *rd_bank(temp) | ch;
	}
}

/* 縮小表示 */
static void s_view_hi(int factor)
{
	int hmax_tmp, f2, f3, h, v, vm, x, bx, y;
	uchar ch;
	HUGE_BUF *mptr;
	BUFFER *mtmp;
	BUFFER *mp;
	ulong gadr, gtmp;
	uchar far *mskp;

	mptr = map_top_ptr;
	f2 = 8 / factor;
	switch (factor) {
	  case 2:
		  gadr = v_top_addr;
		  mskp = view4_hi_mask;
		  break;
	  case 4:
		  gadr = v_top_addr2;
		  mskp = view8_hi_mask;
		  break;
	}

	for (v = 0; v < vmax; v += factor) {
		vm = ((vmax - v) < factor) ? vmax - v : factor;
		mtmp = (BUFFER *)mptr;
		gtmp = gadr;
		for (h = 0; h < hmaxb; h += factor) {
			ch = 0;
			f3 = ((hmaxb - h) < factor) ? hmaxb - h : factor;
			for (x = 0; x < f3; x++) {
				mp = mtmp++;
				for (y = vm; y--;) {
					if (*mp) {
						for (bx = 0; bx < f2; bx++) {
							if (*mp & mskp[bx]) {
								ch |= bit_mask[x * f2 + bx];
							}
						}
					}
					mp += hmaxb;
				}
			}
			if (ch) {
				*wr_bank(gtmp) = ch;
			}
			gtmp++;
		}
		mptr += (ulong)hmaxb *factor;

		gadr += (ulong)g_width_b;
	}
	if (f_box) {
		v_box_hi(v_top_addr, -v_x_shift, -v_y_shift,
				 s_max_width / factor, s_max_height / factor);
	}
}

static void s_view8_low(void)
	/*	オリジナルdvioutのまま 	*/
{
	int i, j, vm, v;
	uint ch, gptr_off, x, y;
	HUGE_BUF *ptr0;
	BUFFER *ptr1;
	BUFFER *ptr;
	BUFFER *ptrg0;
	BUFFER *ptrg;

	ptrg0 = v_top2;
	ptr0 = map_top_ptr;

	for (ch = v = 0; v < vmax; v += 8) {
		vm = ((vmax - v) < 8) ? vmax - v : 8;
		ptr1 = (BUFFER *)ptr0;
		ptr0 += (hmaxb << 3);
		ptrg = ptrg0;
		ptrg0 += g_width_b;
		for (i = 0; i < hmaxb; i++) {
			ptr = ptr1++;
			for (j = vm; j != 0; j--) {
				if (*ptr != 0) {
					ch |= 1;
					break;
				}
				ptr += hmaxb;
			}
			if ((i & 7) == 7)
				*ptrg++ = ch;
			ch += ch;
		}
		if ((i = (hmaxb & 7)) != 0)
			*ptrg++ = (ch << (7 - i));
	}
	if (f_box)
		v_box_low(v_top2, -v_x_shift2, -v_y_shift2,
				  s_max_width / 8, s_max_height / 8);
}

static void s_view4_low(void)
	/*	オリジナルdvioutのまま 	*/
{
	int i, j, vm, v;
	uint ch, gptr_off, x, y;
	HUGE_BUF *ptr0;
	BUFFER *ptr1;
	BUFFER *ptr;
	BUFFER *ptrg0;
	BUFFER *ptrg;

	ptrg0 = v_top;
	ptr0 = map_top_ptr;

	for (ch = v = 0; v < vmax; v += 4) {
		vm = ((vmax - v) < 4) ? vmax - v : 4;
		ptr1 = (BUFFER *)ptr0;
		ptr0 += (hmaxb << 2);
		ptrg = ptrg0;
		ptrg0 += g_width_b;
		for (i = 0; i < hmaxb; i++) {
			ptr = ptr1++;
			for (j = vm; j != 0; j--) {
				if ((*ptr & 0xf0) != 0) {
					ch |= 2;
				}
				if ((*ptr & 0x0f) != 0) {
					ch |= 1;
				}
				if ((ch & 3) == 3)
					break;
				ptr += hmaxb;
			}
			if ((i & 3) == 3)
				*ptrg++ = ch;
			ch <<= 2;
		}
		if ((i = (hmaxb & 3)) != 0)
			*ptrg++ = (ch << ((3 - i) * 2));
	}
	if (f_box)
		v_box_low(v_top, -v_x_shift, -v_y_shift,
				  s_max_width / 4, s_max_height / 4);
}

static void msg_help(void)
	/*	オリジナルのdvioutに加えて,PgUp,PgDn,Qを追加、[HELP]を削除	*/
{
	cls(0);
	if (!f_ramdac && f_reverse)
		pallet(0);

	fprintf(stderr,
			"\x1b" "[5;H"
			"\t\t\t\t<<< Help >>>\n\n"
			"\t(SHIFT + ) Cursor \t: Scroll in a page\n"
			"\tN, PgDn\t\t\t: Next page\n"
			"\tP, B, PgUp\t\t: Previous page\n"
			"\tG, M, \t\t\t: Move to a page\n"
			"\tnumbers\t\t\t: pages (skipped) by next B, G, M, N, P\n"
			"\tSpace\t\t\t: Next part\n"
			"\tBack Space\t\t: Previous part\n"
			"\tC, V\t\t\t: (Compressed) View outline\n"
			"\tR\t\t\t: Reverse Screen(toggle)\n"
			"\tH\t\t\t: Help message\n"
			"\tReturn\t\t\t: Next block or quit\n"
			"\tQ,Escape\t\t: Quit\n"
		);
	/*
	directvideo=0;
	cprintf(
		"\r\n\n\n\n"
		"                        <<< Help >>>\r\n\r\n"
		"     (SHIFT + ) \x1b \x1a \x18 \x19   : Scroll in a page\r\n"
		"         N, PgDn          : Next page\r\n"
		"         P, B, PgUp       : Previous page\r\n"
		"         G, M,            : Move to a page\r\n"
		"         numbers          : pages (skipped) by next B, G, M, N, P\r\n"
		"         Space            : Next part\r\n"
		"         Back Space       : Previous part\r\n"
		"         C, V             : (Compressed) View outline (cf. -W -H options)\r\n"
		"         R                : Reverse Screen(toggle)\r\n"
		"         H                : Help message\r\n"
		"         Return           : Next block or quit\r\n"
		"         Q,Escape         : Quit\r\n"
	);
*/
	flskey();
	getch();
	if (!f_ramdac && f_reverse)
		pallet(1);

	fprintf(stderr, "\x1b" "[5;H" "\x1b" "[0J" "\x1e");
}

static void cls(int mode)
{
	struct DIVSCAN far *tp = divscan_tbl;

	div_scanline(tp, 0, g_width_b, g_height);
	clear_block(0, tp, g_height);

	wr_bank(0);
}

static int get_view(int page, int div, struct SCROLL *s_dat)
{
	int i, pt, pg;
	HUGE_BUF *buf_tmp;
	ulong gptr_tmp;
	BUFFER *p;
	union REGS regs;

	pg = -1;
	wr_bank(s_dat->offset);
	if (f_bios_out) {
		fprintf(stderr, "\x1b" "[0;0H" "[%d]" "\x1b" "[0;0H", page);
	}

	if (num_view_buf <= 0) {
		regs.h.ah = 0;
		int86(0x16, &regs, &regs);
/*		bioskey(0);	*/
		return 0;
	}
	pt = view.current;
	while (TRUE) {
		if (div != f_div2)
			goto skip;
		if (--pt < 0)
			pt = view.total - 1;
		if (pt >= 0) {
#ifndef	NEMS
			if (view_EMS_unit) {
				buf_tmp = view_buf;
				set_ems(view_EMS_top + view_EMS_unit * pt);
			}
			else
#endif
				buf_tmp = view_buf + view_buf_size * pt;
			gptr_tmp = (ulong)(g_width_b / 2);

			for (i = g_height; i > 0; i--) {
				draw_line(buf_tmp, gptr_tmp, g_width_b / 2, s_dat);
				buf_tmp += g_width_b / 2;
				gptr_tmp += g_width_b;
			}
			pg = view.page[pt];
		}
skip:	flskey();
		regs.h.ah = 0;
		int86(0x16, &regs, &regs);
		i = regs.h.al;
/*		i = (uchar)bioskey(0);	*/
		if (i != 'm' && i != 'M' && i != 'ﾓ')
			pg = -1;
		if (i != 'v' && i != 'V' && i != 'ﾋ')
			break;
	}
	wr_bank(s_dat->offset);
	if (f_bios_out) {
		fprintf(stderr, "\x1a" "[%d]", page);
	}

	if (div != f_div2)
		return 0;
	for (pt = view.total; pt > 0;) {
		if (view.page[--pt] == page) {
			view.current = pt + 1;
			goto nxt;
		}
	}
	if (view.total < num_view_buf)
		view.current = view.total++;
	if (view.current >= num_view_buf)
		view.current = 0;
	view.page[view.current] = page;

	gptr_tmp = 0L;
#ifndef	NEMS
	if (view_EMS_unit) {
		buf_tmp = view_buf;
		set_ems(view_EMS_top + view_EMS_unit * view.current);
	}
	else
#endif
		buf_tmp = view_buf + view_buf_size * view.current;
	view.current++;
	for (i = g_height; i > 0; i--) {
		p = rd_bank(gptr_tmp);
		movedata(G_SEG, FP_OFF(p),
				 FP_SEG(buf_tmp), FP_OFF(buf_tmp), g_width_b / 2);
		buf_tmp += g_width_b / 2;
		gptr_tmp += g_width_b;
	}
nxt:
	if (pg >= 0)
		unit_pages = pg;
	return pg+1;

}

static void move_screen(struct SCROLL *s_dat, KeyInput direct, int step)
	/*	ビットマップを、縦横それぞれのサイズの1/2^step分(SHIFT + cursor)または
	step dotだけメモリー転送によりスクロールする */
{
	int x_step, y_step;

	x_step = y_step = 0;
	switch (direct) {
	  case ArrowDown:
		  if (f_soft_scroll)
			  y_step = step;
		  break;
	  case ShiftDown:
		  y_step = (s_dat->gh_act) >> step;
		  break;
	  case ArrowUp:
		  if (f_soft_scroll)
			  y_step = -step;
		  break;
	  case ShiftUp:
		  y_step = -((s_dat->gh_act) >> step);
		  break;
	  case ArrowLeft:
		  if (f_soft_scroll)
			  x_step = -step;
		  break;
	  case ShiftLeft:
		  x_step = -((s_dat->gw_act) >> step);
		  break;
	  case ArrowRight:
		  if (f_soft_scroll)
			  x_step = step;
		  break;
	  case ShiftRight:
		  x_step = (s_dat->gw_act) >> step;
		  break;
	}

	if (x_step != 0 && !s_dat->x_lock) {
		if (s_dat->x + x_step <= 0)
			x_step = -s_dat->x;
		if (s_dat->x + x_step + g_width_b >= hmaxb)
			x_step = hmaxb - (s_dat->x + g_width_b);
		x_step &= 0xfffe;
		s_dat->x += x_step;
		s_dat->mptr += x_step;
	}
	else if (y_step != 0 && !s_dat->y_lock) {
		if (s_dat->y + y_step <= 0)
			y_step = -s_dat->y;
		if (s_dat->y + y_step + g_height >= vmax)
			y_step = vmax - (s_dat->y + g_height);
		s_dat->y += y_step;
		s_dat->mptr += (long)y_step *(ulong)hmaxb;
	}
	draw_screen(s_dat);
}

static void pallet(int mode)
{
	union REGS regs;

	if (mode == 1) {			/*	exchange black and white	*/
		save_palette(SAVE_REV_PAL);

		/* foreground color */
		regs.x.ax = 0x1000;
		regs.h.bl = 0x0f;
		regs.h.bh = 0x00;
		int86(V_BIOS, &regs, &regs);
		regs.x.ax = 0x1000;
		regs.h.bl = 0x07;
		regs.h.bh = 0x00;
		int86(V_BIOS, &regs, &regs);

		/* background color */
		regs.x.ax = 0x1000;
		regs.h.bl = 0x00;
		regs.h.bh = 0xff;
		int86(V_BIOS, &regs, &regs);
	}
	else {						/*	reset palette setting	*/
		restore_palette(SAVE_REV_PAL);
	}
}

static void flskey(void)
	/*  flush key-buffer. by calling dos function */
{
	bdos(12, 0, 0);
}

/* WD90CXX 拡張レジスタのアクセス・ロック解除処理 */
static void enable_wd_ext(void)
{
	outport(0x3ce, 0x050f);	/*  Extensions on */
	outport(0x3d4, 0x8529);		/*  unlock PR10-PR17  */
	outport(0x3c4, 0x4806);		/*  unlock extended sequencer */
}

static void paradise_on(void)
{	   /*	For Paradise WD100X Chip Set board 	*/
	unsigned char x;

	enable_wd_ext();

	/*  single bank mode */
	outportb(0x3c4, 0x11);
	x = inportb(0x3c5) & 0x7f;
	outportb(0x3c5, x);

	outportb(0x3ce, 0x0b);
	x = (inportb(0x3cf) & 0xf7) & 0x3f;
	outportb(0x3cf, x);
}

/* WD90CXX の表示開始アドレスが 64KB で折り返るようにする */
static void fix_wd_disp(void)
{
	unsigned char x;

	enable_wd_ext();

	outportb(video_crtc, 0x2F);
	x = inportb(video_crtc + 1) & 0x9F;
	outportb(video_crtc + 1, x | 0x40);
}

/*
 * 垂直帰線区間待ち
 */
static void wait_vret(void)
{
	while ((inportb(INPUT_STATUS1) & V_RETRACE) == 0) {
	};
}

/*---------------------------------------------------------
 * ビデオチップ依存の処理関数
 *   rd_bank_.*() : リードバンク設定関数
 *   wr_bank_.*() : ライトバンク設定関数
 *   st_addr_.*() : 表示開始アドレス設定関数
 */

/*---------------------------------------------------------
 * for standard VGA
 */
static BUFFER *rd_bank_vga(ulong address)
{
	return (BUFFER *)G_TOP + (uint)address;
}

static BUFFER *wr_bank_vga(ulong address)
{
	return (BUFFER *)G_TOP + (uint)address;
}

static void st_disp_vga(ulong start_addr)
{
	outport(video_crtc, ((uint)start_addr & 0xff00) | START_ADDRESS_HIGH);
	outport(video_crtc, ((uint)start_addr << 8) | START_ADDRESS_LOW);
}

/*---------------------------------------------------------
 * for Tseng Lab. ET4000
 */
static BUFFER *rd_bank_et4000(ulong address)
{
	uchar portb;

	portb = inportb(ET4000_BANK_PORT) & 0x0f;
	portb |= (uchar)(address >> 12) & 0xf0;
	outportb(ET4000_BANK_PORT, portb);

	return (BUFFER *)G_TOP + (uint)address;
}

static BUFFER *wr_bank_et4000(ulong address)
{
	uchar portb;

	portb = inportb(ET4000_BANK_PORT) & 0xF0;
	portb |= (uchar)(address >> 16) & 0x0F;
	outportb(ET4000_BANK_PORT, portb);

	return (BUFFER *)G_TOP + (uint)address;
}

static void st_disp_et4000(ulong start_addr)
{
	uint dad;

	st_disp_vga(start_addr);

	outportb(video_crtc, ET4000_DISP_INDEX);
	dad = ((uint)inportb(video_crtc + 1) & 0x00FC) << 8;
	dad |= (uint)((start_addr & 0x00030000L) >> 8);
	outport(video_crtc, dad | ET4000_DISP_INDEX);
}

/*---------------------------------------------------------
 * for Western Digital WD90C00
 */
static BUFFER *rd_bank_wd90c00(ulong address)
{
	uint portw;

	portw = (uint)(address >> 4) & 0x7000;
	outport(WD90C00_BANK_PORT, portw | WD90C00_BANK_INDEX);

	return (BUFFER *)G_TOP + (uint)address;
}

static BUFFER *wr_bank_wd90c00(ulong address)
{
	uint portw;

	portw = (uint)(address >> 4) & 0x7000;
	outport(WD90C00_BANK_PORT, portw | WD90C00_BANK_INDEX);

	return (BUFFER *)G_TOP + (uint)address;
}

static void st_disp_wd90c00(ulong start_addr)
{
	uint dad;

	st_disp_vga(start_addr);

	outportb(WD90C00_GRC_PORT, WD90C00_DISP_INDEX);
	dad = ((uint)inportb(WD90C00_GRC_PORT + 1) << 8) & 0xE700;
	dad |= (uint)(start_addr >> 5) & 0x1800;
	outport(WD90C00_GRC_PORT, dad | WD90C00_DISP_INDEX);
}

/*---------------------------------------------------------
 * for Trident T8900
 */
static BUFFER *rd_bank_t8900(ulong address)
{
	uint offset = (uint)address;
	uint portw;

	portw = (uint)(address >> 8) & 0xFF00;
	outport(0x3c4, 0x000b);
	inport(0x3c4);
	outport(T8900_BANK_PORT, portw | T8900_BANK_INDEX);

	return (BUFFER *)G_TOP + offset;
}

static BUFFER *wr_bank_t8900(ulong address)
{
	uint portw;

	portw = (uint)(address >> 8) & 0x0F00;
	portw ^= 0x0200;
	outport(0x3c4, 0x000b);
	inport(0x3c4);
	outport(T8900_BANK_PORT, portw | T8900_BANK_INDEX);

	return (BUFFER *)G_TOP + (uint)address;
}

static void st_disp_t8900(ulong start_addr)
{
	uint dad;

	st_disp_vga(start_addr);

	outportb(video_crtc, T8900_DISP_INDEX);
	dad = ((uint)inportb(video_crtc + 1) << 8) & 0xDF00;
	dad |= (uint)(start_addr >> 3) & 0x2000;
	dad |= 0x8000;
	outport(video_crtc, dad | T8900_DISP_INDEX);
}

/*---------------------------------------------------------
 * for S3 86C911
 */
static BUFFER *rd_bank_86c911(ulong address)
{
	uchar portb;

	outportb(video_crtc, S86C911_S3_LOCK);
	outportb(video_crtc + 1, 0x48);

	outportb(video_crtc, S86C911_BANK_INDEX);
	portb = inportb(video_crtc + 1) & 0xF0;
	portb |= (uchar)(address >> 14) & 0x0C;
	outportb(video_crtc + 1, portb);

	outportb(video_crtc, S86C911_S3_LOCK);
	outportb(video_crtc + 1, 0xB7);

	return (BUFFER *)G_TOP + (uint)address;
}

static BUFFER *wr_bank_86c911(ulong address)
{
	uchar portb;

	outportb(video_crtc, S86C911_S3_LOCK);
	outportb(video_crtc + 1, 0x48);

	outportb(video_crtc, S86C911_BANK_INDEX);
	portb = inportb(video_crtc + 1) & 0xF0;
	portb |= (uchar)(address >> 14) & 0x0C;
	outportb(video_crtc + 1, portb);

	outportb(video_crtc, S86C911_S3_LOCK);
	outportb(video_crtc + 1, 0xB7);

	return (BUFFER *)G_TOP + (uint)address;
}

static void st_disp_86c911(ulong start_addr)
{
	uint dad;

	st_disp_vga(start_addr);

	/* Unlock S3R? register */
	outportb(video_crtc, S86C911_S3_LOCK);
	outportb(video_crtc + 1, 0x48);

	outportb(video_crtc, 0x32);
	dad = ((uint)inportb(video_crtc + 1) & 0x00BF) << 8;
	outport(video_crtc, dad | 0x32);

	outportb(video_crtc, S86C911_DISP_INDEX);
	dad = ((uint)inportb(video_crtc + 1) & 0x00CF) << 8;
	dad |= (uint)((start_addr & 0x00030000L) >> 4);
	outport(video_crtc, dad | S86C911_DISP_INDEX);

	/* Lock S3R? register */
	outportb(video_crtc, S86C911_S3_LOCK);
	outportb(video_crtc + 1, 0xB7);
}

/*---------------------------------------------------------
 * for ATI Mach[8|32]
 */
static BUFFER *rd_bank_mach(ulong address)
{
	uint portw;

	portw = (uint)(address >> 16) & 0x07;
	portw <<= 9;
	portw |= (portw << 4) | 0xb2;
	outport(MACH_CMD_PORT, portw);

	return (BUFFER *)G_TOP + (uint)address;
}

static BUFFER *wr_bank_mach(ulong address)
{
	uint portw;

	portw = (uint)(address >> 16) & 0x07;
	portw <<= 9;
	portw |= (portw << 4) | 0xb2;
	outport(MACH_CMD_PORT, portw);

	return (BUFFER *)G_TOP + (uint)address;
}

static void st_disp_mach(ulong start_addr)
{
	uint dad;

	st_disp_vga(start_addr);

	if (start_addr == 0L)
		*wr_bank_mach(0x0000L);
}

/*---------------------------------------------------------
 * for VESA BIOS Extension
 */
static BUFFER *rd_bank_vbe(ulong address)
{
	union REGS iregs, oregs;
	struct SREGS sregs;
	struct VBE_ACT_INFO far *vp = &vbe_act;

	iregs.x.ax = 0x4f05;
	iregs.h.bh = 0x00;
	iregs.h.bl = vp->rd_win;
	iregs.x.dx = address / vp->granula;
	int86(V_BIOS, &iregs, &oregs);

	return vp->rd_top + address % vp->granula;
}

static BUFFER *wr_bank_vbe(ulong address)
{
	union REGS iregs, oregs;
	struct SREGS sregs;
	struct VBE_ACT_INFO far *vp = &vbe_act;

	iregs.x.ax = 0x4f05;
	iregs.h.bh = 0x00;
	iregs.h.bl = vp->wr_win;
	iregs.x.dx = (uint)(address / vp->granula);
	int86(V_BIOS, &iregs, &oregs);

	return vp->wr_top + address % vp->granula;
}

static void st_disp_vbe(ulong start_addr)
{
}

/* VBE 情報の取得 */
static int get_vbe_info(struct VBE_INFO far * tbl)
{
	union REGS iregs, oregs;
	struct SREGS sregs;

	iregs.x.ax = 0x4f00;
	sregs.es = FP_SEG(tbl);
	iregs.x.di = FP_OFF(tbl);
	int86x(0x10, &iregs, &oregs, &sregs);

	return (oregs.x.ax == 0x004F);
}

/* VBE 画面モード別情報の取得 */
static int get_vbe_mode_info(uint mode, struct VBE_MODE_INFO far * vmp)
{
	union REGS iregs, oregs;
	struct SREGS sregs;

	/* VBE 画面モード別情報を取得 */
	iregs.x.ax = 0x4f01;
	sregs.es = FP_SEG(vmp);
	iregs.x.di = FP_OFF(vmp);
	iregs.x.cx = mode;
	int86x(0x10, &iregs, &oregs, &sregs);

	return (oregs.x.ax == 0x004F);
}

/* VBE 識別子の照合 */
static BOOL vbe_sign(char far * p)
{
	char far *q;
	int i;
	BOOL result = TRUE;

	q = vesa_id;
	for (i = 4; i; i--) {
		if (*p++ != *q++) {
			result = FALSE;
			break;
		}
	}
	return result;
}

/* 解像度から VBE 画面モードへの変換 */
static uint reso2vesa(int req_reso)
{
	uint vmode = VMODE_UNKNOWN;
	struct VESA_MODES *vp;

	vp = modes_vesa;
	while (vp->reso) {
		if (vp->reso == req_reso) {
			vmode = vp->vmode;
			break;
		}
		vp++;
	}
	return vmode;
}

/* VBE 情報を作業領域に書き込む */
void set_vbe_info(
					 struct VBE_MODE_INFO far * vmp,
					 struct VBE_ACT_INFO far * vp
)
{
	vbe_win_func = vmp->win_func;

	vp->granula = (ulong)(vmp->win_granula) * 1024;
	vp->size = (ulong)(vmp->win_size) * 1024;
	vp->width = vmp->win_bps;

	if (vmp->win_a_attr & 0x01) {	/* Window A Exists ? */
		if (vmp->win_a_attr & 0x02) {	/* Readable ? */
			vp->rd_top = (BUFFER *)(vmp->win_a_seg * SIZE64KB);
			vp->rd_win = 0x00;
		}
		if (vmp->win_a_attr & 0x04) {	/* Writable ? */
			vp->wr_top = (BUFFER *)(vmp->win_a_seg * SIZE64KB);
			vp->wr_win = 0x00;
		}
	}
	if (vmp->win_b_attr & 0x01) {	/* Window B Exists ? */
		if (vmp->win_b_attr & 0x02) {	/* Window A Readable ? */
			vp->rd_top = (BUFFER *)(vmp->win_b_seg * SIZE64KB);
			vp->rd_win = 0x01;
		}
		if (vmp->win_b_attr & 0x04) {	/* Window A Writable ? */
			vp->wr_top = (BUFFER *)(vmp->win_b_seg * SIZE64KB);
			vp->wr_win = 0x01;
		}
	}
}

/* VESA 画面モードの機能取得 */
static uint get_vbe_funcs(uint req_vmode)
{
	union REGS iregs, oregs;
	struct SREGS sregs;
	uint far *tbl;	/* VBE 画面モードテーブル・ポインタ */
	uchar far *hp;
	struct VBE_INFO far *vhp;	/* VBE 情報領域へのポインタ */
	struct VBE_MODE_INFO far *vmp;	/* VBE 画面モード別情報領域へのポインタ */
	uint vmode = VMODE_UNKNOWN;

	if ((hp = get_work(256)) != NULL) {;
		vhp = (struct VBE_INFO far *)hp;
		if (get_vbe_info(vhp) && vbe_sign(vhp->sign)) {
			tbl = vhp->modesp;
			while (*tbl != 0xffff) {
				if (*tbl == req_vmode) {
					vmp = (struct VBE_MODE_INFO far *)hp;
					vmode = VMODE_ERR_VESA;
					if (get_vbe_mode_info(req_vmode, vmp)) {
						if (vmp->mode_attr & 0x0001) {
							set_vbe_info(vmp, &vbe_act);
							vmode = req_vmode;
							if (!(vmp->mode_attr & 0x0004)) {
								f_bios_out = FALSE;
							}
						}
					}
					break;
				}
				tbl++;
			}
		}
	}
	else {
		printf("Can't allocate heap.\n");
		exit(1);
	}
	return vmode;
}
