IBM-PC.htm

戻る

<!DOCTYPE HTML>
<HTML LANG="ja">

<HEAD>
<META CHARSET="Shift_JIS">
<TITLE>IBM PC エミュレータ</TITLE>
</HEAD>

<BODY STYLE="background-color:#CCFFFF">
<CENTER>
<B>IBM PC エミュレータ (Firefox 43)</B>
<BR>

<DIV ID="frm" STYLE="border:solid gray 4px">
<CANVAS ID="scr" WIDTH=1 HEIGHT=1 STYLE="image-rendering:-moz-crisp-edges; display:none"></CANVAS>
<SPAN ID="dscr" STYLE="background-color:black; display:inline-block">
<SPAN STYLE="margin-top:5em; padding:20px; border-radius:3px; text-align:left; color:black; background-color:#EEEEEE; display:inline-block">起動するディスク イメージをマウントして<BR>リセット ボタンを押してください。</SPAN>
</SPAN>
</DIV>
<BR>

<FORM>
<TABLE BORDER=0>
<TR><TD NOWRAP>
<SPAN STYLE="vertical-align:middle; margin-right:0.5em">FD ドライブ 1</SPAN>
<SPAN STYLE="display:inline-block; line-height:0; vertical-align:middle">
<SPAN STYLE="display:inline-block; height:0; overflow:hidden; vertical-align:top">
<INPUT TYPE=BUTTON VALUE="マウント&#x0a;取り外し" STYLE="visibility:hidden">
</SPAN>
<BR>
<INPUT TYPE=BUTTON NAME="mnt" VALUE="マウント" onClick="mount_fd(0)" STYLE="width:100%">
</SPAN>
<SPAN NAME="nam" STYLE="white-space:pre; display:inline-block; width:10em; vertical-align:middle; padding-left:2px; padding-right:2px; overflow:hidden; text-overflow:ellipsis; color:black; background-color:white"> </SPAN>
<LABEL><INPUT TYPE=CHECKBOX NAME="prt" STYLE="vertical-align:middle"> <SPAN STYLE="vertical-align:middle">書き込み禁止</SPAN></LABEL>
<INPUT TYPE=BUTTON NAME="sav" VALUE="保存" DISABLED onClick="save_fd(0)" STYLE="vertical-align:middle">
</TD></TR>
<TR><TD NOWRAP>
<SPAN STYLE="vertical-align:middle; margin-right:0.5em">FD ドライブ 2</SPAN>
<SPAN STYLE="display:inline-block; line-height:0; vertical-align:middle">
<SPAN STYLE="display:inline-block; height:0; overflow:hidden; vertical-align:top">
<INPUT TYPE=BUTTON VALUE="マウント&#x0a;取り外し" STYLE="visibility:hidden">
</SPAN>
<BR>
<INPUT TYPE=BUTTON NAME="mnt" VALUE="マウント" onClick="mount_fd(1)" STYLE="width:100%">
</SPAN>
<SPAN NAME="nam" STYLE="white-space:pre; display:inline-block; width:10em; vertical-align:middle; padding-left:2px; padding-right:2px; overflow:hidden; text-overflow:ellipsis; color:black; background-color:white"> </SPAN>
<LABEL><INPUT TYPE=CHECKBOX NAME="prt" STYLE="vertical-align:middle"> <SPAN STYLE="vertical-align:middle">書き込み禁止</SPAN></LABEL>
<INPUT TYPE=BUTTON NAME="sav" VALUE="保存" DISABLED onClick="save_fd(1)" STYLE="vertical-align:middle">
</TD></TR>
<TR><TD NOWRAP>
<SPAN STYLE="margin-right:1em">速度</SPAN>
遅い<INPUT TYPE=RANGE MIN="0" MAX="0.1" STEP="any" VALUE="0.1" onInput="run_delay = 0.1 - Number(this.value)">速い
</TD></TR>
<TR><TD NOWRAP>
<LABEL><INPUT TYPE=CHECKBOX onClick="video_composite = this.checked" STYLE="vertical-align:middle"> <SPAN STYLE="vertical-align:middle">コンポジット モニタ(グラフィック モードのみ)</SPAN></LABEL>
</TD></TR>
<TR><TD NOWRAP>
<BR>
<INPUT TYPE=BUTTON ID="rst" VALUE="リセット" onClick="boot()">
<INPUT TYPE=BUTTON ID="hlt" VALUE="停止" onClick="halt()" STYLE="margin-left:1em">
<INPUT TYPE=BUTTON VALUE="空ディスク イメージ作成" onClick="new_fd()" STYLE="margin-left:5em">
</TD></TR>
</TABLE>
</FORM>

</CENTER>

<DIV ID="nfd" STYLE="position:absolute; left:0; top:0; padding:15px 20px; text-align:left; color:black; background-color:white; border:solid #990000 4px; border-radius:3px; visibility:hidden">
新しいディスク イメージを作成します。<BR><BR>
<FORM>
ディスク容量
<SELECT ID="nfd_fmt" STYLE="margin-left:2px">
<OPTION SELECTED>160 KB
<OPTION>180 KB
<OPTION>200 KB
<OPTION>320 KB
<OPTION>360 KB
<OPTION>400 KB
<OPTION>720 KB
<OPTION>1.44 MB
</SELECT>
<BR><BR>
<LABEL><INPUT TYPE=RADIO NAME="nfd_grp" ID="nfd_drv1" CHECKED> FD ドライブ 1 にマウントする</LABEL><BR>
<LABEL><INPUT TYPE=RADIO NAME="nfd_grp"> FD ドライブ 2 にマウントする</LABEL><BR>
<LABEL><INPUT TYPE=RADIO NAME="nfd_grp" ID="nfd_fil"> ファイルに保存する</LABEL><BR>
<BR>
<INPUT TYPE=BUTTON VALUE="作成" onClick="new_fd_go()" STYLE="margin-left:2em">
<INPUT TYPE=BUTTON VALUE="キャンセル" onClick="new_fd_hide()" STYLE="margin-left:1em">
</FORM>
</DIV>

<A ID="dl" STYLE="display:none"></A><!--FD イメージ保存処理用-->

<IFRAME ID="tmr1" WIDTH=1 HEIGHT=1 SRC="about:blank" STYLE="display:none"></IFRAME><!--新タイマー取得用-->
<IFRAME ID="tmr2" WIDTH=1 HEIGHT=1 SRC="about:blank" STYLE="display:none"></IFRAME>

<SCRIPT TYPE="text/javascript">
<!--
// IBM PC エミュレータ Firefox 43 以上用
// Firefox 59 以上では,Firefox の設定(about:config)の“privacy.reduceTimerPrecision”
// の値を false に変更する.

  const INIT_MODE = 3;  // 初期ビデオ モード

  const AUDIO_GAIN = 0.3;  // オーディオ ゲイン

  // ディスク イメージ サイズ バイト数
  disk_size = [
     163840,  // 160K
     184320,  // 180K
     204800,  // 200K
     327680,  // 320K
     368640,  // 360K
     409600,  // 400K
     737280,  // 720K
    1474560   // 1.44M
  ];
  // ディスク シリンダ数,ヘッド数,セクタ数,セクタ長
  disk_spec = [
    [40, 1,  8, 512],  // 160K
    [40, 1,  9, 512],  // 180K
    [40, 1, 10, 512],  // 200K
    [40, 2,  8, 512],  // 320K
    [40, 2,  9, 512],  // 360K
    [40, 2, 10, 512],  // 400K
    [80, 2,  9, 512],  // 720K
    [80, 2, 18, 512]   // 1.44M
  ];

  elem_frm = document.getElementById("frm");
  elem_scr = document.getElementById("scr");
  elem_dscr = document.getElementById("dscr");
  elem_mnt = document.getElementsByName("mnt");
  elem_nam = document.getElementsByName("nam");
  elem_prt = document.getElementsByName("prt");
  elem_sav = document.getElementsByName("sav");
  elem_rst = document.getElementById("rst");
  elem_hlt = document.getElementById("hlt");
  elem_nfd = document.getElementById("nfd");
  elem_dl = document.getElementById("dl");
  elem_tmr1 = document.getElementById("tmr1");
  elem_tmr2 = document.getElementById("tmr2");

  // ページを再ロードしたときのため
  document.forms[0].reset();
  elem_mnt[0].disabled = elem_mnt[1].disabled = false;
  elem_sav[0].disabled = elem_sav[1].disabled = true;
  elem_rst.disabled = elem_hlt.disabled = false;
  document.forms[1].reset();

  // 画面
  ctx_scr = elem_scr.getContext("2d");
  ctx_scr.fillStyle = "black";

  err = true;
  elem_scr.width  = 1000;
  elem_scr.height = 1000;
  img_width  = ctx_scr.getImageData(0, 0, 1000, 1).width;
  img_height = ctx_scr.getImageData(0, 0, 1, 1000).height;
  elem_scr.width  = width320  = Math.round(320000 / img_width);
  elem_scr.height = height200 = Math.round(200000 / img_height);
  if(ctx_scr.getImageData(0, 0, width320, 1).width == 320
       && ctx_scr.getImageData(0, 0, 1, height200).height == 200) {
    elem_scr.width = width640 = Math.round(640000 / img_width);
    if(ctx_scr.getImageData(0, 0, width640, 1).width == 640) {
      elem_scr.width  = width720  = Math.round(720000 / img_width);
      elem_scr.height = height350 = Math.round(350000 / img_height);
      if(ctx_scr.getImageData(0, 0, width720, 1).width == 720
           && ctx_scr.getImageData(0, 0, 1, height350).height == 350)
        err = false;
    }
  }
  delete img_width;
  delete img_height;
  if(err) {
    alert("画面サイズと同じ大きさのピクセル データを持つ Canvas を作成できません。");
    elem_rst.disabled = elem_hlt.disabled = true;
  }
  else {
    if(INIT_MODE == 7) {  // 初期ビデオ モード MDA
      width = width720;
      hheight = height350;
    }
    else {
      width = width640;
      height = height200 << 1;
    }
    elem_frm.style.width  = elem_dscr.style.width  = String(width)  + "px";
    elem_frm.style.height = elem_dscr.style.height = String(height) + "px";
    delete width;
    delete height;
  }
  delete err;

  // オーディオ
  with(aud_ctx = new AudioContext()) {
    // ビープ音発生用
    beep_osc = createOscillator();
    beep_osc.type = "square";
    beep_osc.start(0);
    beep_osc_gain = createGain();
    beep_osc_gain.gain.value = AUDIO_GAIN;
    beep_osc.connect(beep_osc_gain);

    // スピーカー直接出力用
    spk_out = createScriptProcessor(512, 0, 1);
    spk_out_gain = createGain();
    spk_out_gain.gain.value = AUDIO_GAIN * 2;
    lpf1 = createBiquadFilter();
    lpf1.type = "lowpass";
    lpf1.frequency.value = 4000;
    lpf1.Q.value = 1;
    lpf1.connect(spk_out_gain);
    lpf2 = createBiquadFilter();
    lpf2.type = "lowpass";
    lpf2.frequency.value = 4000;
    lpf2.Q.value = 1;
    lpf2.connect(lpf1);
    spk_out.connect(lpf2);
    delete lpf1;
    delete lpf2;
    spk_out_rate = sampleRate / 1000;
  }
  spk_out_buff = new Float64Array(2048);
  spk_out_int = 512 / spk_out_rate;

  run_state = false;
  run_delay = 0;

  addEventListener("message", run, false);

// FD イメージ マウント/取り外し
function mount_fd(i_drive) {
  if(mnt_image[i_drive] == undefined) {
    // マウント
    mount_i_drive = i_drive;
    elem_mnt[0].disabled = elem_mnt[1].disabled = true;
    setTimeout(enable_mnt, 100);
    elem_file = document.createElement("INPUT");
    elem_file.type = "FILE";
    elem_file.accept = ".img,.ima";
    elem_file.onchange = mount_fd_sel;
    elem_file.click();
  }
  else {
    // 取り外し
    if(mnt_dirty[i_drive]) {
      if(!confirm("ディスク イメージが更新されていますが、取り外してもよいですか?"))
        return;
      mnt_dirty[i_drive] = false;
      elem_nam[i_drive].style.color = "black";
    }

    mnt_image[i_drive] = undefined;
    elem_nam[i_drive].textContent = " ";
    elem_mnt[i_drive].value = "マウント";
    elem_sav[i_drive].disabled = true;
  }
}

function enable_mnt() {
  elem_mnt[0].disabled = elem_mnt[1].disabled = false;
}

function mount_fd_sel() {
  file_reader = new FileReader();
  file_reader.onload = mount_fd_sel2;
  file_reader.readAsArrayBuffer(elem_file.files.item(0));
  elem_mnt[0].disabled = elem_mnt[1].disabled = true;
}

function mount_fd_sel2() {
  elem_mnt[0].disabled = elem_mnt[1].disabled = false;
  var image = file_reader.result;
  delete file_reader;

  var i_size;
  if((i_size = disk_size.indexOf(image.byteLength)) < 0) {
    alert("対応していないディスク形式です。");
    return;
  }

  mount_image(mount_i_drive, new Uint8Array(image), i_size, elem_file.files.item(0).name);
}

// FD イメージ保存
function save_fd(i_drive) {
  with(elem_dl) {
    download = elem_nam[i_drive].textContent;
    if(href != "")
      URL.revokeObjectURL(href);
    href = URL.createObjectURL(new Blob([mnt_image[i_drive]], {type:"application/octet-stream"}));
    click();
  }
}

// リセット
function boot() {
  if(run_state)
    halt_wait(boot_2);
  else
    boot_2();
}

function boot_2() {
  if(mnt_image[0] == undefined && mnt_image[1] == undefined) {
    elem_dscr.style.width  = elem_frm.style.width;
    elem_dscr.style.height = elem_frm.style.height;
    elem_dscr.style.display = "inline-block";
    elem_scr.style.display = "none";
    return;
  }

  elem_tmr1.onload = boot_3;
  elem_tmr1.src = "about:blank";
  elem_rst.disabled = true;
}

function boot_3() {
  elem_rst.disabled = false;

  var i_drive = (mnt_image[0] == undefined) ? 1 : 0;

  timer = elem_tmr1.contentWindow.performance;
  timer_sw = false;
  elem_tmr1.onload = elem_tmr2.onload = reset_timer;

  mem.fill(0);

  bios_init();

  elem_scr.style.display = "";
  elem_dscr.style.display = "none";

  mem.set(new Uint8Array(mnt_image[i_drive].buffer, 0, mnt_spec[i_drive][3]), 0x7c00);  // ブート セクタ
  es4 = ds4 = 0x00000;
  regs[I_DL] = i_drive;
  flag_o = flag_d = flag_t
    = flag_s = flag_z = flag_a = flag_p = flag_c = 0;
  flag_i = 1;
  seg4_ovr = -1;
  cs4 = 0x00000;
  ip = 0x7c00;
  // PC booter の中にはスタックを初期化しないものがあるので,
  // スタックをセットしておく.
  ss4 = 0x00300;
  set_reg2(I_SP, 0x0100);

  run_break = undefined;
  run_wait = 0;
  run_pause = false;
  run_halt = false;
  run_hwint_ss = -1;
  run_icnt = 0;
  postMessage(undefined, "*");
  run_state = true;

  // Firefox 103 以上でも動くようにするため
  if(aud_ctx.resume !== undefined)
    aud_ctx.resume();
}

// 停止
function halt() {
  if(run_state)
    halt_wait(halt_2);
}

function halt_2() {
  elem_dscr.style.width  = elem_frm.style.width;
  elem_dscr.style.height = elem_frm.style.height;
  elem_dscr.style.display = "inline-block";
  elem_scr.style.display = "none";
}

// エミュレーション停止
function halt_wait(cb) {
  switch(sound_state) {
  case 1:  // ビープ音発生中
    beep_osc_gain.disconnect();
    break;
  case 2:  // スピーカー直接出力中
    spk_out.onaudioprocess = null;
    spk_out_gain.disconnect();
    break;
  }

  run_break = cb;
  elem_rst.disabled = elem_hlt.disabled = true;
}

// ディスク イメージ作成
function new_fd() {
  removeEventListener("keydown", keyboard_event, false);
  removeEventListener("keyup",   keyboard_event, false);
  addEventListener("keydown",   new_fd_modal, false);
  addEventListener("keyup",     new_fd_modal, false);
  addEventListener("mousedown", new_fd_modal, false);
  addEventListener("mouseup",   new_fd_modal, false);
  addEventListener("mousemove", new_fd_modal, false);
  addEventListener("click",     new_fd_modal, false);

  with(elem_nfd.style) {
    left = String((document.documentElement.scrollWidth  - elem_nfd.offsetWidth)  >> 1) + "px";
    top  = String((document.documentElement.scrollHeight - elem_nfd.offsetHeight) >> 1) + "px";
    visibility = "";
  }
}

// 作成
function new_fd_go() {
  var i_size = document.getElementById("nfd_fmt").selectedIndex;
  var image = new Uint8Array(disk_size[i_size]);
  if(document.getElementById("nfd_fil").checked) {  // ファイルに保存する
    with(elem_dl) {
      download = "unnamed.img";
      if(href != "")
        URL.revokeObjectURL(href);
      href = URL.createObjectURL(new Blob([image], {type:"application/octet-stream"}));
      click();
    }
  }
  else {  // FD ドライブにマウントする
    var i_drive = (document.getElementById("nfd_drv1").checked) ? 0 : 1;
    if(mnt_image[i_drive] != undefined) {
      if(mnt_dirty[i_drive]) {
        if(!confirm("FD ドライブ " + String(i_drive + 1)
                      + " のディスク イメージは更新されていますが、取り外してもよいですか?"))
          return;
        mnt_dirty[i_drive] = false;
        elem_nam[i_drive].style.color = "black";
      }
    }

    mount_image(i_drive, image, i_size, "unnamed.img");
  }

  new_fd_hide();
}

// 閉じる
function new_fd_hide() {
  with(elem_nfd.style) {
    visibility = "hidden";
    left = top = "0";
  }

  removeEventListener("keydown",   new_fd_modal, false);
  removeEventListener("keyup",     new_fd_modal, false);
  removeEventListener("mousedown", new_fd_modal, false);
  removeEventListener("mouseup",   new_fd_modal, false);
  removeEventListener("mousemove", new_fd_modal, false);
  removeEventListener("click",     new_fd_modal, false);
  addEventListener("keydown", keyboard_event, false);
  addEventListener("keyup",   keyboard_event, false);
}

function new_fd_modal(e) {
  if(!elem_nfd.contains(e.target) && e.target != elem_dl) {
    e.stopPropagation();
    e.preventDefault();
  }
}

// FD イメージ マウント
function mount_image(i_drive, image, i_size, name) {
  mnt_image[i_drive] = image;
  mnt_spec[i_drive] = disk_spec[i_size];

  elem_nam[i_drive].textContent = name;
  elem_mnt[i_drive].value = "取り外し";
  elem_sav[i_drive].disabled = false;
}

// エミュレーション実行
function run(e) {
  if(e.source == window) {
    var cnt = 512;
    for(; ; ) {
      for(; cnt; cnt--) {
        if(run_break != undefined) {
          run_state = false;
          elem_rst.disabled = elem_hlt.disabled = false;
          run_break();
          return;
        }

        // ディレイ
        if(run_delay) {
          if(!(cnt & 0x7)) {
            for(var to = timer.now() + run_delay; timer.now() < to; )
              ;
          }
        }

        timer_now = timer.now();

        if(ss4 == run_hwint_ss && regs_2[I_SP_2] == run_hwint_sp)  // ハードウェア割り込み処理呼び出し終了
          run_hwint_ss = -1;

        // CRT 帰線区間のエミュレート & 画面更新
        if(timer_now >= video_line_next) {
          if(!--video_line_cnt) {
            video_line_cnt = video_lines;

            video_frame_cnt = (video_frame_cnt + 1) & 0x1f;
            if(!(video_frame_cnt & 0x7))  // 8 フレーム毎に画面を定期更新する
              video_refresh_req1 = true;  // 画面更新要求
          }
          if(video_color_chg >= 0) {  // カラー パレット変更中
            video_refresh_1l();
            if(video_line_cnt == video_color_chg)  // カラー パレット変更終了
              video_color_chg = -1;
          }
          // CGA の CRTC のクロックとタイマーのクロックは同じ発振器の出力を元にしているため,
          // 完全に同期する.
          // そのことを前提としているプログラムがあるので,浮動小数点数の誤差の累積によって
          // 両者の同期が次第にずれていくことが無いように注意する必要がある.
          video_line_osc_cnt += video_line_osc_n;
          video_line_next = video_line_org + video_line_osc_cnt * video_line_osc_int;
        }

        // キー リピート
        if(timer_now >= keyboard_rep_next) {
          if(keyboard_input.length < 4)
            keyboard_input.push(keyboard_rep_data);
          keyboard_rep_next += keyboard_rep_rate;
        }

        if(timer_now >= timer_0_next) {  // システム タイマー割り込み要求
          // 実際のハードウェアでは,前の割り込み要求が受け付けられていない状態では割り込み要求は
          // 捨てられると思う.
          // タイマー割り込みを取りこぼすと処理に影響があるようなプログラムは,次の割り込み要求が
          // 発生する前に割り込みを受け付けるように作られているはず.
          // しかし,このエミュレータでは,バックグラウンドでガーベッジ コレクションが走った場合
          // など,次の割り込み要求までに割り込みが受け付けられないことが容易に起こり得る.
          // そのため,割り込み要求を受け付けることができない場合には,割り込み要求を保留するよう
          // にして,取りこぼしが起きないようにする.
          if(!(pic_m_irr & 0x01)
               // 遅延がどんどん累積していく場合はあきらめる
               || timer_now > timer_0_next + 200) {
            pic_m_irr |= 0x01;

            timer_0_update();
          }
        }
        if(keyboard_input.length)  // キーボード割り込み要求
          pic_m_irr |= 0x02;

        if((pic_m_irr & 0x01) && !(pic_m_imr & 0x01) && !(pic_m_isr & 0x01) && flag_i) {  // システム タイマー割り込み
          interval_int();
        }
        else if((pic_m_irr & 0x02) && !(pic_m_imr & 0x02) && !(pic_m_isr & 0x03) && flag_i) {  // キーボード割り込み
          if(keyboard_int())
            continue;
        }

        if(run_hwint_ss < 0) {  // ハードウェア割り込み処理呼び出し中でない
          // 画面更新
          if(video_refresh_req1) {
            if(video_color_chg < 0)  // カラー パレット変更中でない
              video_refresh();
            video_refresh_req2 = true;
            video_refresh_req1 = false;
          }
          else {
            if(video_refresh_req2) {
              if(!video_disabled)
                ctx_scr.putImageData(video_data, 0, 0);
              video_refresh_req2 = false;
            }
          }

          if(run_pause)  // Pause 中
            continue;
          if(run_wait) {  // 待機中
            switch(run_wait) {
            case 0x10001:  // BEL コードによるビープ終了
              if(bell_ended())
                continue;
              break;
            case 0x10002:  // 入力待ち & バッファに入力あり
              run_wait = 0;
              op_popf();
              regs[I_AH] = keyboard_wait_func;
              bios_keyboard();
              if(run_wait)  // 入力待ち
                continue;
              break;
            default:
              continue;
            }
          }
        }

        if(run_halt)  // HLT 命令により停止中
          continue;

        // デフォルトの割り込み処理
        if(cs4 == 0xf0000) {
          switch(ip) {
          case INT_08_OFF:
            interval_int_def();
            break;
          case INT_08_OFF_2:
            interval_int_def_2();
            break;
          case INT_09_OFF:
            keyboard_int_def();
            break;
          case INT_13_OFF:
            bios_disk_int_def();
            break;
          }
        }

        var code;
        for(; ; ) {
          if(((code = get_code1()) & 0xe7) != 0x26)  // セグメント オーバーライドでない
            break;
          seg4_ovr = code & 0x18;
        }

        op_tbl[code]();

        seg4_ovr = -1;
      }

      // 割り込み禁止状態のときは,ビジー ウェイトでタイミングを取っている場合があるので,
      // ブラウザに制御を戻すのを遅延させる.
      if(flag_i)
        break;

      if(run_icnt == 24)
        break;
      cnt = 64;
      run_icnt++;
    }

    if(timer_now >= 1000000) {
      // タイマー再取得
      ((timer_sw) ? elem_tmr1 : elem_tmr2).src = "about:blank";
      return;
    }
    postMessage(undefined, "*");
  }
}

function reset_timer(e) {
  var new_timer = e.target.contentWindow.performance;
  var now;
  var delta = (now = timer.now()) - new_timer.now();
  timer = new_timer;
  timer_sw = !timer_sw;

  video_line_next -= delta;
  video_line_org = video_line_next - video_line_osc_n * video_line_osc_int;
  video_line_osc_cnt = video_line_osc_n;
  if(keyboard_rep_next < Number.MAX_VALUE)
    keyboard_rep_next -= delta;
  if(timer_0_next < Number.MAX_VALUE) {
    timer_0_next -= delta;
    timer_0_org = timer_0_next - timer_0_count * OSC_INT;
    timer_0_osc_cnt = timer_0_count;
  }
  if(timer_2_org < Number.MAX_VALUE)
    timer_2_org = now - (now - timer_2_org) % (timer_2_count * OSC_INT) - delta;
  timer_2_gate_off -= delta;
  if(sound_state == 2) {  // スピーカー直接出力中
    spk_out_time -= delta;
    spk_out_put_prev -= delta;
    spk_out_get_prev -= delta;
  }

  postMessage(undefined, "*");
}

// ---- 8086 エミュレーション ----

  mem = new Uint8Array(0x100000 + 16);  // メモリ + レジスタ
  mem_s = new Int8Array(mem.buffer);    // メモリ/レジスタ 符号付きアクセス用
  regs = new Uint8Array(mem.buffer, 0x100000, 16);   // レジスタ アクセス用
  regs_s = new Int8Array(mem.buffer, 0x100000, 16);  // レジスタ 符号付きアクセス用
  regs_2 = new Uint16Array(mem.buffer, 0x100000, 8);  // レジスタ 2 バイト アクセス用
                                                      // (実行環境により endian は異なる)

  // レジスタ インデックス
  const I_AX = 0;
  const I_CX = 2;
  const I_DX = 4;
  const I_BX = 6;
  const I_SP = 8;
  const I_BP = 10;
  const I_SI = 12;
  const I_DI = 14;
  const I_AL = 0;
  const I_CL = 2;
  const I_DL = 4;
  const I_BL = 6;
  const I_AH = 1;
  const I_CH = 3;
  const I_DH = 5;
  const I_BH = 7;
  const I_SP_L = 8;
  const I_SP_H = 9;
  const I_BP_L = 10;
  const I_BP_H = 11;
  const I_SI_L = 12;
  const I_SI_H = 13;
  const I_DI_L = 14;
  const I_DI_H = 15;

  // レジスタ インデックス 2 バイト アクセス用
  const I_AX_2 = 0;
  const I_CX_2 = 1;
  const I_DX_2 = 2;
  const I_BX_2 = 3;
  const I_SP_2 = 4;
  const I_BP_2 = 5;
  const I_SI_2 = 6;
  const I_DI_2 = 7;

  // 命令処理関数テーブル
  op_tbl = [
    op_add_s1, op_add_s2, op_add_d1, op_add_d2, op_add_im1, op_add_im2, op_push_es, op_pop_es,
    op_or_s1,  op_or_s2,  op_or_d1,  op_or_d2,  op_or_im1,  op_or_im2,  op_push_cs, op_nop,
    op_adc_s1, op_adc_s2, op_adc_d1, op_adc_d2, op_adc_im1, op_adc_im2, op_push_ss, op_pop_ss,
    op_sbb_s1, op_sbb_s2, op_sbb_d1, op_sbb_d2, op_sbb_im1, op_sbb_im2, op_push_ds, op_pop_ds,
    op_and_s1, op_and_s2, op_and_d1, op_and_d2, op_and_im1, op_and_im2, , op_daa,
    op_sub_s1, op_sub_s2, op_sub_d1, op_sub_d2, op_sub_im1, op_sub_im2, , op_das,
    op_xor_s1, op_xor_s2, op_xor_d1, op_xor_d2, op_xor_im1, op_xor_im2, , op_aaa,
    op_cmp_s1, op_cmp_s2, op_cmp_d1, op_cmp_d2, op_cmp_im1, op_cmp_im2, , op_aas,

    op_inc_ax, op_inc_cx, op_inc_dx, op_inc_bx, op_inc_sp, op_inc_bp, op_inc_si, op_inc_di,
    op_dec_ax, op_dec_cx, op_dec_dx, op_dec_bx, op_dec_sp, op_dec_bp, op_dec_si, op_dec_di,
    op_push_ax, op_push_cx, op_push_dx, op_push_bx, op_push_sp, op_push_bp, op_push_si, op_push_di,
    op_pop_ax, op_pop_cx, op_pop_dx, op_pop_bx, op_pop_sp, op_pop_bp, op_pop_si, op_pop_di,
    op_nop, op_nop, op_nop, op_nop, op_nop, op_nop, op_nop, op_nop,
    op_nop, op_nop, op_nop, op_nop, op_nop, op_nop, op_nop, op_nop,
    op_jo, op_jno, op_jb, op_jnb, op_je, op_jne, op_jbe, op_jnbe,
    op_js, op_jns, op_jp, op_jnp, op_jl, op_jnl, op_jle, op_jnle,

    op_misc11, op_misc12, op_misc21, op_misc22, op_test1, op_test2, op_xchg1, op_xchg2,
    op_mov_s1, op_mov_s2, op_mov_d1, op_mov_d2, op_mov_seg_s, op_lea, op_mov_seg_d, op_misc3,
    op_nop, op_xchg_cx, op_xchg_dx, op_xchg_bx, op_xchg_sp, op_xchg_bp, op_xchg_si, op_xchg_di,
    op_cbw, op_cwd, op_call_far, op_nop, op_pushf, op_popf, op_sahf, op_lahf,
    op_mov_l1, op_mov_l2, op_mov_t1, op_mov_t2, op_movsb, op_movsw, op_cmpsb, op_cmpsw,
    op_test_im1, op_test_im2, op_stosb, op_stosw, op_lodsb, op_lodsw, op_scasb, op_scasw,
    op_mov_al_im, op_mov_cl_im, op_mov_dl_im, op_mov_bl_im, op_mov_ah_im, op_mov_ch_im, op_mov_dh_im, op_mov_bh_im,
    op_mov_ax_im, op_mov_cx_im, op_mov_dx_im, op_mov_bx_im, op_mov_sp_im, op_mov_bp_im, op_mov_si_im, op_mov_di_im,

    op_nop, op_nop, op_ret_n, op_ret, op_les, op_lds, op_mov_m_im1, op_mov_m_im2,
    op_nop, op_nop, op_retf_n, op_retf, op_int_3, op_int_n, op_into, op_iret,
    op_rs_1_1, op_rs_1_2, op_rs_n_1, op_rs_n_2, op_aam, op_aad, op_nop, op_xlat,
    op_nop, op_nop, op_nop, op_nop, op_nop, op_nop, op_nop, op_nop,
    op_loopne, op_loope, op_loop, op_jcxz, op_in_1, op_in_2, op_out_1, op_out_2,
    op_call_near, op_jmp_near, op_jmp_far, op_jmp, op_in_dx1, op_in_dx2, op_out_dx1, op_out_dx2,
    op_nop, op_nop, op_repne, op_repe, op_hlt, op_cmc, op_misc4, op_misc5,
    op_clc, op_stc, op_cli, op_sti, op_cld, op_std, op_misc6, op_misc7
  ];

function op_nop() {
}

// ADD REG8/MEM8,REG8
function op_add_s1() {
  decode_mrm(-1, 0);
  mem[dat_i_mem0] = add_1(mem[dat_i_mem0], regs[mrm_i_reg], 0);
}

// ADD REG16/MEM16,REG16
function op_add_s2() {
  decode_mrm(-1, 1);
  set_dat_mem2(add_2(get_dat_mem2(), get_mrm_reg2(), 0));
}

// ADD REG8,REG8/MEM8
function op_add_d1() {
  decode_mrm(-1, 0);
  regs[mrm_i_reg] = add_1(regs[mrm_i_reg], mem[dat_i_mem0], 0);
}

// ADD REG16,REG16/MEM16
function op_add_d2() {
  decode_mrm(-1, 1);
  set_mrm_reg2(add_2(get_mrm_reg2(), get_dat_mem2(), 0));
}

// ADD AL,IMMED8
function op_add_im1() {
  regs[I_AL] = add_1(regs[I_AL], get_code1(), 0);
}

// ADD AX,IMMED16
function op_add_im2() {
  set_reg2(I_AX, add_2(get_reg2(I_AX), get_code2(), 0));
}

// PUSH ES
function op_push_es() {
  push(es4 >> 4);
}

// POP ES
function op_pop_es() {
  es4 = pop() << 4;
}

// OR REG8/MEM8,REG8
function op_or_s1() {
  decode_mrm(-1, 0);
  flags_log1(mem[dat_i_mem0] |= regs[mrm_i_reg]);
}

// OR REG16/MEM16,REG16
function op_or_s2() {
  decode_mrm(-1, 1);
  flags_log2(mem[dat_i_mem0] |= regs[mrm_i_reg], mem[dat_i_mem1] |= regs[mrm_i_reg | 1]);
}

// OR REG8,REG8/MEM8
function op_or_d1() {
  decode_mrm(-1, 0);
  flags_log1(regs[mrm_i_reg] |= mem[dat_i_mem0]);
}

// OR REG16,REG16/MEM16
function op_or_d2() {
  decode_mrm(-1, 1);
  flags_log2(regs[mrm_i_reg] |= mem[dat_i_mem0], regs[mrm_i_reg | 1] |= mem[dat_i_mem1]);
}

// OR AL,IMMED8
function op_or_im1() {
  flags_log1(regs[I_AL] |= get_code1());
}

// OR AX,IMMED16
function op_or_im2() {
  get_code2_2();
  flags_log2(regs[I_AL] |= mem[cod_i_mem0], regs[I_AH] |= mem[cod_i_mem1]);
}

// PUSH CS
function op_push_cs() {
  push(cs4 >> 4);
}

// ADC REG8/MEM8,REG8
function op_adc_s1() {
  decode_mrm(-1, 0);
  mem[dat_i_mem0] = add_1(mem[dat_i_mem0], regs[mrm_i_reg], flag_c);
}

// ADC REG16/MEM16,REG16
function op_adc_s2() {
  decode_mrm(-1, 1);
  set_dat_mem2(add_2(get_dat_mem2(), get_mrm_reg2(), flag_c));
}

// ADC REG8,REG8/MEM8
function op_adc_d1() {
  decode_mrm(-1, 0);
  regs[mrm_i_reg] = add_1(regs[mrm_i_reg], mem[dat_i_mem0], flag_c);
}

// ADC REG16,REG16/MEM16
function op_adc_d2() {
  decode_mrm(-1, 1);
  set_mrm_reg2(add_2(get_mrm_reg2(), get_dat_mem2(), flag_c));
}

// ADC AL,IMMED8
function op_adc_im1() {
  regs[I_AL] = add_1(regs[I_AL], get_code1(), flag_c);
}

// ADC AX,IMMED16
function op_adc_im2() {
  set_reg2(I_AX, add_2(get_reg2(I_AX), get_code2(), flag_c));
}

// PUSH SS
function op_push_ss() {
  push(ss4 >> 4);
}

// POP SS
function op_pop_ss() {
  ss4 = pop() << 4;
}

// SBB REG8/MEM8,REG8
function op_sbb_s1() {
  decode_mrm(-1, 0);
  mem[dat_i_mem0] = sub_1(mem[dat_i_mem0], regs[mrm_i_reg], flag_c);
}

// SBB REG16/MEM16,REG16
function op_sbb_s2() {
  decode_mrm(-1, 1);
  set_dat_mem2(sub_2(get_dat_mem2(), get_mrm_reg2(), flag_c));
}

// SBB REG8,REG8/MEM8
function op_sbb_d1() {
  decode_mrm(-1, 0);
  regs[mrm_i_reg] = sub_1(regs[mrm_i_reg], mem[dat_i_mem0], flag_c);
}

// SBB REG16,REG16/MEM16
function op_sbb_d2() {
  decode_mrm(-1, 1);
  set_mrm_reg2(sub_2(get_mrm_reg2(), get_dat_mem2(), flag_c));
}

// SBB AL,IMMED8
function op_sbb_im1() {
  regs[I_AL] = sub_1(regs[I_AL], get_code1(), flag_c);
}

// SBB AX,IMMED16
function op_sbb_im2() {
  set_reg2(I_AX, sub_2(get_reg2(I_AX), get_code2(), flag_c));
}

// PUSH DS
function op_push_ds() {
  push(ds4 >> 4);
}

// POP DS
function op_pop_ds() {
  ds4 = pop() << 4;
}

// AND REG8/MEM8,REG8
function op_and_s1() {
  decode_mrm(-1, 0);
  flags_log1(mem[dat_i_mem0] &= regs[mrm_i_reg]);
}

// AND REG16/MEM16,REG16
function op_and_s2() {
  decode_mrm(-1, 1);
  flags_log2(mem[dat_i_mem0] &= regs[mrm_i_reg], mem[dat_i_mem1] &= regs[mrm_i_reg | 1]);
}

// AND REG8,REG8/MEM8
function op_and_d1() {
  decode_mrm(-1, 0);
  flags_log1(regs[mrm_i_reg] &= mem[dat_i_mem0]);
}

// AND REG16,REG16/MEM16
function op_and_d2() {
  decode_mrm(-1, 1);
  flags_log2(regs[mrm_i_reg] &= mem[dat_i_mem0], regs[mrm_i_reg | 1] &= mem[dat_i_mem1]);
}

// AND AL,IMMED8
function op_and_im1() {
  flags_log1(regs[I_AL] &= get_code1());
}

// AND AX,IMMED16
function op_and_im2() {
  get_code2_2();
  flags_log2(regs[I_AL] &= mem[cod_i_mem0], regs[I_AH] &= mem[cod_i_mem1]);
}

// DAA
function op_daa() {
  var val = regs[I_AL];
  if((val & 0x0f) > 0x09 || flag_a) {
    val = (val + 0x06) & 0xff;
    flag_a = 1;
  }
  if(val > 0x9f || flag_c) {
    val = (val + 0x60) & 0xff;
    flag_c = 1;
  }
  flags_szp1(regs[I_AL] = val);
}

// SUB REG8/MEM8,REG8
function op_sub_s1() {
  decode_mrm(-1, 0);
  mem[dat_i_mem0] = sub_1(mem[dat_i_mem0], regs[mrm_i_reg], 0);
}

// SUB REG16/MEM16,REG16
function op_sub_s2() {
  decode_mrm(-1, 1);
  set_dat_mem2(sub_2(get_dat_mem2(), get_mrm_reg2(), 0));
}

// SUB REG8,REG8/MEM8
function op_sub_d1() {
  decode_mrm(-1, 0);
  regs[mrm_i_reg] = sub_1(regs[mrm_i_reg], mem[dat_i_mem0], 0);
}

// SUB REG16,REG16/MEM16
function op_sub_d2() {
  decode_mrm(-1, 1);
  set_mrm_reg2(sub_2(get_mrm_reg2(), get_dat_mem2(), 0));
}

// SUB AL,IMMED8
function op_sub_im1() {
  regs[I_AL] = sub_1(regs[I_AL], get_code1(), 0);
}

// SUB AX,IMMED16
function op_sub_im2() {
  set_reg2(I_AX, sub_2(get_reg2(I_AX), get_code2(), 0));
}

// DAS
function op_das() {
  var val = regs[I_AL];
  if((val & 0x0f) > 0x09 || flag_a) {
    val = (val - 0x06) & 0xff;
    flag_a = 1;
  }
  if(val > 0x9f || flag_c) {
    val = (val - 0x60) & 0xff;
    flag_c = 1;
  }
  flags_szp1(regs[I_AL] = val);
}

// XOR REG8/MEM8,REG8
function op_xor_s1() {
  decode_mrm(-1, 0);
  flags_log1(mem[dat_i_mem0] ^= regs[mrm_i_reg]);
}

// XOR REG16/MEM16,REG16
function op_xor_s2() {
  decode_mrm(-1, 1);
  flags_log2(mem[dat_i_mem0] ^= regs[mrm_i_reg], mem[dat_i_mem1] ^= regs[mrm_i_reg | 1]);
}

// XOR REG8,REG8/MEM8
function op_xor_d1() {
  decode_mrm(-1, 0);
  flags_log1(regs[mrm_i_reg] ^= mem[dat_i_mem0]);
}

// XOR REG16,REG16/MEM16
function op_xor_d2() {
  decode_mrm(-1, 1);
  flags_log2(regs[mrm_i_reg] ^= mem[dat_i_mem0], regs[mrm_i_reg | 1] ^= mem[dat_i_mem1]);
}

// XOR AL,IMMED8
function op_xor_im1() {
  flags_log1(regs[I_AL] ^= get_code1());
}

// XOR AX,IMMED16
function op_xor_im2() {
  get_code2_2();
  flags_log2(regs[I_AL] ^= mem[cod_i_mem0], regs[I_AH] ^= mem[cod_i_mem1]);
}

// AAA
function op_aaa() {
  var val = regs[I_AL];
  if((val & 0x0f) > 0x09 || flag_a) {
    val += 0x06;
    regs[I_AH] = (regs[I_AH] + 1) & 0xff;
    flag_a = 1;
  }
  flag_c = flag_a;
  regs[I_AL] = val & 0x0f;
}

// CMP REG8/MEM8,REG8
function op_cmp_s1() {
  decode_mrm(-1, 0);
  sub_1(mem[dat_i_mem0], regs[mrm_i_reg], 0);
}

// CMP REG16/MEM16,REG16
function op_cmp_s2() {
  decode_mrm(-1, 1);
  sub_2(get_dat_mem2(), get_mrm_reg2(), 0);
}

// CMP REG8,REG8/MEM8
function op_cmp_d1() {
  decode_mrm(-1, 0);
  sub_1(regs[mrm_i_reg], mem[dat_i_mem0], 0);
}

// CMP REG16,REG16/MEM16
function op_cmp_d2() {
  decode_mrm(-1, 1);
  sub_2(get_mrm_reg2(), get_dat_mem2(), 0);
}

// CMP AL,IMMED8
function op_cmp_im1() {
  sub_1(regs[I_AL], get_code1(), 0);
}

// CMP AX,IMMED16
function op_cmp_im2() {
  sub_2(get_reg2(I_AX), get_code2(), 0);
}

// AAS
function op_aas() {
  var val = regs[I_AL];
  if((val & 0x0f) > 0x09 || flag_a) {
    val -= 0x06;
    regs[I_AH] = (regs[I_AH] - 1) & 0xff;
    flag_a = 1;
  }
  flag_c = flag_a;
  regs[I_AL] = val & 0x0f;
}

// INC AX
function op_inc_ax() {
  inc_reg2(I_AX);
}

// INC CX
function op_inc_cx() {
  inc_reg2(I_CX);
}

// INC DX
function op_inc_dx() {
  inc_reg2(I_DX);
}

// INC BX
function op_inc_bx() {
  inc_reg2(I_BX);
}

// INC SP
function op_inc_sp() {
  inc_reg2(I_SP);
}

// INC BP
function op_inc_bp() {
  inc_reg2(I_BP);
}

// INC SI
function op_inc_si() {
  inc_reg2(I_SI);
}

// INC DI
function op_inc_di() {
  inc_reg2(I_DI);
}

function inc_reg2(i_reg) {
  var val = (get_reg2(i_reg) + 1) & 0xffff;
  flag_o = (val == 0x8000) ? 1 : 0;
  flag_a = (val & 0xf) ? 0 : 1;
  set_reg2(i_reg, val);
  flags_szp2(val);
}

// DEC AX
function op_dec_ax() {
  dec_reg2(I_AX);
}

// DEC CX
function op_dec_cx() {
  dec_reg2(I_CX);
}

// DEC DX
function op_dec_dx() {
  dec_reg2(I_DX);
}

// DEC BX
function op_dec_bx() {
  dec_reg2(I_BX);
}

// DEC SP
function op_dec_sp() {
  dec_reg2(I_SP);
}

// DEC BP
function op_dec_bp() {
  dec_reg2(I_BP);
}

// DEC SI
function op_dec_si() {
  dec_reg2(I_SI);
}

// DEC DI
function op_dec_di() {
  dec_reg2(I_DI);
}

function dec_reg2(i_reg) {
  var val = get_reg2(i_reg);
  flag_o = (val == 0x8000) ? 1 : 0;
  flag_a = (val & 0xf) ? 0 : 1;
  set_reg2(i_reg, val = (val - 1) & 0xffff);
  flags_szp2(val);
}

// PUSH AX
function op_push_ax() {
  push_2(regs[I_AL], regs[I_AH]);
}

// PUSH CX
function op_push_cx() {
  push_2(regs[I_CL], regs[I_CH]);
}

// PUSH DX
function op_push_dx() {
  push_2(regs[I_DL], regs[I_DH]);
}

// PUSH BX
function op_push_bx() {
  push_2(regs[I_BL], regs[I_BH]);
}

// PUSH SP
function op_push_sp() {
  // 8086 では,SP - 2(PUSH 後の SP の値)が PUSH される
  var sp = (get_reg2(I_SP) - 2) & 0xffff;
  mem[(ss4 + sp) & 0xfffff] = regs[I_SP] = sp & 0xff;
  mem[(ss4 + ((sp + 1) & 0xffff)) & 0xfffff] = regs[I_SP | 1] = sp >> 8;
}

// PUSH BP
function op_push_bp() {
  push_2(regs[I_BP_L], regs[I_BP_H]);
}

// PUSH SI
function op_push_si() {
  push_2(regs[I_SI_L], regs[I_SI_H]);
}

// PUSH DI
function op_push_di() {
  push_2(regs[I_DI_L], regs[I_DI_H]);
}

// POP AX
function op_pop_ax() {
  pop_reg(I_AX);
}

// POP CX
function op_pop_cx() {
  pop_reg(I_CX);
}

// POP DX
function op_pop_dx() {
  pop_reg(I_DX);
}

// POP BX
function op_pop_bx() {
  pop_reg(I_BX);
}

// POP SP
function op_pop_sp() {
  pop_reg(I_SP);
}

// POP BP
function op_pop_bp() {
  pop_reg(I_BP);
}

// POP SI
function op_pop_si() {
  pop_reg(I_SI);
}

// POP DI
function op_pop_di() {
  pop_reg(I_DI);
}

function pop_reg(i_reg) {
  var sp = get_reg2(I_SP);
  set_reg2(I_SP, (sp + 2) & 0xffff);
  regs[i_reg] = mem[(ss4 + sp) & 0xfffff];
  regs[i_reg | 1] = mem[(ss4 + ((sp + 1) & 0xffff)) & 0xfffff];
}

// JO
function op_jo() {
  jmp_short(flag_o);
}

// JNO
function op_jno() {
  jmp_short(!flag_o);
}

// JB
function op_jb() {
  jmp_short(flag_c);
}

// JNB
function op_jnb() {
  jmp_short(!flag_c);
}

// JE
function op_je() {
  jmp_short(flag_z);
}

// JNE
function op_jne() {
  jmp_short(!flag_z);
}

// JBE
function op_jbe() {
  jmp_short(flag_c || flag_z);
}

// JNBE
function op_jnbe() {
  jmp_short(!flag_c && !flag_z);
}

// JS
function op_js() {
  jmp_short(flag_s);
}

// JNS
function op_jns() {
  jmp_short(!flag_s);
}

// JP
function op_jp() {
  jmp_short(flag_p);
}

// JNP
function op_jnp() {
  jmp_short(!flag_p);
}

// JL
function op_jl() {
  jmp_short(flag_s != flag_o);
}

// JNL
function op_jnl() {
  jmp_short(flag_s == flag_o);
}

// JLE
function op_jle() {
  jmp_short(flag_s != flag_o || flag_z);
}

// JNLE
function op_jnle() {
  jmp_short(flag_s == flag_o && !flag_z);
}

// ADD/OR/ADC/SBB/AND/SUB/XOR/CMP REG8/MEM8,IMMED8
function op_misc11() {
  var op2;
  decode_mrm(op2 = get_code1(), 0);

  switch(op2 & 0x38) {
  case 0x00:  // ADD
    mem[dat_i_mem0] = add_1(mem[dat_i_mem0], get_code1(), 0);
    break;

  case 0x08:  // OR
    flags_log1(mem[dat_i_mem0] |= get_code1());
    break;

  case 0x10:  // ADC
    mem[dat_i_mem0] = add_1(mem[dat_i_mem0], get_code1(), flag_c);
    break;

  case 0x18:  // SBB
    mem[dat_i_mem0] = sub_1(mem[dat_i_mem0], get_code1(), flag_c);
    break;

  case 0x20:  // AND
    flags_log1(mem[dat_i_mem0] &= get_code1());
    break;

  case 0x28:  // SUB
    mem[dat_i_mem0] = sub_1(mem[dat_i_mem0], get_code1(), 0);
    break;

  case 0x30:  // XOR
    flags_log1(mem[dat_i_mem0] ^= get_code1());
    break;

  default:  // CMP
    sub_1(mem[dat_i_mem0], get_code1(), 0);
  }
}

// ADD/OR/ADC/SBB/AND/SUB/XOR/CMP REG16/MEM16,IMMED16
function op_misc12() {
  var op2;
  decode_mrm(op2 = get_code1(), 1);

  switch(op2 & 0x38) {
  case 0x00:  // ADD
    set_dat_mem2(add_2(get_dat_mem2(), get_code2(), 0));
    break;

  case 0x08:  // OR
    get_code2_2();
    flags_log2(mem[dat_i_mem0] |= mem[cod_i_mem0], mem[dat_i_mem1] |= mem[cod_i_mem1]);
    break;

  case 0x10:  // ADC
    set_dat_mem2(add_2(get_dat_mem2(), get_code2(), flag_c));
    break;

  case 0x18:  // SBB
    set_dat_mem2(sub_2(get_dat_mem2(), get_code2(), flag_c));
    break;

  case 0x20:  // AND
    get_code2_2();
    flags_log2(mem[dat_i_mem0] &= mem[cod_i_mem0], mem[dat_i_mem1] &= mem[cod_i_mem1]);
    break;

  case 0x28:  // SUB
    set_dat_mem2(sub_2(get_dat_mem2(), get_code2(), 0));
    break;

  case 0x30:  // XOR
    get_code2_2();
    flags_log2(mem[dat_i_mem0] ^= mem[cod_i_mem0], mem[dat_i_mem1] ^= mem[cod_i_mem1]);
    break;

  default:  // CMP
    sub_2(get_dat_mem2(), get_code2(), 0);
  }
}

// ADD/ADC/SBB/SUB/CMP REG8/MEM8,IMMED8
function op_misc21() {
  var op2;
  switch((op2 = get_code1()) & 0x38) {
  case 0x00:  // ADD
    decode_mrm(op2, 0);
    mem[dat_i_mem0] = add_1(mem[dat_i_mem0], get_code1(), 0);
    break;

  case 0x10:  // ADC
    decode_mrm(op2, 0);
    mem[dat_i_mem0] = add_1(mem[dat_i_mem0], get_code1(), flag_c);
    break;

  case 0x18:  // SBB
    decode_mrm(op2, 0);
    mem[dat_i_mem0] = sub_1(mem[dat_i_mem0], get_code1(), flag_c);
    break;

  case 0x28:  // SUB
    decode_mrm(op2, 0);
    mem[dat_i_mem0] = sub_1(mem[dat_i_mem0], get_code1(), 0);
    break;

  case 0x38:  // CMP
    decode_mrm(op2, 0);
    sub_1(mem[dat_i_mem0], get_code1(), 0);
    break;
  }
}

// ADD/ADC/SBB/SUB/CMP REG16/MEM16,IMMED8
// OR/AND/XOR/ REG16/MEM16,IMMED8(命令表に無いが,使っているプログラムがある)
function op_misc22() {
  var op2;
  decode_mrm(op2 = get_code1(), 1);

  var val;
  switch(op2 & 0x38) {
  case 0x00:  // ADD
    set_dat_mem2(add_2(get_dat_mem2(), get_code_sx(), 0));
    break;

  case 0x08:  // OR
    val = get_code1();
    flags_log2(mem[dat_i_mem0] |= val, (val & 0x80) ? mem[dat_i_mem1] = 0xff : mem[dat_i_mem1]);
    break;

  case 0x10:  // ADC
    set_dat_mem2(add_2(get_dat_mem2(), get_code_sx(), flag_c));
    break;

  case 0x18:  // SBB
    set_dat_mem2(sub_2(get_dat_mem2(), get_code_sx(), flag_c));
    break;

  case 0x20:  // AND
    val = get_code1();
    flags_log2(mem[dat_i_mem0] &= val, (val & 0x80) ? mem[dat_i_mem1] : mem[dat_i_mem1] = 0x00);
    break;

  case 0x28:  // SUB
    set_dat_mem2(sub_2(get_dat_mem2(), get_code_sx(), 0));
    break;

  case 0x30:  // XOR
    val = get_code1();
    flags_log2(mem[dat_i_mem0] ^= val, (val & 0x80) ? mem[dat_i_mem1] ^= 0xff : mem[dat_i_mem1]);
    break;

  default:  // CMP
    sub_2(get_dat_mem2(), get_code_sx(), 0);
  }
}

// TEST REG8,REG8/MEM8
function op_test1() {
  decode_mrm(-1, 0);
  flags_log1(regs[mrm_i_reg] & mem[dat_i_mem0]);
}

// TEST REG16,REG16/MEM16
function op_test2() {
  decode_mrm(-1, 1);
  flags_log2(regs[mrm_i_reg] & mem[dat_i_mem0], regs[mrm_i_reg | 1] & mem[dat_i_mem1]);
}

// XCHG REG8,REG8/MEM8
function op_xchg1() {
  decode_mrm(-1, 0);
  var w = regs[mrm_i_reg];
  regs[mrm_i_reg] = mem[dat_i_mem0];
  mem[dat_i_mem0] = w;
}

// XCHG REG16,REG16/MEM16
function op_xchg2() {
  decode_mrm(-1, 1);
  var w = regs[mrm_i_reg];
  regs[mrm_i_reg] = mem[dat_i_mem0];
  mem[dat_i_mem0] = w;
  w = regs[mrm_i_reg | 1];
  regs[mrm_i_reg | 1] = mem[dat_i_mem1];
  mem[dat_i_mem1] = w;
}

// MOV REG8/MEM8,REG8
function op_mov_s1() {
  decode_mrm(-1, 0);
  mem[dat_i_mem0] = regs[mrm_i_reg];
}

// MOV REG16/MEM16,REG16
function op_mov_s2() {
  decode_mrm(-1, 1);
  mem[dat_i_mem0] = regs[mrm_i_reg];
  mem[dat_i_mem1] = regs[mrm_i_reg | 1];
}

// MOV REG8,REG8/MEM8
function op_mov_d1() {
  decode_mrm(-1, 0);
  regs[mrm_i_reg] = mem[dat_i_mem0];
}

// MOV REG16,REG16/MEM16
function op_mov_d2() {
  decode_mrm(-1, 1);
  regs[mrm_i_reg]     = mem[dat_i_mem0];
  regs[mrm_i_reg | 1] = mem[dat_i_mem1];
}

// MOV REG16/MEM16,SEGREG
function op_mov_seg_s() {
  var op2;
  var seg4;
  switch((op2 = get_code1()) & 0x38) {
  case 0x00:  // ES
    seg4 = es4;
    break;
  case 0x08:  // CS
    seg4 = cs4;
    break;
  case 0x10:  // SS
    seg4 = ss4;
    break;
  case 0x18:  // DS
    seg4 = ds4;
    break;
  default:
    return;
  }
  decode_mrm(op2, 1);
  set_dat_mem2(seg4 >> 4);
}

// LEA REG16,MEM16
function op_lea() {
  var mrm = get_code1();
  var r_m = mrm & 0x07;
  var ea;

  switch(mrm & 0xc0) {
  case 0xc0:  // レジスタ
    return;

  case 0x40:  // 変位 1 バイト
    ea = get_code_sx();
    break;

  case 0x80:  // 変位 2 バイト
    ea = get_code2();
    break;

  default:  // 0x00
    if(r_m == 0x6) {  // 変位 2 バイト
      ea = get_code2();
      r_m = 0x8;
      break;
    }
    // 変位なし
    ea = 0;
  }

  switch(r_m) {
  case 0x0:
    ea += get_reg2(I_BX) + get_reg2(I_SI);
    break;
  case 0x1:
    ea += get_reg2(I_BX) + get_reg2(I_DI);
    break;
  case 0x2:
    ea += get_reg2(I_BP) + get_reg2(I_SI);
    break;
  case 0x3:
    ea += get_reg2(I_BP) + get_reg2(I_DI);
    break;
  case 0x4:
    ea += get_reg2(I_SI);
    break;
  case 0x5:
    ea += get_reg2(I_DI);
    break;
  case 0x6:
    ea += get_reg2(I_BP);
    break;
  case 0x7:
    ea += get_reg2(I_BX);
    break;
  // 0x8: インデックスなし
  }
  set_reg2((mrm & 0x38) >> 2, ea & 0xffff);
}

// MOV SEGREG,REG16/MEM16
function op_mov_seg_d() {
  var op2;
  switch((op2 = get_code1()) & 0x38) {
  case 0x00:  // ES
    decode_mrm(op2, 1);
    es4 = get_dat_mem2() << 4;
    break;
  case 0x08:  // CS
    decode_mrm(op2, 1);
    cs4 = get_dat_mem2() << 4;
    break;
  case 0x10:  // SS
    decode_mrm(op2, 1);
    ss4 = get_dat_mem2() << 4;
    break;
  case 0x18:  // DS
    decode_mrm(op2, 1);
    ds4 = get_dat_mem2() << 4;
    break;
  }
}

// POP REG16/MEM16
function op_misc3() {
  var op2;
  if(((op2 = get_code1()) & 0x38) == 0x00) {  // POP
    decode_mrm(op2, 1);
    set_dat_mem2(pop());
  }
}

// XCHG AX,CX
function op_xchg_cx() {
  var w = regs_2[I_AX_2];
  regs_2[I_AX_2] = regs_2[I_CX_2];
  regs_2[I_CX_2] = w;
}

// XCHG AX,DX
function op_xchg_dx() {
  var w = regs_2[I_AX_2];
  regs_2[I_AX_2] = regs_2[I_DX_2];
  regs_2[I_DX_2] = w;
}

// XCHG AX,BX
function op_xchg_bx() {
  var w = regs_2[I_AX_2];
  regs_2[I_AX_2] = regs_2[I_BX_2];
  regs_2[I_BX_2] = w;
}

// XCHG AX,SP
function op_xchg_sp() {
  var w = regs_2[I_AX_2];
  regs_2[I_AX_2] = regs_2[I_SP_2];
  regs_2[I_SP_2] = w;
}

// XCHG AX,BP
function op_xchg_bp() {
  var w = regs_2[I_AX_2];
  regs_2[I_AX_2] = regs_2[I_BP_2];
  regs_2[I_BP_2] = w;
}

// XCHG AX,SI
function op_xchg_si() {
  var w = regs_2[I_AX_2];
  regs_2[I_AX_2] = regs_2[I_SI_2];
  regs_2[I_SI_2] = w;
}

// XCHG AX,DI
function op_xchg_di() {
  var w = regs_2[I_AX_2];
  regs_2[I_AX_2] = regs_2[I_DI_2];
  regs_2[I_DI_2] = w;
}

// CBW
function op_cbw() {
  regs[I_AH] = (regs[I_AL] & 0x80) ? 0xff : 0x00;
}

// CWD
function op_cwd() {
  regs_2[I_DX_2] = (regs[I_AH] & 0x80) ? 0xffff : 0x0000;
}

// CALL FAR
function op_call_far() {
  var new_ip = get_code2();
  push(cs4 >> 4);
  cs4 = get_code2() << 4;
  push(ip);
  ip = new_ip;
}

// PUSHF
function op_pushf() {
  push_2((flag_s << 7) | (flag_z << 6) | (flag_a << 4) | (flag_p << 2) | 0x02 | flag_c,
         (flag_o << 3) | (flag_d << 2) | (flag_i << 1) | flag_t);
}

// POPF
function op_popf() {
  var flags = pop();
  flag_o = (flags & 0x0800) >> 11;
  flag_d = (flags & 0x0400) >> 10;
  flag_i = (flags & 0x0200) >> 9;
  flag_t = (flags & 0x0100) >> 8;
  flag_s = (flags & 0x0080) >> 7;
  flag_z = (flags & 0x0040) >> 6;
  flag_a = (flags & 0x0010) >> 4;
  flag_p = (flags & 0x0004) >> 2;
  flag_c = flags & 0x0001;

  if(flag_i)
    run_icnt = 0;
}

// SAHF
function op_sahf() {
  var flags = regs[I_AH];
  flag_s = (flags & 0x80) >> 7;
  flag_z = (flags & 0x40) >> 6;
  flag_a = (flags & 0x10) >> 4;
  flag_p = (flags & 0x04) >> 2;
  flag_c = flags & 0x01;
}

// LAHF
function op_lahf() {
  regs[I_AH] = (flag_s << 7) | (flag_z << 6) | (flag_a << 4) | (flag_p << 2) | 0x02 | flag_c;
}

// MOV AL,MEM8
function op_mov_l1() {
  data_addr(get_code2(), 0);
  regs[I_AL] = mem[dat_i_mem0];
}

// MOV AX,MEM16
function op_mov_l2() {
  data_addr(get_code2(), 1);
  regs[I_AL] = mem[dat_i_mem0];
  regs[I_AH] = mem[dat_i_mem1];
}

// MOV MEM8,AL
function op_mov_t1() {
  data_addr(get_code2(), 0);
  mem[dat_i_mem0] = regs[I_AL];
}

// MOV MEM16,AX
function op_mov_t2() {
  data_addr(get_code2(), 1);
  mem[dat_i_mem0] = regs[I_AL];
  mem[dat_i_mem1] = regs[I_AH];
}

// MOVSB
function op_movsb() {
  var si = get_reg2(I_SI);
  var di = get_reg2(I_DI);
  data_addr(si, 0);
  mem[(es4 + di) & 0xfffff] = mem[dat_i_mem0];
  if(flag_d) {
    set_reg2(I_SI, (si - 1) & 0xffff);
    set_reg2(I_DI, (di - 1) & 0xffff);
  }
  else {
    set_reg2(I_SI, (si + 1) & 0xffff);
    set_reg2(I_DI, (di + 1) & 0xffff);
  }
}

// MOVSW
function op_movsw() {
  var si = get_reg2(I_SI);
  var di = get_reg2(I_DI);
  data_addr(si, 1);
  var w = mem[dat_i_mem1];
  mem[(es4 + di) & 0xfffff] = mem[dat_i_mem0];
  mem[(es4 + ((di + 1) & 0xffff)) & 0xfffff] = w;
  if(flag_d) {
    set_reg2(I_SI, (si - 2) & 0xffff);
    set_reg2(I_DI, (di - 2) & 0xffff);
  }
  else {
    set_reg2(I_SI, (si + 2) & 0xffff);
    set_reg2(I_DI, (di + 2) & 0xffff);
  }
}

// CMPSB
function op_cmpsb() {
  var si = get_reg2(I_SI);
  var di = get_reg2(I_DI);
  data_addr(si, 0);
  sub_1(mem[dat_i_mem0], mem[(es4 + di) & 0xfffff], 0);
  if(flag_d) {
    set_reg2(I_SI, (si - 1) & 0xffff);
    set_reg2(I_DI, (di - 1) & 0xffff);
  }
  else {
    set_reg2(I_SI, (si + 1) & 0xffff);
    set_reg2(I_DI, (di + 1) & 0xffff);
  }
}

// CMPSW
function op_cmpsw() {
  var si = get_reg2(I_SI);
  var di = get_reg2(I_DI);
  data_addr(si, 1);
  sub_2(get_dat_mem2(), get_mem2(es4, di), 0);
  if(flag_d) {
    set_reg2(I_SI, (si - 2) & 0xffff);
    set_reg2(I_DI, (di - 2) & 0xffff);
  }
  else {
    set_reg2(I_SI, (si + 2) & 0xffff);
    set_reg2(I_DI, (di + 2) & 0xffff);
  }
}

// TEST AL,IMMED8
function op_test_im1() {
  flags_log1(regs[I_AL] & get_code1());
}

// TEST AX,IMMED16
function op_test_im2() {
  get_code2_2();
  flags_log2(regs[I_AL] & mem[cod_i_mem0], regs[I_AH] & mem[cod_i_mem1]);
}

// STOSB
function op_stosb() {
  var di = get_reg2(I_DI);
  mem[(es4 + di) & 0xfffff] = regs[I_AL];
  set_reg2(I_DI, ((flag_d) ? (di - 1) : (di + 1)) & 0xffff);
}

// STOSW
function op_stosw() {
  var di = get_reg2(I_DI);
  mem[(es4 + di) & 0xfffff] = regs[I_AL];
  mem[(es4 + ((di + 1) & 0xffff)) & 0xfffff] = regs[I_AH];
  set_reg2(I_DI, ((flag_d) ? (di - 2) : (di + 2)) & 0xffff);
}

// LODSB
function op_lodsb() {
  var si = get_reg2(I_SI);
  data_addr(si, 0);
  regs[I_AL] = mem[dat_i_mem0];
  set_reg2(I_SI, ((flag_d) ? (si - 1) : (si + 1)) & 0xffff);
}

// LODSW
function op_lodsw() {
  var si = get_reg2(I_SI);
  data_addr(si, 1);
  regs[I_AL] = mem[dat_i_mem0];
  regs[I_AH] = mem[dat_i_mem1];
  set_reg2(I_SI, ((flag_d) ? (si - 2) : (si + 2)) & 0xffff);
}

// SCASB
function op_scasb() {
  var di = get_reg2(I_DI);
  sub_1(regs[I_AL], mem[(es4 + di) & 0xfffff], 0);
  set_reg2(I_DI, ((flag_d) ? (di - 1) : (di + 1)) & 0xffff);
}

// SCASW
function op_scasw() {
  var di = get_reg2(I_DI);
  sub_2(get_reg2(I_AX), get_mem2(es4, di), 0);
  set_reg2(I_DI, ((flag_d) ? (di - 2) : (di + 2)) & 0xffff);
}

// MOV AL,IMMED8
function op_mov_al_im() {
  regs[I_AL] = get_code1();
}

// MOV CL,IMMED8
function op_mov_cl_im() {
  regs[I_CL] = get_code1();
}

// MOV DL,IMMED8
function op_mov_dl_im() {
  regs[I_DL] = get_code1();
}

// MOV BL,IMMED8
function op_mov_bl_im() {
  regs[I_BL] = get_code1();
}

// MOV AH,IMMED8
function op_mov_ah_im() {
  regs[I_AH] = get_code1();
}

// MOV CH,IMMED8
function op_mov_ch_im() {
  regs[I_CH] = get_code1();
}

// MOV DH,IMMED8
function op_mov_dh_im() {
  regs[I_DH] = get_code1();
}

// MOV BH,IMMED8
function op_mov_bh_im() {
  regs[I_BH] = get_code1();
}

// MOV AX,IMMED16
function op_mov_ax_im() {
  get_code2_2();
  regs[I_AL] = mem[cod_i_mem0];
  regs[I_AH] = mem[cod_i_mem1];
}

// MOV CX,IMMED16
function op_mov_cx_im() {
  get_code2_2();
  regs[I_CL] = mem[cod_i_mem0];
  regs[I_CH] = mem[cod_i_mem1];
}

// MOV DX,IMMED16
function op_mov_dx_im() {
  get_code2_2();
  regs[I_DL] = mem[cod_i_mem0];
  regs[I_DH] = mem[cod_i_mem1];
}

// MOV BX,IMMED16
function op_mov_bx_im() {
  get_code2_2();
  regs[I_BL] = mem[cod_i_mem0];
  regs[I_BH] = mem[cod_i_mem1];
}

// MOV SP,IMMED16
function op_mov_sp_im() {
  get_code2_2();
  regs[I_SP_L] = mem[cod_i_mem0];
  regs[I_SP_H] = mem[cod_i_mem1];
}

// MOV BP,IMMED16
function op_mov_bp_im() {
  get_code2_2();
  regs[I_BP_L] = mem[cod_i_mem0];
  regs[I_BP_H] = mem[cod_i_mem1];
}

// MOV SI,IMMED16
function op_mov_si_im() {
  get_code2_2();
  regs[I_SI_L] = mem[cod_i_mem0];
  regs[I_SI_H] = mem[cod_i_mem1];
}

// MOV DI,IMMED16
function op_mov_di_im() {
  get_code2_2();
  regs[I_DI_L] = mem[cod_i_mem0];
  regs[I_DI_H] = mem[cod_i_mem1];
}

// RET IMMED16
function op_ret_n() {
  var inc = get_code2();
  ip = pop();
  set_reg2(I_SP, (get_reg2(I_SP) + inc) & 0xffff);
}

// RET
function op_ret() {
  ip = pop();
}

// LES REG16,MEM16
function op_les() {
  decode_mrm(-1, 2);
  if(!(dat_i_mem0 & 0x100000)) {  // レジスタ以外
    set_mrm_reg2(get_dat_mem2());
    es4 = data_seg4;
  }
}

// LDS REG16,MEM16
function op_lds() {
  decode_mrm(-1, 2);
  if(!(dat_i_mem0 & 0x100000)) {  // レジスタ以外
    set_mrm_reg2(get_dat_mem2());
    ds4 = data_seg4;
  }
}

// MOV MEM8,IMMED8
function op_mov_m_im1() {
  var op2;
  if(((op2 = get_code1()) & 0x38) == 0x00) {
    decode_mrm(op2, 0);
    mem[dat_i_mem0] = get_code1();
  }
}

// MOV MEM16,IMMED16
function op_mov_m_im2() {
  var op2;
  if(((op2 = get_code1()) & 0x38) == 0x00) {
    decode_mrm(op2, 1);
    get_code2_2();
    mem[dat_i_mem0] = mem[cod_i_mem0];
    mem[dat_i_mem1] = mem[cod_i_mem1];
  }
}

// RETF IMMED16
function op_retf_n() {
  var inc = get_code2();
  ip = pop();
  cs4 = pop() << 4;
  set_reg2(I_SP, (get_reg2(I_SP) + inc) & 0xffff);
}

// RETF
function op_retf() {
  ip = pop();
  cs4 = pop() << 4;
}

// INT 3
function op_int_3() {
  int(0x03);
}

// INT IMMED8
function op_int_n() {
  var n = get_code1();

  // BIOS エミュレーション
  switch(n) {
  case 0x10:  // Video Services
    bios_video();
    return;
  case 0x11:  // Equipment List
    bios_equip();
    return;
  case 0x12:  // Memory Size
    bios_memory();
    return;
  // INT 13H 割り込みをフックするプログラムのため,Disk BIOS については実際に割り込みを実行する.
//case 0x13:  // Low Level Disk Services
//  bios_disk();
//  return;
  case 0x14:  // Serial Port I/O
    bios_serial();
    return;
  case 0x15:  // Miscellaneous Services
    bios_misc();
    return;
  case 0x16:  // Keyboard Services
    bios_keyboard();
    return;
  case 0x17:  // Printer Services
    bios_printer();
    return;
  case 0x19:  // Bootstrap Loader
    bios_boot();
    return;
  case 0x1a:  // Real Time Clock Services
    bios_clock();
    return;
  }

  int(n);
}

// Disk BIOS 割り込み デフォルト処理
function bios_disk_int_def() {
  bios_disk();

  // 結果のキャリー フラグ設定
  var i_mem = (ss4 + ((get_reg2(I_SP) + 4) & 0xffff)) & 0xfffff;
  mem[i_mem] = mem[i_mem] & 0xfe | flag_c;
}

// INTO
function op_into() {
  if(flag_o)
    int(0x04);
}

// IRET
function op_iret() {
  ip = pop();
  cs4 = pop() << 4;
  op_popf();
}

// ROL/ROR/RCL/RCR/SHL(SAL)/SHR/SAR REG8/MEM8,1
function op_rs_1_1() {
  rs_1(1);
}

// ROL/ROR/RCL/RCR/SHL(SAL)/SHR/SAR REG16/MEM16,1
function op_rs_1_2() {
  rs_2(1);
}

// ROL/ROR/RCL/RCR/SAL(SHL)/SHR/SAR REG8/MEM8,CL
function op_rs_n_1() {
  rs_1(regs[I_CL]);
}

// ROL/ROR/RCL/RCR/SAL(SHL)/SHR/SAR REG16/MEM16,CL
function op_rs_n_2() {
  rs_2(regs[I_CL]);
}

// ローテート/シフト  バイト用
function rs_1(n) {
  var op2;
  var op_sub;
  if(((op_sub = (op2 = get_code1()) & 0x38)) != 0x30) {
    decode_mrm(op2, 0);
    if(!n)
      return;
    var val = mem[dat_i_mem0];

    switch(op_sub) {
    case 0x00:  // ROL
      n &= 0x7;
      val = (val << n) & 0xff | (val >> (8 - n));
      flag_c = val & 0x01;
      flag_o = ((val & 0x80) >> 7) ^ flag_c;
      break;

    case 0x08:  // ROR
      n &= 0x7;
      val = (val >> n) | (val << (8 - n)) & 0xff;
      flag_o = ((val >> 7) ^ (val >> 6)) & 0x1;
      flag_c = (val & 0x80) >> 7;
      break;

    case 0x10:  // RCL
      n %= 9;
      val |= flag_c << 8;
      val = (val << n) | (val >> (9 - n));
      flag_c = (val & 0x100) >> 8;
      flag_o = ((val & 0x80) >> 7) ^ flag_c;
      val &= 0xff;
      break;

    case 0x18:  // RCR
      n %= 9;
      val |= flag_c << 8;
      val = (val >> n) | (val << (9 - n));
      flag_o = ((val >> 7) ^ (val >> 6)) & 0x1;
      flag_c = (val & 0x100) >> 8;
      val &= 0xff;
      break;

    case 0x20:  // SHL(SAL)
      if(n > 9)
        n = 9;
      val <<= n;
      flag_c = (val & 0x100) >> 8;
      flag_o = ((val & 0x80) >> 7) ^ flag_c;
      flags_szp1(val &= 0xff);
      break;

    case 0x28:  // SHR
      if(n > 9)
        n = 9;
      val >>= n - 1;
      flag_o = val >> 6;
      flag_c = val & 0x1;
      flags_szp1(val >>= 1);
      break;

    default:  // SAR
      if(n > 9)
        n = 9;
      if(val & 0x80)
        val |= 0xffffff00;
      val >>= n - 1;
      flag_o = 0;
      flag_c = val & 0x1;
      flags_szp1(val = (val >> 1) & 0xff);
    }

    mem[dat_i_mem0] = val;
  }
}

// ローテート/シフト  ワード用
function rs_2(n) {
  var op2;
  var op_sub;
  if(((op_sub = (op2 = get_code1()) & 0x38)) != 0x30) {
    decode_mrm(op2, 1);
    if(!n)
      return;
    var val = get_dat_mem2();

    switch(op_sub) {
    case 0x00:  // ROL
      n &= 0xf;
      val = (val << n) & 0xffff | (val >> (16 - n));
      flag_c = val & 0x0001;
      flag_o = ((val & 0x8000) >> 15) ^ flag_c;
      break;

    case 0x08:  // ROR
      n &= 0xf;
      val = (val >> n) | (val << (16 - n)) & 0xffff;
      flag_o = ((val >> 15) ^ (val >> 14)) & 0x1;
      flag_c = (val & 0x8000) >> 15;
      break;

    case 0x10:  // RCL
      n %= 17;
      val |= flag_c << 16;
      val = (val << n) | (val >> (17 - n));
      flag_c = (val & 0x10000) >> 16;
      flag_o = ((val & 0x8000) >> 15) ^ flag_c;
      val &= 0xffff;
      break;

    case 0x18:  // RCR
      n %= 17;
      val |= flag_c << 16;
      val = (val >> n) | (val << (17 - n));
      flag_o = ((val >> 15) ^ (val >> 14)) & 0x1;
      flag_c = (val & 0x10000) >> 16;
      val &= 0xffff;
      break;

    case 0x20:  // SHL(SAL)
      if(n > 17)
        n = 17;
      val <<= n;
      flag_c = (val & 0x10000) >> 16;
      flag_o = ((val & 0x8000) >> 15) ^ flag_c;
      flags_szp2(val &= 0xffff);
      break;

    case 0x28:  // SHR
      if(n > 17)
        n = 17;
      val >>= n - 1;
      flag_o = val >> 14;
      flag_c = val & 0x1;
      flags_szp2(val >>= 1);
      break;

    default:  // SAR
      if(n > 17)
        n = 17;
      if(val & 0x8000)
        val |= 0xffff0000;
      val >>= n - 1;
      flag_o = 0;
      flag_c = val & 0x1;
      flags_szp2(val = (val >> 1) & 0xffff);
    }

    set_dat_mem2(val);
  }
}

// AAM
function op_aam() {
  if(get_code1() == 0x0a) {
    var val = regs[I_AL];
    var rem;
    flags_szp1(regs[I_AL] = rem = val % 10);
    regs[I_AH] = (val - rem) / 10;
  }
}

// AAD
function op_aad() {
  if(get_code1() == 0x0a) {
    flags_szp1(regs[I_AL] = (regs[I_AH] * 10 + regs[I_AL]) & 0xff);
    regs[I_AH] = 0;
  }
}

// XLAT
function op_xlat() {
  data_addr((get_reg2(I_BX) + regs[I_AL]) & 0xffff, 0);
  regs[I_AL] = mem[dat_i_mem0];
}

// LOOPNE
function op_loopne() {
  var cx;
  set_reg2(I_CX, cx = (get_reg2(I_CX) - 1) & 0xffff);
  jmp_short(cx && !flag_z);
}

// LOOPE
function op_loope() {
  var cx;
  set_reg2(I_CX, cx = (get_reg2(I_CX) - 1) & 0xffff);
  jmp_short(cx && flag_z);
}

// LOOP
function op_loop() {
  var cx;
  set_reg2(I_CX, cx = (get_reg2(I_CX) - 1) & 0xffff);
  jmp_short(cx);
}

// JCXZ
function op_jcxz() {
  jmp_short(!regs_2[I_CX_2]);
}

// IN AL,IMMED8
function op_in_1() {
  in_1(get_code1());
}

// IN AX,IMMED8
function op_in_2() {
  in_2(get_code1());
}

// OUT IMMED8,AL
function op_out_1() {
  out_1(get_code1());
}

// OUT IMMED8,AX
function op_out_2() {
  out_2(get_code1());
}

// CALL NEAR
function op_call_near() {
  var disp = get_code2();
  push(ip);
  ip = (ip + disp) & 0xffff;
}

// JMP NEAR
function op_jmp_near() {
  var disp = get_code2();
  ip = (ip + disp) & 0xffff;
}

// JMP FAR
function op_jmp_far() {
  var new_ip = get_code2();
  cs4 = get_code2() << 4;
  ip = new_ip;
}

// JMP SHORT
function op_jmp() {
  var disp = get_code_sx();
  ip = (ip + disp) & 0xffff;
}

// IN AL,DX
function op_in_dx1() {
  in_1(get_reg2(I_DX));
}

// IN AX,DX
function op_in_dx2() {
  in_2(get_reg2(I_DX));
}

// OUT DX,AL
function op_out_dx1() {
  out_1(get_reg2(I_DX));
}

// OUT DX,AX
function op_out_dx2() {
  out_2(get_reg2(I_DX));
}

// REPNE
function op_repne() {
  rep(1);
}

// REP/REPE
function op_repe() {
  rep(0);
}

function rep(z) {
  var wip = ip;
  var code = mem[(cs4 + wip) & 0xfffff];
  if(code == 0xf0) {  // LOCK
    wip = (wip + 1) & 0xffff;
    code = mem[(cs4 + wip) & 0xfffff];
  }
  if((code & 0xe7) == 0x26) {  // セグメント オーバーライド
    seg4_ovr = code & 0x18;
    wip = (wip + 1) & 0xffff;
    code = mem[(cs4 + wip) & 0xfffff];
  }

  // MOVSB,MOVSW,STOSB,STOSW,LODSB,LODSW については
  // REPNE は REP(REPE)と同じ動作になる
  var cx;
  var si, di;
  var dir;
  var seg4;
  var al;
  switch(code) {
  case 0xa4:  // MOVSB
    src_addr();
    di = get_reg2(I_DI);
    dir = (flag_d) ? -1 : 1;
    for(cx = get_reg2(I_CX); cx; cx--) {
      mem[(es4 + di) & 0xfffff] = mem[(seg4 + si) & 0xfffff];
      si = (si + dir) & 0xffff;
      di = (di + dir) & 0xffff;
    }
    regs_2[I_CX_2] = 0;
    set_reg2(I_SI, si);
    set_reg2(I_DI, di);
    break;

  case 0xa5:  // MOVSW
    src_addr();
    di = get_reg2(I_DI);
    dir = (flag_d) ? -2 : 2;
    for(cx = get_reg2(I_CX); cx; cx--) {
      var w = mem[(seg4 + ((si + 1) & 0xffff)) & 0xfffff];
      mem[(es4 + di) & 0xfffff] = mem[(seg4 + si) & 0xfffff];
      mem[(es4 + ((di + 1) & 0xffff)) & 0xfffff] = w;
      si = (si + dir) & 0xffff;
      di = (di + dir) & 0xffff;
    }
    regs_2[I_CX_2] = 0;
    set_reg2(I_SI, si);
    set_reg2(I_DI, di);
    break;

  case 0xa6:  // CMPSB
    src_addr();
    di = get_reg2(I_DI);
    dir = (flag_d) ? -1 : 1;
    for(cx = get_reg2(I_CX); cx; ) {
      sub_1(mem[(seg4 + si) & 0xfffff], mem[(es4 + di) & 0xfffff], 0);
      si = (si + dir) & 0xffff;
      di = (di + dir) & 0xffff;
      cx--;
      if(flag_z == z)
        break;
    }
    set_reg2(I_CX, cx);
    set_reg2(I_SI, si);
    set_reg2(I_DI, di);
    break;

  case 0xa7:  // CMPSW
    src_addr();
    di = get_reg2(I_DI);
    dir = (flag_d) ? -2 : 2;
    for(cx = get_reg2(I_CX); cx; ) {
      sub_2(get_mem2(seg4, si), get_mem2(es4, di), 0);
      si = (si + dir) & 0xffff;
      di = (di + dir) & 0xffff;
      cx--;
      if(flag_z == z)
        break;
    }
    set_reg2(I_CX, cx);
    set_reg2(I_SI, si);
    set_reg2(I_DI, di);
    break;

  case 0xaa:  // STOSB
    di = get_reg2(I_DI);
    dir = (flag_d) ? -1 : 1;
    al = regs[I_AL];
    for(cx = get_reg2(I_CX); cx; cx--) {
      mem[(es4 + di) & 0xfffff] = al;
      di = (di + dir) & 0xffff;
    }
    regs_2[I_CX_2] = 0;
    set_reg2(I_DI, di);
    break;

  case 0xab:  // STOSW
    di = get_reg2(I_DI);
    dir = (flag_d) ? -2 : 2;
    al = regs[I_AL];
    var ah = regs[I_AH];
    for(cx = get_reg2(I_CX); cx; cx--) {
      mem[(es4 + di) & 0xfffff] = al;
      mem[(es4 + ((di + 1) & 0xffff)) & 0xfffff] = ah;
      di = (di + dir) & 0xffff;
    }
    regs_2[I_CX_2] = 0;
    set_reg2(I_DI, di);
    break;

  case 0xac:  // LODSB
    src_addr();
    dir = (flag_d) ? -1 : 1;
    for(cx = get_reg2(I_CX); cx; cx--) {
      regs[I_AL] = mem[(seg4 + si) & 0xfffff];
      si = (si + dir) & 0xffff;
    }
    regs_2[I_CX_2] = 0;
    set_reg2(I_SI, si);
    break;

  case 0xad:  // LODSW
    src_addr();
    dir = (flag_d) ? -2 : 2;
    for(cx = get_reg2(I_CX); cx; cx--) {
      regs[I_AL] = mem[(seg4 + si) & 0xfffff];
      regs[I_AH] = mem[(seg4 + ((si + 1) & 0xffff)) & 0xfffff];
      si = (si + dir) & 0xffff;
    }
    regs_2[I_CX_2] = 0;
    set_reg2(I_SI, si);
    break;

  case 0xae:  // SCASB
    di = get_reg2(I_DI);
    dir = (flag_d) ? -1 : 1;
    al = regs[I_AL];
    for(cx = get_reg2(I_CX); cx; ) {
      sub_1(al, mem[(es4 + di) & 0xfffff], 0);
      di = (di + dir) & 0xffff;
      cx--;
      if(flag_z == z)
        break;
    }
    set_reg2(I_CX, cx);
    set_reg2(I_DI, di);
    break;

  case 0xaf:  // SCASW
    di = get_reg2(I_DI);
    dir = (flag_d) ? -2 : 2;
    var ax = get_reg2(I_AX);
    for(cx = get_reg2(I_CX); cx; ) {
      sub_2(ax, get_mem2(es4, di), 0);
      di = (di + dir) & 0xffff;
      cx--;
      if(flag_z == z)
        break;
    }
    set_reg2(I_CX, cx);
    set_reg2(I_DI, di);
    break;

  default:
    return;
  }

  ip = (wip + 1) & 0xffff;

  function src_addr() {
    switch(seg4_ovr) {
    case 0x00:
      seg4 = es4;
      break;
    case 0x08:
      seg4 = cs4;
      break;
    case 0x10:
      seg4 = ss4;
      break;
    default:
      seg4 = ds4;
    }

    si = get_reg2(I_SI);
  }
}

// HLT
function op_hlt() {
  run_halt = true;
}

// CMC
function op_cmc() {
  flag_c ^= 0x1;
}

// TEST REG8/MEM8,IMMED8
// NOT/NEG/MUL/IMUL/DIV/IDIV REG8/MEM8
function op_misc4() {
  var op2;
  var val;
  var div;
  var rem;
  switch((op2 = get_code1()) & 0x38) {
  case 0x00:  // TEST
    decode_mrm(op2, 0);
    flags_log1(mem[dat_i_mem0] & get_code1());
    break;

  case 0x10:  // NOT
    decode_mrm(op2, 0);
    mem[dat_i_mem0] ^= 0xff;
    break;

  case 0x18:  // NEG
    decode_mrm(op2, 0);
    val = mem_s[dat_i_mem0];
    flag_o = (val == 0xffffff80) ? 1 : 0;
    flag_a = (val & 0xf) ? 1 : 0;
    flag_c = (val) ? 1 : 0;
    flags_szp1(mem[dat_i_mem0] = (- val) & 0xff);
    break;

  case 0x20:  // MUL
    decode_mrm(op2, 0);
    set_reg2(I_AX, val = regs[I_AL] * mem[dat_i_mem0]);
    flag_o = flag_c = (val & 0xff00) ? 1 : 0;
    break;

  case 0x28:  // IMUL
    decode_mrm(op2, 0);
    val = regs_s[I_AL] * mem_s[dat_i_mem0];
    set_reg2(I_AX, val & 0xffff);
    flag_o = flag_c = (val < -128 || val > 127) ? 1 : 0;
    break;

  case 0x30:  // DIV
    decode_mrm(op2, 0);
    val = get_reg2(I_AX);
    if(div = mem[dat_i_mem0]) {
      regs[I_AH] = rem = val % div;
      regs[I_AL] = (val = (val - rem) / div) & 0xff;
      if(!(val & 0xff00))
        break;
    }
    int(0x00);
    return;

  case 0x38:  // IDIV
    decode_mrm(op2, 0);
    val = get_reg2s(I_AX);
    if(div = mem_s[dat_i_mem0]) {
      regs[I_AH] = (rem = val % div) & 0xff;
      regs[I_AL] = (val = (val - rem) / div) & 0xff;
      if(val >= -128 && val <= 127)
        break;
    }
    int(0x00);
    return;
  }
}

// TEST REG16/MEM16,IMMED16
// NOT/NEG/MUL/IMUL/DIV/IDIV REG16/MEM16
function op_misc5() {
  var op2;
  var val;
  var div;
  var rem;
  switch((op2 = get_code1()) & 0x38) {
  case 0x00:  // TEST
    decode_mrm(op2, 1);
    get_code2_2();
    flags_log2(mem[dat_i_mem0] & mem[cod_i_mem0], mem[dat_i_mem1] & mem[cod_i_mem1]);
    break;

  case 0x10:  // NOT
    decode_mrm(op2, 1);
    mem[dat_i_mem0] ^= 0xff;
    mem[dat_i_mem1] ^= 0xff;
    break;

  case 0x18:  // NEG
    decode_mrm(op2, 1);
    val = get_dat_mem2s();
    flag_o = (val == 0xffff8000) ? 1 : 0;
    flag_a = (val & 0xf) ? 1 : 0;
    flag_c = (val) ? 1 : 0;
    set_dat_mem2(val = (- val) & 0xffff);
    flags_szp2(val);
    break;

  case 0x20:  // MUL
    decode_mrm(op2, 1);
    val = get_reg2(I_AX) * get_dat_mem2();
    set_reg2(I_AX, rem = val % 0x10000);
    set_reg2(I_DX, val = (val - rem) / 0x10000);
    flag_o = flag_c = (val) ? 1 : 0;
    break;

  case 0x28:  // IMUL
    decode_mrm(op2, 1);
    val = get_reg2s(I_AX) * get_dat_mem2s();
    set_reg2(I_DX, val >>> 16);
    set_reg2(I_AX, val & 0xffff);
    flag_o = flag_c = (val > 32767 || val < -32768) ? 1 : 0;
    break;

  case 0x30:  // DIV
    decode_mrm(op2, 1);
    val = get_reg2(I_DX) * 0x10000 + get_reg2(I_AX);
    if(div = get_dat_mem2()) {
      set_reg2(I_DX, rem = val % div);
      set_reg2(I_AX, (val = (val - rem) / div) % 0x10000);
      if(val < 0x10000)
        break;
    }
    int(0x00);
    return;

  case 0x38:  // IDIV
    decode_mrm(op2, 1);
    val = (get_reg2s(I_DX) << 16) | get_reg2(I_AX);
    if(div = get_dat_mem2s()) {
      set_reg2(I_DX, (rem = val % div) & 0xffff);
      set_reg2(I_AX, (val = (val - rem) / div) & 0xffff);
      if(val >= -32768 && val <= 32767)
        break;
    }
    int(0x00);
    return;
  }
}

// CLC
function op_clc() {
  flag_c = 0;
}

// STC
function op_stc() {
  flag_c = 1;
}

// CLI
function op_cli() {
  flag_i = 0;
}

// STI
function op_sti() {
  flag_i = 1;
  run_icnt = 0;
}

// CLD
function op_cld() {
  flag_d = 0;
}

// STD
function op_std() {
  flag_d = 1;
}

// INC/DEC REG8/MEM8
function op_misc6() {
  var op2;
  var val;
  switch((op2 = get_code1()) & 0x38) {
  case 0x00:  // INC
    decode_mrm(op2, 0);
    val = (mem[dat_i_mem0] + 1) & 0xff;
    flag_o = (val == 0x80) ? 1 : 0;
    flag_a = (val & 0xf) ? 0 : 1;
    flags_szp1(mem[dat_i_mem0] = val);
    break;

  case 0x08:  // DEC
    decode_mrm(op2, 0);
    val = mem[dat_i_mem0];
    flag_o = (val == 0x80) ? 1 : 0;
    flag_a = (val & 0xf) ? 0 : 1;
    flags_szp1(mem[dat_i_mem0] = (val - 1) & 0xff);
    break;
  }
}

// INC/DEC MEM16
// CALL/JMP REG16/MEM16
// CALL/JMP MEM16
// PUSH REG16/MEM16
function op_misc7() {
  var op2;
  var val;
  switch((op2 = get_code1()) & 0x38) {
  case 0x00:  // INC
    decode_mrm(op2, 1);
    val = (get_dat_mem2() + 1) & 0xffff;
    flag_o = (val == 0x8000) ? 1 : 0;
    flag_a = (val & 0xf) ? 0 : 1;
    set_dat_mem2(val);
    flags_szp2(val);
    break;

  case 0x08:  // DEC
    decode_mrm(op2, 1);
    val = get_dat_mem2();
    flag_o = (val == 0x8000) ? 1 : 0;
    flag_a = (val & 0xf) ? 0 : 1;
    set_dat_mem2(val = (val - 1) & 0xffff);
    flags_szp2(val);
    break;

  case 0x10:  // CALL
    decode_mrm(op2, 1);
    push(ip);
    ip = get_dat_mem2();
    break;

  case 0x18:  // CALL
    decode_mrm(op2, 2);
    if(!(dat_i_mem0 & 0x100000)) {  // レジスタ以外
      push(cs4 >> 4);
      push(ip);
      ip = get_dat_mem2();
      cs4 = data_seg4;
    }
    break;

  case 0x20:  // JMP
    decode_mrm(op2, 1);
    ip = get_dat_mem2();
    break;

  case 0x28:  // JMP
    decode_mrm(op2, 2);
    if(!(dat_i_mem0 & 0x100000)) {  // レジスタ以外
      ip = get_dat_mem2();
      cs4 = data_seg4;
    }
    break;

  case 0x30:  // PUSH
    decode_mrm(op2, 1);
    push_2(mem[dat_i_mem0], mem[dat_i_mem1]);
    break;
  }
}

function jmp_short(cond) {
  if(cond) {
    var disp = get_code_sx();
    ip = (ip + disp) & 0xffff;
  }
  else {
    ip = (ip + 1) & 0xffff;
  }
}

function int(n) {
  n <<= 2;
  op_pushf();
  push(cs4 >> 4);
  push(ip);
  flag_i = flag_t = 0;
  cs4 = (mem[n | 2] | (mem[n | 3] << 8)) << 4;
  ip = mem[n] | (mem[n | 1] << 8);
}

function decode_mrm(mrm, w) {
  if(mrm < 0)
    mrm = get_code1();
  mrm_i_reg = (mrm & 0x38) >> 2;
  if(!w && (mrm & 0x20))  // AH 〜 BH
    mrm_i_reg -= 7;
  var r_m = mrm & 0x07;
  var disp;

  switch(mrm & 0xc0) {
  case 0xc0:  // レジスタ
    dat_i_mem0 = 0x100000 + (r_m << 1);
    if(w) {
      dat_i_mem1 = dat_i_mem0 | 1;
    }
    else {
      if(r_m & 0x04)  // AH 〜 BH
        dat_i_mem0 -= 7;
    }
    return;

  case 0x40:  // 変位 1 バイト
    disp = get_code_sx();
    break;

  case 0x80:  // 変位 2 バイト
    disp = get_code2();
    break;

  default:  // 0x00
    if(r_m == 0x6) {  // 変位 2 バイト
      // インデックスなし
      data_addr(get_code2(), w);
      return;
    }
    // 変位なし
    disp = 0;
  }

  switch(r_m) {
  case 0x0:
    data_addr((disp + get_reg2(I_BX) + get_reg2(I_SI)) & 0xffff, w);
    break;
  case 0x1:
    data_addr((disp + get_reg2(I_BX) + get_reg2(I_DI)) & 0xffff, w);
    break;
  case 0x2:
    data_addr_ss((disp + get_reg2(I_BP) + get_reg2(I_SI)) & 0xffff, w);
    break;
  case 0x3:
    data_addr_ss((disp + get_reg2(I_BP) + get_reg2(I_DI)) & 0xffff, w);
    break;
  case 0x4:
    data_addr((disp + get_reg2(I_SI)) & 0xffff, w);
    break;
  case 0x5:
    data_addr((disp + get_reg2(I_DI)) & 0xffff, w);
    break;
  case 0x6:
    data_addr_ss((disp + get_reg2(I_BP)) & 0xffff, w);
    break;
  default:  // 0x7
    data_addr((disp + get_reg2(I_BX)) & 0xffff, w);
  }
}

function get_mrm_reg2() {
  return regs[mrm_i_reg] | (regs[mrm_i_reg | 1] << 8);
}

function set_mrm_reg2(val) {
  regs[mrm_i_reg] = val & 0xff;
  regs[mrm_i_reg | 1] = val >> 8;
}

function data_addr(off, w) {
  var seg4;
  switch(seg4_ovr) {
  case 0x00:
    seg4 = es4;
    break;
  case 0x08:
    seg4 = cs4;
    break;
  case 0x10:
    seg4 = ss4;
    break;
  default:
    seg4 = ds4;
  }

  dat_i_mem0 = (seg4 + off) & 0xfffff;
  if(w) {
    dat_i_mem1 = (seg4 + ((off + 1) & 0xffff)) & 0xfffff;
    if(w == 2)
      data_seg4 = get_mem2(seg4, off + 2) << 4;
  }
}

function data_addr_ss(off, w) {
  var seg4;
  switch(seg4_ovr) {
  case 0x00:
    seg4 = es4;
    break;
  case 0x08:
    seg4 = cs4;
    break;
  case 0x18:
    seg4 = ds4;
    break;
  default:
    seg4 = ss4;
  }

  dat_i_mem0 = (seg4 + off) & 0xfffff;
  if(w) {
    dat_i_mem1 = (seg4 + ((off + 1) & 0xffff)) & 0xfffff;
    if(w == 2)
      data_seg4 = get_mem2(seg4, off + 2) << 4;
  }
}

// 加算  バイト用
function add_1(val1, val2, cy) {
  var val = val1 + val2 + cy;
  var w = val1 ^ val2 ^ val;
  flag_o = ((w >> 7) ^ (val >> 8)) & 0x1;
  flag_a = (w >> 4) & 0x1;
  flag_c = (val >> 8) & 0x1;
  flags_szp1(val &= 0xff);
  return val;
}

// 加算  ワード用
function add_2(val1, val2, cy) {
  var val = val1 + val2 + cy;
  var w = val1 ^ val2 ^ val;
  flag_o = ((w >> 15) ^ (val >> 16)) & 0x1;
  flag_a = (w >> 4) & 0x1;
  flag_c = (val >> 16) & 0x1;
  flags_szp2(val &= 0xffff);
  return val;
}

// 減算  バイト用
function sub_1(val1, val2, cy) {
  val2 ^= 0xff;
  var val = val1 + val2 + (cy ^ 0x1);
  var w = val1 ^ val2 ^ val;
  flag_o = ((w >> 7) ^ (val >> 8)) & 0x1;
  flag_a = (w >> 4) & 0x1 ^ 0x1;
  flag_c = (val >> 8) & 0x1 ^ 0x1;
  flags_szp1(val &= 0xff);
  return val;
}

// 減算  ワード用
function sub_2(val1, val2, cy) {
  val2 ^= 0xffff;
  var val = val1 + val2 + (cy ^ 0x1);
  var w = val1 ^ val2 ^ val;
  flag_o = ((w >> 15) ^ (val >> 16)) & 0x1;
  flag_a = (w >> 4) & 0x1 ^ 0x1;
  flag_c = (val >> 16) & 0x1 ^ 0x1;
  flags_szp2(val &= 0xffff);
  return val;
}

function push(val) {
  var sp;
  set_reg2(I_SP, sp = (get_reg2(I_SP) - 2) & 0xffff);
  mem[(ss4 + sp) & 0xfffff] = val & 0xff;
  mem[(ss4 + ((sp + 1) & 0xffff)) & 0xfffff] = val >> 8;
}

function push_2(val_l, val_h) {
  var sp;
  set_reg2(I_SP, sp = (get_reg2(I_SP) - 2) & 0xffff);
  mem[(ss4 + sp) & 0xfffff] = val_l;
  mem[(ss4 + ((sp + 1) & 0xffff)) & 0xfffff] = val_h;
}

function pop() {
  var sp;
  var val = get_mem2(ss4, sp = get_reg2(I_SP));
  set_reg2(I_SP, (sp + 2) & 0xffff);
  return val;
}

// 論理演算後フラグ セット  バイト用
function flags_log1(val) {
  flag_o = flag_c = 0;
  flags_szp1(val);
}

// 論理演算後フラグ セット  ワード用
function flags_log2(val_l, val_h) {
  flag_o = flag_c = 0;

  // サイン
  flag_s = val_h >> 7;

  // ゼロ
  flag_z = (val_l | val_h) ? 0 : 1;

  // パリティ
  // パリティは下位バイトのみ判定する
  var w = val_l ^ (val_l >> 4);
  w ^= w >> 2;
  flag_p = (w ^ (w >> 1) ^ 0x1) & 0x1;
}

// サイン,ゼロ,パリティ フラグ セット  バイト用
function flags_szp1(val) {
  // サイン
  flag_s = val >> 7;

  // ゼロ
  flag_z = (val) ? 0 : 1;

  // パリティ
  var w = val ^ (val >> 4);
  w ^= w >> 2;
  flag_p = (w ^ (w >> 1) ^ 0x1) & 0x1;
}

// サイン,ゼロ,パリティ フラグ セット  ワード用
function flags_szp2(val) {
  // サイン
  flag_s = val >> 15;

  // ゼロ
  flag_z = (val) ? 0 : 1;

  // パリティ
  // パリティは下位バイトのみ判定する
  var w = val ^ (val >> 4);
  w ^= w >> 2;
  flag_p = (w ^ (w >> 1) ^ 0x1) & 0x1;
}

function get_code1() {
  var val = mem[(cs4 + ip) & 0xfffff];
  ip = (ip + 1) & 0xffff;
  return val;
}

function get_code2() {
  var val = mem[(cs4 + ip) & 0xfffff] | (mem[(cs4 + ((ip + 1) & 0xffff)) & 0xfffff] << 8);
  ip = (ip + 2) & 0xffff;
  return val;
}

function get_code2_2() {
  cod_i_mem0 = (cs4 + ip) & 0xfffff;
  cod_i_mem1 = (cs4 + ((ip + 1) & 0xffff)) & 0xfffff;
  ip = (ip + 2) & 0xffff;
}

function get_code_sx() {
  var val = mem_s[(cs4 + ip) & 0xfffff] & 0xffff;
  ip = (ip + 1) & 0xffff;
  return val;
}

function get_dat_mem2() {
  return mem[dat_i_mem0] | (mem[dat_i_mem1] << 8);
}

function get_dat_mem2s() {
  return mem[dat_i_mem0] | (mem_s[dat_i_mem1] << 8);
}

function set_dat_mem2(val) {
  mem[dat_i_mem0] = val & 0xff;
  mem[dat_i_mem1] = val >> 8;
}

function get_mem2(seg4, off) {
  return mem[(seg4 + off) & 0xfffff] | (mem[(seg4 + ((off + 1) & 0xffff)) & 0xfffff] << 8);
}

function get_reg2(i_reg) {
  return regs[i_reg] | (regs[i_reg | 1] << 8);
}

function get_reg2s(i_reg) {
  return regs[i_reg] | (regs_s[i_reg | 1] << 8);
}

function set_reg2(i_reg, val) {
  regs[i_reg] = val & 0xff;
  regs[i_reg | 1] = val >> 8;
}

// ---- BIOS/ハードウェア エミュレーション ----

  const INT_08_OFF = 0xfea5;
  const INT_08_OFF_2 = 0xfea6;
  const INT_09_OFF = 0xe987;
  const INT_13_OFF = 0xec59;
  const SYS_DESC_SEG4 = 0xf0000;
  const SYS_DESC_OFF = 0x0000;
  const FD_PARAM_SEG4 = 0xf0000;
  const FD_PARAM_OFF = 0xefc7;
  const FD_PARAM_8_OFF = 0x0010;
  const FD_PARAM_9_OFF = 0x0020;
  const FD_PARAM_10_OFF = 0x0030;

  // BIOS データ領域アドレス
  const MEM_EQUIP_LIST_0 = 0x410;
  const MEM_EQUIP_LIST_1 = 0x411;
  const MEM_MEM_SIZE_0 = 0x413;
  const MEM_MEM_SIZE_1 = 0x414;
  const MEM_KB_STATUS_0 = 0x417;
  const MEM_KB_STATUS_1 = 0x418;
  const MEM_KB_BUFF_HEAD_0 = 0x41a;
  const MEM_KB_BUFF_HEAD_1 = 0x41b;
  const MEM_KB_BUFF_TAIL_0 = 0x41c;
  const MEM_KB_BUFF_TAIL_1 = 0x41d;
  const MEM_KB_BUFF = 0x41e;
  const MEM_MOTOR_OFF = 0x440;
  const MEM_DISK_STATUS = 0x441;
  const MEM_VIDEO_MODE = 0x449;
  const MEM_SCR_COLS_0 = 0x44a;
  const MEM_SCR_COLS_1 = 0x44b;
  const MEM_VBUF_SIZE_0 = 0x44c;
  const MEM_VBUF_SIZE_1 = 0x44d;
  const MEM_VBUF_START_0 = 0x44e;
  const MEM_VBUF_START_1 = 0x44f;
  const MEM_CURSOR_POS = 0x450;
  const MEM_CURSOR_E = 0x460;
  const MEM_CURSOR_S = 0x461;
  const MEM_ACTIVE_PAGE = 0x462;
  const MEM_CRTC_PORT_0 = 0x463;
  const MEM_CRTC_PORT_1 = 0x464;
  const MEM_CRTC_MODE = 0x465;
  const MEM_CRTC_COLOR = 0x466;
  const MEM_TIMER_COUNTER_0 = 0x46c;
  const MEM_TIMER_COUNTER_1 = 0x46d;
  const MEM_TIMER_COUNTER_2 = 0x46e;
  const MEM_TIMER_COUNTER_3 = 0x46f;
  const MEM_TIMER_OVERFLOW = 0x470;
  const MEM_NUM_HD = 0x475;
  const MEM_SCR_ROW_MAX = 0x484;
  const MEM_KB_FLAGS = 0x496;

  const KB_BUFF_OFF = 0x1e;

  // I/O アドレス
  const I_DMA_STATUS = 0x8;
  const I_PIC_M_STATUS_0 = 0x20;
  const O_PIC_M_COMMAND_0 = 0x20;
  const I_PIC_M_STATUS_1 = 0x21;
  const O_PIC_M_COMMAND_1 = 0x21;
  const I_TIMER_0_COUNT = 0x40;
  const O_TIMER_0_COUNT = 0x40;
  const I_TIMER_2_COUNT = 0x42;
  const O_TIMER_2_COUNT = 0x42;
  const O_TIMER_CONTROL = 0x43;
  const I_PARALLEL_A = 0x60;
  const I_PARALLEL_B = 0x61;
  const O_PARALLEL_B = 0x61;
  const I_KB_STATUS = 0x64;
  const I_JOYSTICK = 0x201;
  const IO_MDA_0 = 0xb4;
  const IO_MDA_1 = 0x3;
  const O_MDA_INDEX = 0x3b4;
  const I_MDA_DATA = 0x3b5;
  const O_MDA_DATA = 0x3b5;
  const O_MDA_MODE = 0x3b8;
  const I_MDA_STATUS = 0x3ba;
  const IO_CGA_0 = 0xd4;
  const IO_CGA_1 = 0x3;
  const O_CGA_INDEX = 0x3d4;
  const I_CGA_DATA = 0x3d5;
  const O_CGA_DATA = 0x3d5;
  const O_CGA_MODE = 0x3d8;
  const I_CGA_UNKNOWN = 0x3d9;
  const O_CGA_COLOR = 0x3d9;
  const I_CGA_STATUS = 0x3da;
  const O_CGA_LP_CLEAR = 0x3db;
  const O_CGA_LP_PRESET = 0x3dc;
  const I_FDC_STATUS = 0x3f4;

  const OSC_INT = 0.0008380953445200438;  // 1000 / (14318180 / 12)
  const TIMER_FREQ = 1000 / OSC_INT;

  save_regs = new Uint16Array(mem.buffer, 0xe0000, 2304);  // レジスタ退避用領域(メモリの未使用領域を流用)

  mnt_image = [undefined, undefined];
  mnt_spec = [undefined, undefined];
  mnt_dirty = [false, false];

// BIOS/ハードウェア初期化
function bios_init() {
  // 割り込みベクター初期化
  mem[0xfff53] = 0xcf;  // IRET
  for(i = 0; i < 0x7c; i += 4) {
    mem[i]     = 0x53;
    mem[i | 1] = 0xff;
    mem[i | 2] = 0x00;
    mem[i | 3] = 0xf0;
  }

  // INT 08H
  mem[0xffea5] = 0x90;  // NOP
  mem[0xffea6] = 0xcf;  // IRET
  mem[0x20] = 0xa5;
  mem[0x21] = 0xfe;

  // INT 09H
  mem[0xfe987] = 0xcf;  // IRET
  mem[0x24] = 0x87;
  mem[0x25] = 0xe9;

  // INT 13H
  mem[0xfec59] = 0xcf;  // IRET
  mem[0x4c] = 0x59;
  mem[0x4d] = 0xec;

  // System Descriptor Table
  mem.set([8, 0, 0xff, 0, 0, 0x20, 0, 0, 0, 0], 0xf0000);
  // Model Number
  mem[0xffffe] = 0xff;

  // Video Parameters
  mem.set([ 56, 40, 45,10, 31, 6, 25, 28,2, 7, 6, 7,0x00,0x00,0x00,0x00,
           113, 80, 90,10, 31, 6, 25, 28,2, 7, 6, 7,0x00,0x00,0x00,0x00,
            56, 40, 45,10,127, 6,100,112,2, 1, 6, 7,0x00,0x00,0x00,0x00,
            97, 80, 82,15, 25, 6, 25, 25,2,13,11,12,0x00,0x00,0x00,0x00,
           0x00,0x08,0x00,0x10,0x00,0x40,0x00,0x40,
           40,40,80,80,40,40,80,80,
           0x2c,0x28,0x2d,0x29,0x2a,0x2e,0x1e,0x29
          ], 0xff0a4);
  mem[0x74] = 0xa4;
  mem[0x75] = 0xf0;

  // Diskette Parameters
  var dp = [0xf1, 0x02, 0, 2, 18, 27, 0xff, 80, 0xf6, 0, 0];
  mem.set(dp, 0xfefc7);
  mem[0x78] = 0xc7;
  mem[0x79] = 0xef;
  mem.set(dp, 0xf0010);
  mem[0xf0014] = 8;
  mem.set(dp, 0xf0020);
  mem[0xf0024] = 9;
  mem.set(dp, 0xf0030);
  mem[0xf0034] = 10;

  // CGA Graphics Character Font
  mem.set(new Uint8Array(video_font_8x8.buffer, 0, 1024), 0xffa6e);
  mem[0x7c] = mem[0x7d] = mem[0x7e] = mem[0x7f] = 0;

  // 最初の実行命令
  // このジャンプ命令の飛び先で機種(?)を判定しているプログラムがある
  mem.set([0xea, 0x5b, 0xe0, 0x00, 0xf0], 0xffff0);  // JMP F000:E05B

  // BIOS データ領域初期化

  mem.fill(0, 0x400, 0x410);  // COMn,LPTn ポート

  switch(INIT_MODE) {  // 初期ビデオ モード
  case 2:
  case 3:
  case 6:
    conf_sw1 = 0x6d;
    break;
  case 7:
    conf_sw1 = 0x7d;
    break;
  default:  // 0,1,4,5
    conf_sw1 = 0x5d;
  }
  mem[MEM_EQUIP_LIST_0] = conf_sw1;
  mem[MEM_EQUIP_LIST_1] = 0x00;

  mem[MEM_MEM_SIZE_0] = 0x80;  // 640
  mem[MEM_MEM_SIZE_1] = 0x02;

  mem[MEM_MOTOR_OFF] = 0;
  mem[MEM_DISK_STATUS] = 0;

  mem[MEM_TIMER_COUNTER_0] = mem[MEM_TIMER_COUNTER_1] = mem[MEM_TIMER_COUNTER_2] = mem[MEM_TIMER_COUNTER_3] = 0;
  mem[MEM_TIMER_OVERFLOW] = 0;

  mem[MEM_NUM_HD] = 0;

  timer_now = timer.now();

  // PIC
  pic_m_irr = pic_m_isr = pic_m_imr = 0x00;
  pic_m_ris = 0;

  // タイマー
  timer_0_org = timer_2_org = timer_now;
  timer_0_control = 0x36;
  timer_0_osc_cnt = timer_0_count = 0x10000;
  timer_0_next = timer_0_org + 0x10000 * OSC_INT;
  timer_2_control = 0xb6;
  timer_2_count = 1326;
  timer_0_o_lh = timer_0_i_lh = timer_2_o_lh = timer_2_i_lh = false;
  timer_0_latch = timer_2_latch = -1;
  timer_0_new_count = timer_2_new_count = 0;
  beep_osc.frequency.value = bell_freq = TIMER_FREQ / 1326;  // 900 Hz

  io_parallel_b = 0x00;
  sound_state = 0;
  timer_2_gate_off = timer_now;

  io_dma_status = 0x04;  // DMA チャネル 2 TC

  video_init();  // ビデオ初期化

  keyboard_init();  // キーボード初期化

  i_save_regs = 0;  // レジスタ退避インデックス
}

// Video Services
function bios_video() {
  var i_cur;
  var page;
  var char;
  var attr;
  var color;
  var val;
  var i_mem;
  var n;
  var x, y;

  var func;
  switch(func = regs[I_AH]) {
  case 0x00:  // Set Video Mode
    var mode;
    if((mode = regs[I_AL] & 0x7f) > 7)
      return;
    mem[MEM_VIDEO_MODE] = video_adapter_mode = video_mode = mode;
    video_comp_color = false;

    switch(video_mode) {  // ビデオ モード
    case 0:
    case 1:
      elem_scr.width = width320;
      elem_scr.height = height200;
      elem_scr.style.width = String(width320 << 1) + "px";
      elem_scr.style.height = String(height200 << 1) + "px";

      mem[MEM_CRTC_MODE] = (video_mode == 0) ? 0x2c : 0x28;
      mem[MEM_SCR_COLS_0] = video_cols = 40;
      mem[MEM_VBUF_SIZE_1] = 0x08;
      mem[MEM_CURSOR_S] = video_cursor_start = 6;
      mem[MEM_CURSOR_E] = video_cursor_end = 7;
      video_vertical_disp = 25;
      video_scan_lines = 8;
      video_i_buff_end = 64000/* 200*320 */;
      video_i_buff_adj = 2240/* 7*320 */;
      break;

    case 2:
    case 3:
      elem_scr.width = width640;
      elem_scr.height = height200;
      elem_scr.style.width = String(width640) + "px";
      elem_scr.style.height = String(height200 << 1) + "px";

      mem[MEM_CRTC_MODE] = (video_mode == 2) ? 0x2d : 0x29;
      mem[MEM_SCR_COLS_0] = video_cols = 80;
      mem[MEM_VBUF_SIZE_1] = 0x10;
      mem[MEM_CURSOR_S] = video_cursor_start = 6;
      mem[MEM_CURSOR_E] = video_cursor_end = 7;
      video_vertical_disp = 25;
      video_scan_lines = 8;
      video_i_buff_end = 128000/* 200*640 */;
      video_i_buff_adj = 4480/* 7*640 */;
      break;

    case 4:
      video_comp_color = true;
      // fall thru
    case 5:
      elem_scr.width = width320;
      elem_scr.height = height200;
      elem_scr.style.width = String(width320 << 1) + "px";
      elem_scr.style.height = String(height200 << 1) + "px";

      mem[MEM_CRTC_MODE] = (video_mode == 4) ? 0x2a : 0x2e;
      mem[MEM_SCR_COLS_0] = video_cols = 40;
      mem[MEM_VBUF_SIZE_1] = 0x40;
      mem[MEM_CURSOR_S] = mem[MEM_CURSOR_E] = 0;
      break;

    case 6:
      elem_scr.width = width640;
      elem_scr.height = height200;
      elem_scr.style.width = String(width640) + "px";
      elem_scr.style.height = String(height200 << 1) + "px";

      mem[MEM_CRTC_MODE] = 0x1e;
      mem[MEM_SCR_COLS_0] = video_cols = 80;
      mem[MEM_VBUF_SIZE_1] = 0x40;
      mem[MEM_CURSOR_S] = mem[MEM_CURSOR_E] = 0;
      break;

    default:  // 7
      elem_scr.width = width720;
      elem_scr.height = height350;
      elem_scr.style.width = String(width720) + "px";
      elem_scr.style.height = String(height350) + "px";

      mem[MEM_CRTC_MODE] = 0x29;
      mem[MEM_SCR_COLS_0] = video_cols = 80;
      mem[MEM_VBUF_SIZE_1] = 0x10;
      mem[MEM_CURSOR_S] = video_cursor_start = 11;
      mem[MEM_CURSOR_E] = video_cursor_end = 12;
    }
    elem_frm.style.width  = elem_scr.style.width;
    elem_frm.style.height = elem_scr.style.height;

    if(video_mode == 7) {  // MDA
      video_mem = new Uint8Array(mem.buffer, 0xb0000, 0x8000);
      mem[MEM_CRTC_PORT_0] = IO_MDA_0;
      mem[MEM_CRTC_PORT_1] = IO_MDA_1;

      video_lines = 370;
      video_line_osc_n = 1;
      video_line_osc_int = 0.05425355231592545;  // 1000 / (16257000 / 9) * 98
    }
    else {  // CGA
      video_mem = new Uint8Array(mem.buffer, 0xb8000, 0x8000);
      video_mem_o = new Uint8Array(mem.buffer, 0xba000, 0x2000)
      mem[MEM_CRTC_PORT_0] = IO_CGA_0;
      mem[MEM_CRTC_PORT_1] = IO_CGA_1;

      if(video_mode == 6) {
        video_cga_palette[0] = video_cga_white;
        video_comp_palette_phase_0 = 0xff;
        video_comp_palette_int[0] = 0x8;
      }
      else {
        video_cga_palette[0] = video_cga_black;
        video_comp_palette_phase_0 = 0x00;
        video_comp_palette_int[0] = 0x0;
      }
      video_comp_palette_phase[0] = video_comp_palette_phase_0;
      video_cga_palette[1] = video_cga_cyan;
      video_cga_palette[2] = (video_mode == 5) ? video_cga_red : video_cga_magenta;
      video_cga_palette[3] = video_cga_lgray;
      if(video_comp_color) {
        video_comp_palette_phase[1] = video_comp_phase_cyan;
        video_comp_palette_phase[2] = video_comp_phase_magenta;
      }
      else {
        video_comp_palette_phase[1] = video_comp_palette_phase[2] = 0xff;
      }
      video_comp_palette_phase[3] = 0xff;
      video_comp_palette_int[1] = video_comp_palette_int[2] = video_comp_palette_int[3] = 0x0;
      video_palette = true;
      video_bright = false;
      mem[MEM_CRTC_COLOR] = (video_mode == 6) ? 0x2f : 0x20;

      video_lines = 262;
      video_line_osc_n = 76;  // 16 * 57 / 12(8 * 114 / 12)
      video_line_osc_int = OSC_INT;
    }
    video_mem_2 = new Uint16Array(mem.buffer, video_mem.byteOffset, 0x4000)

    mem[MEM_ACTIVE_PAGE] = video_page = 0;
    video_start_addr = 0;
    mem[MEM_VBUF_START_1] = 0x00;
    mem.fill(0, MEM_CURSOR_POS, MEM_CURSOR_POS + 16);
    video_crtc_index = 0;
    video_cursor_mode = 0x40;
    video_cursor_addr = 0;
    video_lp_latch = 0;
    video_lp_trigger = 0x0;
    video_blink = true;
    video_disabled = false;

    video_line_org = timer_now;
    video_line_osc_cnt = video_line_osc_n;
    video_line_next = video_line_org + video_line_osc_n * video_line_osc_int;
    video_line_cnt = video_lines;
    video_color_chg = -1;

    ctx_scr.fillRect(0, 0, elem_scr.width, elem_scr.height);
    video_data = ctx_scr.getImageData(0, 0, elem_scr.width, elem_scr.height);
    video_buff = new Uint32Array(video_data.data.buffer);

    if(!(regs[I_AL] & 0x80)) {
      // ビデオ メモリ クリア
      switch(video_mode) {  // ビデオ モード
      case 4:
      case 5:
      case 6:
        video_mem.fill(0, 0, 0x4000);
        break;
      default:  // 0,1,2,3,7
        video_mem_2.fill(video_text_clear);
      }
    }

    break;

  case 0x01:  // Set Cursor Shape
    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      break;
    default:
      mem[MEM_CURSOR_S] = video_cursor_start = regs[I_CH] & 0x1f;
      mem[MEM_CURSOR_E] = video_cursor_end   = regs[I_CL] & 0x1f;
      // Bit 5,6 の設定は仕様外だが,カーソルを消すために使っているプログラムがある.
      // Bit 5: 1,Bit 6: 0 の場合のみ,カーソル非表示に設定する.
      video_cursor_mode = ((regs[I_CH] & 0x60) == 0x20) ? 0x20 : 0x40;
    }
    break;

  case 0x02:  // Set Cursor Position
    var row = regs[I_DH];
    var col = regs[I_DL];

    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      i_cur = MEM_CURSOR_POS;
      break;

    default:  // 0,1,2,3,7
      if((page = regs[I_BH]) >= 8)
        return;
      if(page == video_page)  // 現在表示中のページ
        video_cursor_addr = (row >= 25 || col >= video_cols)
                              ? 0x3fff : (page << ((video_mode < 2) ? 10 : 11)) + row * video_cols + col;
      i_cur = MEM_CURSOR_POS + (page << 1);
    }
    mem[i_cur] = col;
    mem[i_cur + 1] = row;
    break;

  case 0x03:  // Get Cursor Position and Shape
    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      regs[I_CH] = regs[I_CL] = 0;
      i_cur = MEM_CURSOR_POS;
      break;

    default:  // 0,1,2,3,7
      if((page = regs[I_BH]) >= 8)
        return;
      regs[I_CH] = video_cursor_start;
      regs[I_CL] = video_cursor_end;
      i_cur = MEM_CURSOR_POS + (page << 1);
    }
    regs[I_DL] = mem[i_cur];
    regs[I_DH] = mem[i_cur + 1];
    break;

  case 0x04:  // Read Light Pen Position
    regs[I_AH] = 0;
    break;

  case 0x05:  // Select Active Display Page
    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      video_start_addr = 0;
      mem[MEM_VBUF_START_1] = 0x00;
      break;
    default:  // 0,1,2,3,7
      if((page = regs[I_AL]) >= 8)
        return;
      mem[MEM_ACTIVE_PAGE] = video_page = page;
      video_start_addr = page << ((video_mode < 2) ? 10 : 11);
      mem[MEM_VBUF_START_1] = page << ((video_mode < 2) ? 3 : 4);
    }
    break;

  case 0x06:  // Clear/Scroll Up Window
  case 0x07:  // Clear/Scroll Down Window
    n = regs[I_AL];
    var row1 = regs[I_CH];
    var col1 = regs[I_CL];
    var row2 = regs[I_DH];
    var col2 = regs[I_DL];
    if(row1 >= 25 || col1 >= video_cols || row2 < row1 || col2 < col1)
      return;
    if(row2 >= 25)
      row2 = 24;
    if(col2 >= video_cols)
      col2 = video_cols - 1;
    var rows = row2 - row1 + 1;
    var i_mem2;
    var off;

    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      var b2 = (video_mode == 6) ? 0 : 1;
      var cols_bytes = (col2 - col1 + 1) << b2;

      if(func == 0x06) {
        i_mem = row1 * 320 + (col1 << b2);
        if(n && n < rows) {
          off = n * 320;
          for(rows = (rows - n) << 2; rows; rows--) {
            video_mem.copyWithin(i_mem, i_mem + off, i_mem + off + cols_bytes);
            i_mem2 = i_mem + 0x2000;
            video_mem.copyWithin(i_mem2, i_mem2 + off, i_mem2 + off + cols_bytes);
            i_mem += 80;
          }
        }
        else {
          n = rows;
        }
        for(n <<= 2; n; n--) {
          video_mem.fill(0, i_mem, i_mem + cols_bytes);
          i_mem2 = i_mem + 0x2000;
          video_mem.fill(0, i_mem2, i_mem2 + cols_bytes);
          i_mem += 80;
        }
      }
      else {
        i_mem = row2 * 320 + 240 + (col1 << b2);
        if(n && n < rows) {
          off = n * 320;
          for(rows = (rows - n) << 2; rows; rows--) {
            video_mem.copyWithin(i_mem, i_mem - off, i_mem - off + cols_bytes);
            i_mem2 = i_mem + 0x2000;
            video_mem.copyWithin(i_mem2, i_mem2 - off, i_mem2 - off + cols_bytes);
            i_mem -= 80;
          }
        }
        else {
          n = rows;
        }
        for(n <<= 2; n; n--) {
          video_mem.fill(0, i_mem, i_mem + cols_bytes);
          i_mem2 = i_mem + 0x2000;
          video_mem.fill(0, i_mem2, i_mem2 + cols_bytes);
          i_mem -= 80;
        }
      }
      break;

    default:  // 0,1,2,3,7
      video_mem_work[0] = 0x20;
      video_mem_work[1] = regs[I_BH];
      var cols = col2 - col1 + 1;
 
      if(func == 0x06) {
        i_mem = (video_page << ((video_mode < 2) ? 10 : 11)) + row1 * video_cols + col1;
        if(n && n < rows) {
          off = n * video_cols;
          for(rows -= n; rows; rows--) {
            video_mem_2.copyWithin(i_mem, i_mem + off, i_mem + off + cols);
            i_mem += video_cols;
          }
        }
        else {
          n = rows;
        }
        i_mem2 = i_mem << 1;
        for(; n; n--) {
          video_mem_2.fill(video_mem_work_2[0], i_mem, i_mem + cols);
          i_mem += video_cols;
        }
      }
      else {
        i_mem = (video_page << ((video_mode < 2) ? 10 : 11)) + row2 * video_cols + col1;
        if(n && n < rows) {
          off = n * video_cols;
          for(rows -= n; rows; rows--) {
            video_mem_2.copyWithin(i_mem, i_mem - off, i_mem - off + cols);
            i_mem -= video_cols;
          }
        }
        else {
          n = rows;
        }
        i_mem2 = i_mem << 1;
        for(; n; n--) {
          video_mem_2.fill(video_mem_work_2[0], i_mem, i_mem + cols);
          i_mem -= video_cols;
        }
      }
    }

    break;

  case 0x08:  // Read Character and Attribute at Cursor
    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      if((i_mem = cursor_pos()) < 0)
        return;
      regs[I_AL] = read_graphic_char(video_mode == 6);
      break;

    default:  // 0,1,2,3,7
      if((page = regs[I_BH]) >= 8)
        return;
      if((i_mem = cursor_pos()) < 0)
        return;
      regs[I_AH] = video_mem[i_mem + 1];  // 属性
      regs[I_AL] = video_mem[i_mem];  // 文字
    }
    break;

  case 0x09:  // Write Character and Attribute at Cursor
    attr = regs[I_BL];  // 属性/色
    // fall thru
  case 0x0a:  // Write Character at Cursor
    var wa = (func == 0x09);
    char = regs[I_AL];
    n = get_reg2(I_CX);
    var limit;

    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
      if((i_mem = cursor_pos()) < 0)
        return;
      limit = 40 - mem[MEM_CURSOR_POS];
      if(n > limit)
        n = limit;
      attr = (wa) ? attr & 0x83 : 0x3;
      for(; n; n--) {
        write_graphic_char_4_5(attr);
        i_mem += 2;
      }
      break;

    case 6:
      if((i_mem = cursor_pos()) < 0)
        return;
      limit = 80 - mem[MEM_CURSOR_POS];
      if(n > limit)
        n = limit;
      attr = (wa) ? attr & 0x80 : 0;
      for(; n; n--) {
        write_graphic_char_6(attr);
        i_mem++;
      }
      break;

    default:  // 0,1,2,3,7
      if((page = regs[I_BH]) >= 8)
        return;
      if((i_mem = cursor_pos()) < 0)
        return;
      limit = video_cols - mem[MEM_CURSOR_POS + (page << 1)];
      if(n > limit)
        n = limit;
      for(; n; n--) {
        video_mem[i_mem] = char;  // 文字
        if(wa)
          video_mem[i_mem + 1] = attr;  // 属性
        i_mem += 2;
      }
    }

    break;

  case 0x0b:  // Set Color Palette
    if(video_mode == 7)
      return;
    val = regs[I_BL];

    switch(regs[I_BH]) {
    case 0:
      video_cga_palette[0] = video_cga_color[val &= 0xf];
      video_comp_palette_phase_0 = video_comp_phase[val & 0x7];
      if(video_comp_color)
        video_comp_palette_phase[0] = video_comp_palette_phase_0;
      else
        video_comp_palette_phase[0] = (video_comp_palette_phase_0) ? 0xff : 0x00;
      video_comp_palette_int[0] = val & 0x8;
      mem[MEM_CRTC_COLOR] = mem[MEM_CRTC_COLOR] & 0xf0 | val;
      break;

    case 1:
      if(video_palette = ((val & 0x1) != 0)) {
        if(video_adapter_mode != 5) {
          video_cga_palette[1] = video_cga_cyan;
          video_cga_palette[2] = video_cga_magenta;
          video_cga_palette[3] = video_cga_lgray;
          if(video_comp_color) {
            video_comp_palette_phase[1] = video_comp_phase_cyan;
            video_comp_palette_phase[2] = video_comp_phase_magenta;
          }
          else {
            video_comp_palette_phase[1] = video_comp_palette_phase[2] = 0xff;
          }
          video_comp_palette_phase[3] = 0xff;
        }
        mem[MEM_CRTC_COLOR] |= 0x20;
      }
      else {
        if(video_adapter_mode != 5) {
          video_cga_palette[1] = video_cga_green;
          video_cga_palette[2] = video_cga_red;
          video_cga_palette[3] = video_cga_brown;
          if(video_comp_color) {
            video_comp_palette_phase[1] = video_comp_phase_green;
            video_comp_palette_phase[2] = video_comp_phase_red;
            video_comp_palette_phase[3] = video_comp_phase_yellow;
          }
          else {
            video_comp_palette_phase[1] = video_comp_palette_phase[2] = video_comp_palette_phase[3] = 0xff;
          }
        }
        mem[MEM_CRTC_COLOR] &= 0xdf;
      }
      if(video_adapter_mode == 5) {
        video_cga_palette[1] = video_cga_cyan;
        video_cga_palette[2] = video_cga_red;
        video_cga_palette[3] = video_cga_lgray;
        video_comp_palette_phase[1] = video_comp_palette_phase[2] = video_comp_palette_phase[3] = 0xff;
      }
      video_bright = false;
      video_comp_palette_int[1] = video_comp_palette_int[2] = video_comp_palette_int[3] = 0x0;
      break;
    }

    switch(video_mode) {
    case 4:
    case 5:
    case 6:
      video_refresh_req1 = true;  // 画面更新要求
      break;
    }

    break;

  case 0x0c:  // Write Graphics Pixel
    color = regs[I_AL];
    x = get_reg2(I_CX);
    y = get_reg2(I_DX);
    var shift;

    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
      i_mem = (y >> 1) * 80 + (x >> 2);
      if(y & 0x1)
        i_mem += 0x2000;
      shift = (x & 0x3) << 1;
      val = (color & 0x3) << (6 - shift);
      if(color & 0x80)
        video_mem[i_mem] ^= val;
      else
        video_mem[i_mem] = video_mem[i_mem] & (0x3f3f >> shift) | val;
      break;

    case 6:
      i_mem = (y >> 1) * 80 + (x >> 3);
      if(y & 0x1)
        i_mem += 0x2000;
      shift = x & 0x7;
      val = (color & 0x1) << (7 - shift);
      if(color & 0x80)
        video_mem[i_mem] ^= val;
      else
        video_mem[i_mem] = video_mem[i_mem] & (0x7f7f >> shift) | val;
      break;
    }
    break;

  case 0x0d:  // Read Graphics Pixel
    x = get_reg2(I_CX);
    y = get_reg2(I_DX);

    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
      i_mem = (y >> 1) * 80 + (x >> 2);
      if(y & 0x1)
        i_mem += 0x2000;
      regs[I_AL] = (video_mem[i_mem] >> (6 - ((x & 0x3) << 1))) & 0x3;
      break;

    case 6:
      i_mem = (y >> 1) * 80 + (x >> 3);
      if(y & 0x1)
        i_mem += 0x2000;
      regs[I_AL] = (video_mem[i_mem] >> (7 - (x & 0x7))) & 0x1;
      break;
    }
    break;

  case 0x0e:  // Write Character in Teletype Mode
    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      i_cur = MEM_CURSOR_POS;
      break;
    default:  // 0,1,2,3,7
      if((page = regs[I_BH]) >= 8)
        return;
      i_cur = MEM_CURSOR_POS + (page << 1);
    }
    if(mem[i_cur + 1] >= 25 || mem[i_cur] >= video_cols)
      return;

    switch(char = regs[I_AL]) {
    case 0x07:  // BEL
      if(run_wait)  // 待機中
        return;
      bell(bell_ended_0e);
      return;

    case 0x08:  // BS
      if(mem[i_cur])  // カラム
        mem[i_cur]--;
      break;

    case 0x0a:  // LF
      if(mem[i_cur + 1] == 24) {  // 行
        // スクロール
        switch(video_mode) {  // ビデオ モード
        case 4:
        case 5:
        case 6:
          tty_scroll_graphic();
          break;
        default:  // 0,1,2,3,7
          tty_scroll_text(page);
        }
      }
      else {
        mem[i_cur + 1]++;  // 行
      }
      break;

    case 0x0d:  // CR
      mem[i_cur] = 0;  // カラム
      break;

    default:
      i_mem = cursor_pos();

      switch(video_mode) {  // ビデオ モード
      case 4:
      case 5:
      case 6:
        if(video_mode == 6)
          write_graphic_char_6(0);
        else
          write_graphic_char_4_5(regs[I_BL] & 0x3);

        if(mem[MEM_CURSOR_POS] == video_cols - 1) {
          if(mem[MEM_CURSOR_POS + 1] == 24)
            // スクロール
            tty_scroll_graphic();
          else
            mem[MEM_CURSOR_POS + 1]++;
          mem[MEM_CURSOR_POS] = 0;
        }
        else {
          mem[MEM_CURSOR_POS]++;
        }
        break;

      default:  // 0,1,2,3,7
        video_mem[i_mem] = char;  // 文字

        if(mem[i_cur] == video_cols - 1) {  // カラム
          if(mem[i_cur + 1] == 24)  // 行
            // スクロール
            tty_scroll_text(page);
          else
            mem[i_cur + 1]++;  // 行
          mem[i_cur] = 0;  // カラム
        }
        else {
          mem[i_cur]++;  // カラム
        }
      }
    }
    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      break;
    default:  // 0,1,2,3,7
      if(page == video_page)  // 現在表示中のページ
        video_cursor_addr = (page << ((video_mode < 2) ? 10 : 11)) + mem[i_cur + 1] * video_cols + mem[i_cur];
    }

    break;

  case 0x0f:  // Get Current Video Mode
    regs[I_AH] = video_cols;
    regs[I_AL] = video_mode;
    regs[I_BH] = video_page;
    break;

  case 0x13:  // Write String
    switch(video_mode) {  // ビデオ モード
    case 4:
    case 5:
    case 6:
      // グラフィック モードでは無効?
      return;
    }

    if((bios_ws_page = regs[I_BH]) >= 8
         || (bios_ws_row = regs[I_DH]) >= 25 || (bios_ws_col = regs[I_DL]) >= video_cols)
      return;

    bios_ws_chars = get_reg2(I_CX);
    bios_ws_off = get_reg2(I_BP);

    bios_ws_upd_cur = true;
    switch(regs[I_AL]) {
    case 0x00:
      bios_ws_upd_cur = false;
      // fall thru
    case 0x01:
      bios_ws_attr = regs[I_BL];
      break;

    case 0x02:
      bios_ws_upd_cur = false;
      // fall thru
    case 0x03:
      bios_ws_attr = -1;
      break;

    default:
      return;
    }

    write_string();
    break;
  }

  function bell_ended_0e() {
    bell_stop();

    return false;
  }

  // ファンクション 13H の処理
  function write_string() {
    while(bios_ws_chars) {
      bios_ws_chars--;

      var char = mem[(es4 + bios_ws_off) & 0xfffff];
      bios_ws_off = (bios_ws_off + 1) & 0xffff;
      switch(char) {
      case 0x07:  // BEL
        if(run_wait)  // 待機中
          continue;
        bell(bell_ended_13);
        return;

      case 0x08:  // BS
        if(bios_ws_col)
          bios_ws_col--;
        break;

      case 0x0a:  // LF
        if(bios_ws_row == 24)
          // スクロール
          tty_scroll_text(bios_ws_page);
        else
          bios_ws_row++;
        break;

      case 0x0d:  // CR
        bios_ws_col = 0;
        break;

      default:
        var attr;
        if(bios_ws_attr < 0) {
          attr = mem[(es4 + bios_ws_off) & 0xfffff];
          bios_ws_off = (bios_ws_off + 1) & 0xffff;
        }
        else {
          attr = bios_ws_attr;
        }

        var i_mem = (bios_ws_page << ((video_mode < 2) ? 11 : 12)) + ((bios_ws_row * video_cols + bios_ws_col) << 1);
        video_mem[i_mem] = char;  // 文字
        video_mem[i_mem + 1] = attr;  // 属性

        if(bios_ws_col == video_cols - 1) {
          if(bios_ws_row == 24)
            // スクロール
            tty_scroll_text(bios_ws_page);
          else
            bios_ws_row++;
          bios_ws_col = 0;
        }
        else {
          bios_ws_col++;
        }
      }

      if(bios_ws_upd_cur) {
        var i_cur = MEM_CURSOR_POS + (bios_ws_page << 1);
        mem[i_cur] = bios_ws_col;
        mem[i_cur + 1] = bios_ws_row;
        if(bios_ws_page == video_page)  // 現在表示中のページ
          video_cursor_addr = (bios_ws_page << ((video_mode < 2) ? 10 : 11)) + bios_ws_row * video_cols + bios_ws_col;
      }
    }

    function bell_ended_13() {
      bell_stop();

      write_string();

      return true;
    }
  }

  // グラフィック文字読み込み
  function read_graphic_char(resol) {
    for(var ext = false; ; ) {
      var ext_base;
      for(var char = 0; char < 128; char++) {
        var i_mem2 = i_mem;
        var i_font = (ext) ? ext_base + (char << 3) : char << 3;
        var i_line;
        for(i_line = 0; i_line < 8; i_line++) {
          var font1;
          if(resol) {
            font1 = video_mem[i_mem2];
          }
          else {
            var p8 = (video_mem[i_mem2] << 8) | video_mem[i_mem2 + 1];
            font1 = 0;
            for(var bits = 0xc000; bits; bits >>= 2) {
              font1 <<= 1;
              if(p8 & bits)
                font1 |= 0x1;
            }
          }
          if(font1 != ((ext) ? mem[(i_font + i_line) & 0xfffff] : video_font_8x8[i_font + i_line]))
            break;
          if(i_line & 0x1)
            i_mem2 -= 8112/* 0x2000-80 */;
          else
            i_mem2 += 0x2000;
        }
        if(i_line == 8)
          return (ext) ? 0x80 | char : char;
      }

      if(ext)
        return 0;
      ext_base = ((mem[0x7e] | (mem[0x7f] << 8)) << 4) + (mem[0x7c] | (mem[0x7d] << 8));
      if(!ext_base)
        return 0;
      ext = true;
    }
  }

  // グラフィック文字書き込み  ビデオ モード 4,5
  function write_graphic_char_4_5(color) {
    var xor = color & 0x80;
    color &= 0x3;
    var i_font = (char & 0x80) ? ((mem[0x7e] | (mem[0x7f] << 8)) << 4) + (mem[0x7c] | (mem[0x7d] << 8))
                                   + ((char & 0x7f) << 3)
                               : char << 3;
    for(var i_line = 0; i_line < 8; i_line++) {
      var font1 = (char & 0x80) ? mem[(i_font + i_line) & 0xfffff] : video_font_8x8[i_font + i_line];
      var p4_1 = (font1 & 0x80) ? color << 6 : 0;
      if(font1 & 0x40)
        p4_1 |= color << 4;
      if(font1 & 0x20)
        p4_1 |= color << 2;
      if(font1 & 0x10)
        p4_1 |= color;
      var p4_2 = (font1 & 0x08) ? color << 6 : 0;
      if(font1 & 0x04)
        p4_2 |= color << 4;
      if(font1 & 0x02)
        p4_2 |= color << 2;
      if(font1 & 0x01)
        p4_2 |= color;
      if(xor) {
        video_mem[i_mem]     ^= p4_1;
        video_mem[i_mem + 1] ^= p4_2;
      }
      else {
        video_mem[i_mem]     = p4_1;
        video_mem[i_mem + 1] = p4_2;
      }

      if(i_line & 0x1)
        i_mem -= 8112/* 0x2000-80 */;
      else
        i_mem += 0x2000;
    }
  }

  // グラフィック文字書き込み  ビデオ モード 6
  function write_graphic_char_6(xor) {
    var i_font = (char & 0x80) ? ((mem[0x7e] | (mem[0x7f] << 8)) << 4) + (mem[0x7c] | (mem[0x7d] << 8))
                                   + ((char & 0x7f) << 3)
                               : char << 3;
    for(var i_line = 0; i_line < 8; i_line++) {
      var font1 = (char & 0x80) ? mem[(i_font + i_line) & 0xfffff] : video_font_8x8[i_font + i_line];
      if(xor)
        video_mem[i_mem] ^= font1;
      else
        video_mem[i_mem] = font1;
      if(i_line & 0x1)
        i_mem -= 8112/* 0x2000-80 */;
      else
        i_mem += 0x2000;
    }
  }

  // TTY スクロール  テキスト モード
  function tty_scroll_text(page) {
    var i_mem = page << ((video_mode < 2) ? 10 : 11);
    video_mem_2.copyWithin(i_mem, i_mem + video_cols, i_mem + 25 * video_cols);
    i_mem += 24 * video_cols;
    video_mem[i_mem << 1] = 0x20;
    video_mem_2.fill(video_mem_2[i_mem], i_mem + 1, i_mem + video_cols);
  }

  // TTY スクロール  グラフィック モード
  function tty_scroll_graphic() {
    video_mem.copyWithin(     0,  0x140/* 80*4 */,        0x1f40/* 25*80*4 */);
    video_mem.copyWithin(0x2000, 0x2140/* 0x2000+80*4 */, 0x3f40/* 0x2000+25*80*4 */);
    video_mem.fill(0, 0x1e00/* 24*80*4 */,        0x1f40/* 25*80*4 */);
    video_mem.fill(0, 0x3e00/* 0x2000+24*80*4 */, 0x3f40/* 0x2000+25*80*4 */);
  }

  // BEL コードによるビープ音発生
  function bell(ended) {
    timer_2_control = 0xb6;
    timer_2_org = timer_now;
    timer_2_count = 1326;
    timer_2_o_lh = timer_2_i_lh = false;
    timer_2_latch = -1;
    timer_2_new_count = 0;
    beep_osc.frequency.value = bell_freq;
    speaker_old_state = io_parallel_b & 0x03;
    io_parallel_b |= 0x03;
    switch(sound_state) {
    case 1:  // ビープ音発生中
      break;
    case 2:  // スピーカー直接出力中
      spk_out.onaudioprocess = null;
      spk_out_gain.disconnect();
      // fall thru
    default:  // ビープ音発生中でない
      beep_osc_gain.connect(aud_ctx.destination);
      sound_state = 1;
    }

    bell_ended = ended;
    setTimeout(bell_timer, 500);
    run_wait = 1;
    op_pushf();
    flag_i = 1;
    run_icnt = 0;

    function bell_timer() {
      run_wait = 0x10001;
    }
  }

  // BEL コードによるビープ終了
  function bell_stop() {
    // BEL コードによるビープ音発生中に,割り込み処理でスピーカーの状態が
    // 変更されている可能性があるので,一応すべての状態を考慮しておく.
    if(speaker_old_state & 0x01) {  // ゲート オン
      if(!(io_parallel_b & 0x01)) {  // ゲート オフだった
        if(timer_2_org < Number.MAX_VALUE)
          timer_2_org += timer_now - timer_2_gate_off;
      }
    }
    else {
      if(io_parallel_b & 0x01) {  // ゲート オンだった
        if(timer_2_org < Number.MAX_VALUE)
          timer_2_gate_off = timer_now;
      }
    }

    io_parallel_b = io_parallel_b & 0xfc | speaker_old_state;

    switch(speaker_old_state) {
    case 0x03:  // ゲート オン,イネーブル
      if(sound_state != 1) {  // ビープ音発生中でない
        if(sound_state == 2) {  // スピーカー直接出力中
          spk_out.onaudioprocess = null;
          spk_out_gain.disconnect();
        }
        if((timer_2_control & 0x06) == 0x06) {  // Mode 3
          if(timer_2_org < Number.MAX_VALUE) {
            // ビープ音発生
            beep_osc_gain.connect(aud_ctx.destination);
            sound_state = 1;
            break;
          }
        }
        sound_state = 0;
      }
      break;

    case 0x01:  // ゲート オン,ディスエーブル
      switch(sound_state) {
      case 1:  // ビープ音発生中
        beep_osc_gain.disconnect();
        break;
      case 2:  // スピーカー直接出力中
        spk_out.onaudioprocess = null;
        spk_out_gain.disconnect();
        break;
      }
      sound_state = 0;
      break;

    default:  // ゲート オフ
      if(sound_state != 2) {  // スピーカー直接出力中でない
        if(sound_state == 1)  // ビープ音発生中
          beep_osc_gain.disconnect();
        sound_state = 0;
      }
    }
    // BEL コードによるビープ終了後,ポート 61H の状態は復元されるが
    // カウンタ設定値は復元されない.
    // 8254 にはカウンタ設定値を読み取る機能が無いため,変更前のカウンタ
    // 設定値が不明なので復元できないものと思われる.

    run_wait = 0;
    op_popf();
  }

  function cursor_pos() {
    var i_cur;
    switch(video_mode) {  // ビデオ モード
    case 0:
    case 1:
      i_cur = MEM_CURSOR_POS + (page << 1);
      if(mem[i_cur + 1] < 25 && mem[i_cur] < 40)
        return (page << 11) + mem[i_cur + 1] * 80 + (mem[i_cur] << 1);
      break;
    case 4:
    case 5:
      if(mem[MEM_CURSOR_POS + 1] < 25 && mem[MEM_CURSOR_POS] < 40)
        return mem[MEM_CURSOR_POS + 1] * 320/* 80*4 */ + (mem[MEM_CURSOR_POS] << 1);
      break;
    case 6:
      if(mem[MEM_CURSOR_POS + 1] < 25 && mem[MEM_CURSOR_POS] < 80)
        return mem[MEM_CURSOR_POS + 1] * 320/* 80*4 */ + mem[MEM_CURSOR_POS];
      break;
    default:  // 2,3,7
      i_cur = MEM_CURSOR_POS + (page << 1);
      if(mem[i_cur + 1] < 25 && mem[i_cur] < 80)
        return (page << 12) + mem[i_cur + 1] * 160 + (mem[i_cur] << 1);
    }
    return -1;
  }
}

// Equipment List
function bios_equip() {
  regs[I_AL] = mem[MEM_EQUIP_LIST_0];
  regs[I_AH] = mem[MEM_EQUIP_LIST_1];
}

// Memory Size
function bios_memory() {
  regs[I_AL] = mem[MEM_MEM_SIZE_0];
  regs[I_AH] = mem[MEM_MEM_SIZE_1];
}

// Low Level Disk Services
function bios_disk() {
  var sts = 0;

  var i_drive;
  var n_cyl;
  var n_hed;
  var n_sec;
  var sec_bytes;
  var cyl;
  var hed;
  var sec;
  var i_image;

  switch(regs[I_AH]) {
  case 0x00:  // Reset Disk System
    break;

  case 0x01:  // Get Disk System Status
    flag_c = (regs[I_AH] = mem[MEM_DISK_STATUS]) ? 1 : 0;
    return;

  case 0x02:  // Read Sectors
  case 0x03:  // Write Sectors
  case 0x04:  // Verify Sectors
    if(check_para(false)) {
      regs[I_AL] = 0;
      break;
    }
    var n = regs[I_AL];
    if(!n) {
      sts = 0x01;
      break;
    }
    var bytes = n * sec_bytes;
    n -= n_sec - sec + 1;
    if(!hed && n_hed == 2)
      n -= n_sec;
    if(n > (n_cyl - cyl - 1) * n_hed * n_sec) {
      regs[I_AL] = 0;
      sts = 0x01;
      break;
    }

    if(regs[I_AH] == 0x04)  // Verify Sectors
      break;

    var i_mem;
    if((i_mem = es4 + get_reg2(I_BX)) >= 0x100000)
      return;
    if(i_mem + bytes > 0x100000) {
      bytes = 0x100000 - i_mem;
      bytes -= bytes % sec_bytes;
    }
    i_image = ((cyl * n_hed + hed) * n_sec + sec - 1) * sec_bytes;

    if(regs[I_AH] == 0x02) {  // Read Sectors
      mem.set(new Uint8Array(mnt_image[i_drive].buffer, i_image, bytes), i_mem);
    }
    else {
      if(elem_prt[i_drive].checked) {  // 書き込み禁止
        sts = 0x03;
        break;
      }
      mnt_image[i_drive].set(new Uint8Array(mem.buffer, i_mem, bytes), i_image);
      mnt_dirty[i_drive] = true;
      elem_nam[i_drive].style.color = "#CC0000";
    }

    break;

  case 0x05:  // Format Track
    if(check_para(true))
      break;
    i_image = ((cyl * n_hed + hed) * n_sec) * sec_bytes;
    mnt_image[i_drive].fill
      (mem[(((mem[0x7a] | (mem[0x7b] << 8)) << 4) + (mem[0x78] | (mem[0x79] << 8)) + 8) & 0xfffff],  // fill byte
       i_image, i_image + n_sec * sec_bytes);
    mnt_dirty[i_drive] = true;
    elem_nam[i_drive].style.color = "#CC0000";
    break;

  case 0x08:  // Get Drive Parameters
    switch(regs[I_DL]) {
    case 0:
    case 1:
      regs_2[I_AX_2] = 0;
      regs[I_BH] = 0;
      regs[I_CH] = 79;
      regs[I_CL] = 18;
      regs[I_DH] = 1;
      regs[I_BL] = 4;
      es4 = FD_PARAM_SEG4;
      set_reg2(I_DI, FD_PARAM_OFF);
      break;

    default:
      regs[I_AL] = regs[I_DH] = 0;
      regs_2[I_BX_2] = regs_2[I_CX_2] = regs_2[I_DI_2] = 0;
      es4 = 0;
      sts = 0x01;
    }
    regs[I_DL] = 2;
    break;

  case 0x17:  // Set DASD Type for Format
    switch(regs[I_DL]) {
    case 0:
    case 1:
      break;
    default:
      sts = 0x01;
    }
    break;

  case 0x18:  // Set Media Type for Format
    switch(regs[I_DL]) {
    case 0:
    case 1:
      var off;
      switch(get_reg2(I_CX)) {
      case 0x2708:
        off = FD_PARAM_8_OFF;
        break;
      case 0x2709:
      case 0x4f09:
        off = FD_PARAM_9_OFF;
        break;
      case 0x270a:
        off = FD_PARAM_10_OFF;
        break;
      case 0x4f12:
        off = FD_PARAM_OFF;
        break;
      default:
        sts = 0x0c;
      }
      if(!sts) {
        es4 = FD_PARAM_SEG4;
        set_reg2(I_DI, off);
      }
      break;
    default:
      sts = 0x01;
    }
    break;

  default:
    sts = 0x01;
  }

  flag_c = (regs[I_AH] = mem[MEM_DISK_STATUS] = sts) ? 1 : 0;

  function check_para(format) {
    switch(i_drive = regs[I_DL]) {
    case 0:
    case 1:
      break;
    default:
      sts = 0x01;
      return true;
    }
    if(mnt_image[i_drive] == undefined) {
      sts = 0x80;
      return true;
    }

    [n_cyl, n_hed, n_sec, sec_bytes] = mnt_spec[i_drive];

    sec = regs[I_CL];
    cyl = ((sec & 0xc0) << 2) | regs[I_CH];
    sec &= 0x3f;
    hed = regs[I_DH];
    if(format) {
      if(cyl >= n_cyl || hed >= n_hed)
        sts = 0x01;
    }
    else {
      if(cyl >= n_cyl || hed >= n_hed || sec == 0 || sec > n_sec)
        sts = 0x01;
    }
    if(sts)
      return true;

    return false;
  }
}

// Serial Port I/O
function bios_serial() {
  switch(regs[I_AH]) {
  case 0x00:  // Serial Port Initialization
    regs_2[I_AX_2] = 0;
    break;

  case 0x01:  // Transmit Character
    regs[I_AH] = 0;
    break;

  case 0x02:  // Receive Character
    regs_2[I_AX_2] = 0;
    break;

  case 0x03:  // Status
    regs_2[I_AX_2] = 0;
    break;
  }
}

// Miscellaneous Services
function bios_misc() {
  switch(regs[I_AH]) {
  case 0x88:  // Get Extended Memory Size
    regs_2[I_AX_2] = 0;
    break;

  case 0x89:  // Switch to Protected Mode
    regs[I_AH] = 0xff;
    break;

  case 0xc0:  // Get System Parameters
    regs[I_AH] = 0;
    es4 = SYS_DESC_SEG4;
    set_reg2(I_BX, SYS_DESC_OFF);
    flag_c = 0;
    break;

  default:
    regs[I_AH] = 0x86;
    flag_c = 1;
  }
}

// Keyboard Services

  keyboard_code_remove = [
    0x0100,0x0e00,0x1a00,0x1b00,0x1c00,0x2700,0x2800,0x2900,0x2b00,0x3300,0x3400,0x3500,
    0x3700,0x4a00,0x4cf0,0x4e00,0x8500,0x8600,0x8700,0x8800,0x8900,0x8a00,0x8b00,0x8c00,0x8d00,
    0x8de0,0x8e00,0x8f00,0x9000,0x9100,0x91e0,0x9200,0x92e0,0x9300,0x93e0,0x9400,0x9500,0x9600,
    0x9700,0x9800,0x9900,0x9b00,0x9d00,0x9f00,0xa000,0xa100,0xa200,0xa300,0xa400,0xa500,0xa600
  ];

function bios_keyboard() {
  var off;
  switch(regs[I_AH]) {
  case 0x00:  // Read Keyboard Input
    if(peek_83_key()) {
      if(run_wait) {  // 待機中
        regs_2[I_AX_2] = 0;
        break;
      }
      run_wait = 2;
      op_pushf();
      flag_i = 1;
      run_icnt = 0;
      keyboard_wait_func = 0x00;
      break;
    }
    keyboard_i_buff_head = (keyboard_i_buff_head + 2) & 0x1f;
    off = KB_BUFF_OFF + keyboard_i_buff_head;
    mem[MEM_KB_BUFF_HEAD_0] = off & 0xff;
    mem[MEM_KB_BUFF_HEAD_1] = (off & 0xff00) >> 8;
    break;

  case 0x01:  // Return Keyboard Status
    flag_z = peek_83_key();
    flag_i = 1;
    run_icnt = 0;
    break;

  case 0x02:  // Return Shift Flag Status
    regs[I_AL] = mem[MEM_KB_STATUS_0];
    break;

  case 0x03:  // Set Typematic Rate and Delay
    if(regs[I_AL] == 5) {
      keyboard_rep_delay = ((regs[I_BH] & 0x03) + 1) * 250;
      keyboard_rep_rate = keyboard_rep_rate_tbl[regs[I_BL] & 0x1f];
    }
    break;

  case 0x05:  // Store Key Data
    var new_tail = (keyboard_i_buff_tail + 2) & 0x1f;
    if(new_tail == keyboard_i_buff_head) {  // バッファ一杯
      regs[I_AL] = 1;
      break;
    }
    regs[I_AL] = 0;
    keyboard_buff[keyboard_i_buff_tail + 1] = regs[I_CH];
    keyboard_buff[keyboard_i_buff_tail]     = regs[I_CL];
    keyboard_i_buff_tail = new_tail;
    off = KB_BUFF_OFF + keyboard_i_buff_tail;
    mem[MEM_KB_BUFF_TAIL_0] = off & 0xff;
    mem[MEM_KB_BUFF_TAIL_1] = (off & 0xff00) >> 8;
    break;

  case 0x10:  // Read Extended Keyboard Input
    if(peek_101_key()) {
      if(run_wait) {  // 待機中
        regs_2[I_AX_2] = 0;
        break;
      }
      run_wait = 2;
      op_pushf();
      flag_i = 1;
      run_icnt = 0;
      keyboard_wait_func = 0x10;
      break;
    }
    keyboard_i_buff_head = (keyboard_i_buff_head + 2) & 0x1f;
    off = KB_BUFF_OFF + keyboard_i_buff_head;
    mem[MEM_KB_BUFF_HEAD_0] = off & 0xff;
    mem[MEM_KB_BUFF_HEAD_1] = (off & 0xff00) >> 8;
    break;

  case 0x11:  // Return Extended Keyboard Status
    flag_z = peek_101_key();
    flag_i = 1;
    run_icnt = 0;
    break;

  case 0x12:  // Return Extended Shift Flags Status
    regs[I_AL] = mem[MEM_KB_STATUS_0];
    regs[I_AH] = mem[MEM_KB_STATUS_1] & 0x73 | mem[MEM_KB_FLAGS] & 0x0c
                   | ((mem[MEM_KB_STATUS_1] & 0x04) << 5);
    break;
  }

  function peek_83_key() {
    for(; ; ) {
      if(keyboard_i_buff_head == keyboard_i_buff_tail)  // バッファ空
        return 1;
      var scan = keyboard_buff[keyboard_i_buff_head + 1];
      var char = keyboard_buff[keyboard_i_buff_head];
      if(keyboard_code_remove.indexOf((scan << 8) | char) < 0)
        break;
      keyboard_i_buff_head = (keyboard_i_buff_head + 2) & 0x1f;
      var off = KB_BUFF_OFF + keyboard_i_buff_head;
      mem[MEM_KB_BUFF_HEAD_0] = off & 0xff;
      mem[MEM_KB_BUFF_HEAD_1] = (off & 0xff00) >> 8;
    }
    if(scan == 0xe0) {
      switch(char) {
      case 0x0a:
      case 0x0d:
        scan = 0x1c;
        break;
      case 0x2f:
        scan = 0x35;
        break;
      }
    }
    else {
      if(char == 0xe0)
        char = 0x00;
    }
    regs[I_AH] = scan;
    regs[I_AL] = char;
    return 0;
  }

  function peek_101_key() {
    if(keyboard_i_buff_head == keyboard_i_buff_tail)  // バッファ空
      return 1;
    var scan;
    regs[I_AH] = scan = keyboard_buff[keyboard_i_buff_head + 1];
    var char = keyboard_buff[keyboard_i_buff_head];
    regs[I_AL] = (scan == 0x4c && char == 0xf0) ? 0x00 : char;
    return 0;
  }
}

// Printer Services
function bios_printer() {
  switch(regs[I_AH]) {
  case 0x00:  // Print Character to Printer
  case 0x01:  // Initialize Printer
  case 0x02:  // Check Printer Status
    regs[I_AH] = 0;
    break;
  }
}

// Bootstrap Loader Service
function bios_boot() {
  boot();
}

// Real Time Clock Services
function bios_clock() {
  var now;
  switch(regs[I_AH]) {
  case 0x00:  // Get System Timer Count
    regs[I_DL] = mem[MEM_TIMER_COUNTER_0];
    regs[I_DH] = mem[MEM_TIMER_COUNTER_1];
    regs[I_CL] = mem[MEM_TIMER_COUNTER_2];
    regs[I_CH] = mem[MEM_TIMER_COUNTER_3];
    regs[I_AL] = mem[MEM_TIMER_OVERFLOW];
    mem[MEM_TIMER_OVERFLOW] = 0;
    break;

  case 0x01:  // Set System Timer Count
    mem[MEM_TIMER_COUNTER_0] = regs[I_DL];
    mem[MEM_TIMER_COUNTER_1] = regs[I_DH];
    mem[MEM_TIMER_COUNTER_2] = regs[I_CL];
    mem[MEM_TIMER_COUNTER_3] = regs[I_CH];
    mem[MEM_TIMER_OVERFLOW] = 0;
    break;

  case 0x02:  // Get RTC Time
    now = new Date();
    regs[I_CH] = bcd(now.getHours());
    regs[I_CL] = bcd(now.getMinutes());
    regs[I_DH] = bcd(now.getSeconds());
    regs[I_DL] = 0;
    flag_c = 0;
    break;

  case 0x04:  // Get RTC Date
    now = new Date();
    var year = now.getFullYear();
    var year2;
    regs[I_CL] = bcd(year2 = year % 100);
    regs[I_CH] = bcd((year - year2) / 100);
    regs[I_DH] = bcd(now.getMonth() + 1);
    regs[I_DL] = bcd(now.getDate());
    flag_c = 0;
    break;

  case 0x03:  // Set RTC Time
  case 0x05:  // Set RTC Date
    break;

  case 0x06:  // Set RTC Alarm
    flag_c = 0;
    break;

  case 0x07:  // Reset RTC Alarm
    break;
  }

  function bcd(val) {
    var d1 = val % 10;
    return (((val - d1) / 10) << 4) | d1;
  }
}

function timer_0_update() {
  if(timer_0_control & 0x04) {  // Mode 2,3
    // CGA の CRTC のクロックとタイマーのクロックは,同じ発振器の出力を元にしているため
    // 完全に同期する.
    // そのことを前提としているプログラムがあるので,浮動小数点数の誤差の累積によって
    // 両者の同期が次第にずれてゆくことが無いように注意する必要がある.
    if(timer_0_new_count) {  // タイマー値が変わった
      timer_0_org = timer_0_next;
      timer_0_osc_cnt = timer_0_count = timer_0_new_count;
      timer_0_new_count = 0;
    }
    else {
      timer_0_osc_cnt += timer_0_count;
    }
    timer_0_next = timer_0_org + timer_0_osc_cnt * OSC_INT;
  }
  else {
    timer_0_next = Number.MAX_VALUE;
  }
}

// システム タイマー割り込み
// INT 08H 割り込みをフックするプログラムのため,割り込みをエミュレートする.
function interval_int() {
  pic_m_isr |= 0x01;
  pic_m_irr &= 0xfe;
  if(run_hwint_ss < 0) {
    run_hwint_ss = ss4;
    run_hwint_sp = regs_2[I_SP_2];
  }
  run_halt = false;

  int(0x08);
}

// システム タイマー割り込み デフォルト処理
function interval_int_def() {
  var ticks;
  if(mem[MEM_TIMER_COUNTER_3]) {
    ticks = 0;
    mem[MEM_TIMER_OVERFLOW] = 1;
  }
  else {
    if((ticks = (mem[MEM_TIMER_COUNTER_0] | (mem[MEM_TIMER_COUNTER_1] << 8) | (mem[MEM_TIMER_COUNTER_2] << 16)) + 1)
         >= 1573040) {
      ticks = 0;
      mem[MEM_TIMER_OVERFLOW] = 1;
    }
  }
  mem[MEM_TIMER_COUNTER_0] = ticks & 0xff;
  mem[MEM_TIMER_COUNTER_1] = (ticks & 0xff00) >> 8;
  mem[MEM_TIMER_COUNTER_2] = (ticks & 0xff0000) >> 16;
  mem[MEM_TIMER_COUNTER_3] = 0;

  if(mem[MEM_MOTOR_OFF])
    mem[MEM_MOTOR_OFF]--;

  // INT 1CH
  flag_i = 1;
  run_icnt = 0;
  // レジスタ退避
  save_regs[i_save_regs]     = ds4 >> 4;
  save_regs[i_save_regs + 1] = es4 >> 4;
  [save_regs[i_save_regs + 2],
   save_regs[i_save_regs + 3],
   save_regs[i_save_regs + 4],
   save_regs[i_save_regs + 5],
   ,
   save_regs[i_save_regs + 6],
   save_regs[i_save_regs + 7],
   save_regs[i_save_regs + 8]
  ] = regs_2;
  i_save_regs += 9;

  ip++;
  int(0x1c);
}

// INT 1CH 終了
function interval_int_def_2() {
  pic_m_isr &= 0xfe;  // EOI

  // レジスタ復旧
  i_save_regs -= 9;
  ds4 = save_regs[i_save_regs]     << 4;
  es4 = save_regs[i_save_regs + 1] << 4;
  regs_2[I_AX_2] = save_regs[i_save_regs + 2];
  regs_2[I_CX_2] = save_regs[i_save_regs + 3];
  regs_2[I_DX_2] = save_regs[i_save_regs + 4];
  regs_2[I_BX_2] = save_regs[i_save_regs + 5];
  regs_2[I_BP_2] = save_regs[i_save_regs + 6];
  regs_2[I_SI_2] = save_regs[i_save_regs + 7];
  regs_2[I_DI_2] = save_regs[i_save_regs + 8];
}

// スピーカー直接出力 PCM データ作成
function spk_out_dummy_proc(e) {
  e.outputBuffer.getChannelData(0).fill(0);
  if(timer.now() >= spk_out_time + spk_out_int)
    spk_out.onaudioprocess = spk_out_proc;
}

function spk_out_proc(e) {
  var pcm = e.outputBuffer.getChannelData(0);
  var i_pcm = 0;
  var next = spk_out_time + spk_out_int;
  if(spk_out_i_get == spk_out_i_put) {  // バッファ空
    if(timer.now() >= spk_out_get_prev + spk_out_buff[(spk_out_i_get - 1) & 0x7ff] + 100) {
      spk_out.onaudioprocess = null;
      spk_out_gain.disconnect();
      sound_state = 0;
      return;
    }
  }
  else {
    while(spk_out_i_get != spk_out_i_put) {
      var time = spk_out_get_prev + spk_out_buff[spk_out_i_get];
      if(time > next)
        break;
      var i = Math.floor((time - spk_out_time) * spk_out_rate);
      for(; i_pcm < i; i_pcm++)
        pcm[i_pcm] = spk_out_val;
      spk_out_i_get = (spk_out_i_get + 1) & 0x7ff;
      spk_out_get_prev = time;
      spk_out_val = 1 - spk_out_val;
    }
  }
  for(; i_pcm < 512; i_pcm++)
    pcm[i_pcm] = spk_out_val;
  spk_out_time = next;
}

// ---- ビデオ エミュレーション ----

  // 8x8 フォント
  video_font_8x8 = new Uint8Array([
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x7e,0x81,0xa5,0x81,0xbd,0x99,0x81,0x7e,
    0x7e,0xff,0xdb,0xff,0xc3,0xe7,0xff,0x7e,
    0x6c,0xfe,0xfe,0xfe,0x7c,0x38,0x10,0x00,
    0x10,0x38,0x7c,0xfe,0x7c,0x38,0x10,0x00,
    0x38,0x7c,0x38,0xfe,0xfe,0x7c,0x38,0x7c,
    0x10,0x10,0x38,0x7c,0xfe,0x7c,0x38,0x7c,
    0x00,0x00,0x18,0x3c,0x3c,0x18,0x00,0x00,
    0xff,0xff,0xe7,0xc3,0xc3,0xe7,0xff,0xff,
    0x00,0x3c,0x66,0x42,0x42,0x66,0x3c,0x00,
    0xff,0xc3,0x99,0xbd,0xbd,0x99,0xc3,0xff,
    0x0f,0x07,0x0f,0x7d,0xcc,0xcc,0xcc,0x78,
    0x3c,0x66,0x66,0x66,0x3c,0x18,0x7e,0x18,
    0x3f,0x33,0x3f,0x30,0x30,0x70,0xf0,0xe0,
    0x7f,0x63,0x7f,0x63,0x63,0x67,0xe6,0xc0,
    0x99,0x5a,0x3c,0xe7,0xe7,0x3c,0x5a,0x99,
    0x80,0xe0,0xf8,0xfe,0xf8,0xe0,0x80,0x00,
    0x02,0x0e,0x3e,0xfe,0x3e,0x0e,0x02,0x00,
    0x18,0x3c,0x7e,0x18,0x18,0x7e,0x3c,0x18,
    0x66,0x66,0x66,0x66,0x66,0x00,0x66,0x00,
    0x7f,0xdb,0xdb,0x7b,0x1b,0x1b,0x1b,0x00,
    0x3e,0x63,0x38,0x6c,0x6c,0x38,0xcc,0x78,
    0x00,0x00,0x00,0x00,0x7e,0x7e,0x7e,0x00,
    0x18,0x3c,0x7e,0x18,0x7e,0x3c,0x18,0xff,
    0x18,0x3c,0x7e,0x18,0x18,0x18,0x18,0x00,
    0x18,0x18,0x18,0x18,0x7e,0x3c,0x18,0x00,
    0x00,0x18,0x0c,0xfe,0x0c,0x18,0x00,0x00,
    0x00,0x30,0x60,0xfe,0x60,0x30,0x00,0x00,
    0x00,0x00,0xc0,0xc0,0xc0,0xfe,0x00,0x00,
    0x00,0x24,0x66,0xff,0x66,0x24,0x00,0x00,
    0x00,0x18,0x3c,0x7e,0xff,0xff,0x00,0x00,
    0x00,0xff,0xff,0x7e,0x3c,0x18,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x30,0x78,0x78,0x30,0x30,0x00,0x30,0x00,
    0x6c,0x6c,0x6c,0x00,0x00,0x00,0x00,0x00,
    0x6c,0x6c,0xfe,0x6c,0xfe,0x6c,0x6c,0x00,
    0x30,0x7c,0xc0,0x78,0x0c,0xf8,0x30,0x00,
    0x00,0xc6,0xcc,0x18,0x30,0x66,0xc6,0x00,
    0x38,0x6c,0x38,0x76,0xdc,0xcc,0x76,0x00,
    0x60,0x60,0xc0,0x00,0x00,0x00,0x00,0x00,
    0x18,0x30,0x60,0x60,0x60,0x30,0x18,0x00,
    0x60,0x30,0x18,0x18,0x18,0x30,0x60,0x00,
    0x00,0x66,0x3c,0xff,0x3c,0x66,0x00,0x00,
    0x00,0x30,0x30,0xfc,0x30,0x30,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x60,
    0x00,0x00,0x00,0xfc,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,
    0x06,0x0c,0x18,0x30,0x60,0xc0,0x80,0x00,
    0x7c,0xc6,0xce,0xde,0xf6,0xe6,0x7c,0x00,
    0x30,0x70,0x30,0x30,0x30,0x30,0xfc,0x00,
    0x78,0xcc,0x0c,0x38,0x60,0xcc,0xfc,0x00,
    0x78,0xcc,0x0c,0x38,0x0c,0xcc,0x78,0x00,
    0x1c,0x3c,0x6c,0xcc,0xfe,0x0c,0x1e,0x00,
    0xfc,0xc0,0xf8,0x0c,0x0c,0xcc,0x78,0x00,
    0x38,0x60,0xc0,0xf8,0xcc,0xcc,0x78,0x00,
    0xfc,0xcc,0x0c,0x18,0x30,0x30,0x30,0x00,
    0x78,0xcc,0xcc,0x78,0xcc,0xcc,0x78,0x00,
    0x78,0xcc,0xcc,0x7c,0x0c,0x18,0x70,0x00,
    0x00,0x30,0x30,0x00,0x00,0x30,0x30,0x00,
    0x00,0x30,0x30,0x00,0x00,0x30,0x30,0x60,
    0x18,0x30,0x60,0xc0,0x60,0x30,0x18,0x00,
    0x00,0x00,0xfc,0x00,0x00,0xfc,0x00,0x00,
    0x60,0x30,0x18,0x0c,0x18,0x30,0x60,0x00,
    0x78,0xcc,0x0c,0x18,0x30,0x00,0x30,0x00,
    0x7c,0xc6,0xde,0xde,0xde,0xc0,0x78,0x00,
    0x30,0x78,0xcc,0xcc,0xfc,0xcc,0xcc,0x00,
    0xfc,0x66,0x66,0x7c,0x66,0x66,0xfc,0x00,
    0x3c,0x66,0xc0,0xc0,0xc0,0x66,0x3c,0x00,
    0xf8,0x6c,0x66,0x66,0x66,0x6c,0xf8,0x00,
    0xfe,0x62,0x68,0x78,0x68,0x62,0xfe,0x00,
    0xfe,0x62,0x68,0x78,0x68,0x60,0xf0,0x00,
    0x3c,0x66,0xc0,0xc0,0xce,0x66,0x3e,0x00,
    0xcc,0xcc,0xcc,0xfc,0xcc,0xcc,0xcc,0x00,
    0x78,0x30,0x30,0x30,0x30,0x30,0x78,0x00,
    0x1e,0x0c,0x0c,0x0c,0xcc,0xcc,0x78,0x00,
    0xe6,0x66,0x6c,0x78,0x6c,0x66,0xe6,0x00,
    0xf0,0x60,0x60,0x60,0x62,0x66,0xfe,0x00,
    0xc6,0xee,0xfe,0xfe,0xd6,0xc6,0xc6,0x00,
    0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00,
    0x38,0x6c,0xc6,0xc6,0xc6,0x6c,0x38,0x00,
    0xfc,0x66,0x66,0x7c,0x60,0x60,0xf0,0x00,
    0x78,0xcc,0xcc,0xcc,0xdc,0x78,0x1c,0x00,
    0xfc,0x66,0x66,0x7c,0x6c,0x66,0xe6,0x00,
    0x78,0xcc,0xe0,0x70,0x1c,0xcc,0x78,0x00,
    0xfc,0xb4,0x30,0x30,0x30,0x30,0x78,0x00,
    0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xfc,0x00,
    0xcc,0xcc,0xcc,0xcc,0xcc,0x78,0x30,0x00,
    0xc6,0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x00,
    0xc6,0xc6,0x6c,0x38,0x38,0x6c,0xc6,0x00,
    0xcc,0xcc,0xcc,0x78,0x30,0x30,0x78,0x00,
    0xfe,0xc6,0x8c,0x18,0x32,0x66,0xfe,0x00,
    0x78,0x60,0x60,0x60,0x60,0x60,0x78,0x00,
    0xc0,0x60,0x30,0x18,0x0c,0x06,0x02,0x00,
    0x78,0x18,0x18,0x18,0x18,0x18,0x78,0x00,
    0x10,0x38,0x6c,0xc6,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,
    0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x78,0x0c,0x7c,0xcc,0x76,0x00,
    0xe0,0x60,0x60,0x7c,0x66,0x66,0xdc,0x00,
    0x00,0x00,0x78,0xcc,0xc0,0xcc,0x78,0x00,
    0x1c,0x0c,0x0c,0x7c,0xcc,0xcc,0x76,0x00,
    0x00,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00,
    0x38,0x6c,0x60,0xf0,0x60,0x60,0xf0,0x00,
    0x00,0x00,0x76,0xcc,0xcc,0x7c,0x0c,0xf8,
    0xe0,0x60,0x6c,0x76,0x66,0x66,0xe6,0x00,
    0x30,0x00,0x70,0x30,0x30,0x30,0x78,0x00,
    0x0c,0x00,0x0c,0x0c,0x0c,0xcc,0xcc,0x78,
    0xe0,0x60,0x66,0x6c,0x78,0x6c,0xe6,0x00,
    0x70,0x30,0x30,0x30,0x30,0x30,0x78,0x00,
    0x00,0x00,0xcc,0xfe,0xfe,0xd6,0xc6,0x00,
    0x00,0x00,0xf8,0xcc,0xcc,0xcc,0xcc,0x00,
    0x00,0x00,0x78,0xcc,0xcc,0xcc,0x78,0x00,
    0x00,0x00,0xdc,0x66,0x66,0x7c,0x60,0xf0,
    0x00,0x00,0x76,0xcc,0xcc,0x7c,0x0c,0x1e,
    0x00,0x00,0xdc,0x76,0x66,0x60,0xf0,0x00,
    0x00,0x00,0x7c,0xc0,0x78,0x0c,0xf8,0x00,
    0x10,0x30,0x7c,0x30,0x30,0x34,0x18,0x00,
    0x00,0x00,0xcc,0xcc,0xcc,0xcc,0x76,0x00,
    0x00,0x00,0xcc,0xcc,0xcc,0x78,0x30,0x00,
    0x00,0x00,0xc6,0xd6,0xfe,0xfe,0x6c,0x00,
    0x00,0x00,0xc6,0x6c,0x38,0x6c,0xc6,0x00,
    0x00,0x00,0xcc,0xcc,0xcc,0x7c,0x0c,0xf8,
    0x00,0x00,0xfc,0x98,0x30,0x64,0xfc,0x00,
    0x1c,0x30,0x30,0xe0,0x30,0x30,0x1c,0x00,
    0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00,
    0xe0,0x30,0x30,0x1c,0x30,0x30,0xe0,0x00,
    0x76,0xdc,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x10,0x38,0x6c,0xc6,0xc6,0xfe,0x00,
    0x78,0xcc,0xc0,0xcc,0x78,0x18,0x0c,0x78,
    0x00,0xcc,0x00,0xcc,0xcc,0xcc,0x7e,0x00,
    0x1c,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00,
    0x7e,0xc3,0x3c,0x06,0x3e,0x66,0x3f,0x00,
    0xcc,0x00,0x78,0x0c,0x7c,0xcc,0x7e,0x00,
    0xe0,0x00,0x78,0x0c,0x7c,0xcc,0x7e,0x00,
    0x30,0x30,0x78,0x0c,0x7c,0xcc,0x7e,0x00,
    0x00,0x00,0x78,0xc0,0xc0,0x78,0x0c,0x38,
    0x7e,0xc3,0x3c,0x66,0x7e,0x60,0x3c,0x00,
    0xcc,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00,
    0xe0,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00,
    0xcc,0x00,0x70,0x30,0x30,0x30,0x78,0x00,
    0x7c,0xc6,0x38,0x18,0x18,0x18,0x3c,0x00,
    0xe0,0x00,0x70,0x30,0x30,0x30,0x78,0x00,
    0xc6,0x38,0x6c,0xc6,0xfe,0xc6,0xc6,0x00,
    0x30,0x30,0x00,0x78,0xcc,0xfc,0xcc,0x00,
    0x1c,0x00,0xfc,0x60,0x78,0x60,0xfc,0x00,
    0x00,0x00,0x7f,0x0c,0x7f,0xcc,0x7f,0x00,
    0x3e,0x6c,0xcc,0xfe,0xcc,0xcc,0xce,0x00,
    0x78,0xcc,0x00,0x78,0xcc,0xcc,0x78,0x00,
    0x00,0xcc,0x00,0x78,0xcc,0xcc,0x78,0x00,
    0x00,0xe0,0x00,0x78,0xcc,0xcc,0x78,0x00,
    0x78,0xcc,0x00,0xcc,0xcc,0xcc,0x7e,0x00,
    0x00,0xe0,0x00,0xcc,0xcc,0xcc,0x7e,0x00,
    0x00,0xcc,0x00,0xcc,0xcc,0x7c,0x0c,0xf8,
    0xc3,0x18,0x3c,0x66,0x66,0x3c,0x18,0x00,
    0xcc,0x00,0xcc,0xcc,0xcc,0xcc,0x78,0x00,
    0x18,0x18,0x7e,0xc0,0xc0,0x7e,0x18,0x18,
    0x38,0x6c,0x64,0xf0,0x60,0xe6,0xfc,0x00,
    0xcc,0xcc,0x78,0xfc,0x30,0xfc,0x30,0x30,
    0xf8,0xcc,0xcc,0xfa,0xc6,0xcf,0xc6,0xc7,
    0x0e,0x1b,0x18,0x3c,0x18,0x18,0xd8,0x70,
    0x1c,0x00,0x78,0x0c,0x7c,0xcc,0x7e,0x00,
    0x38,0x00,0x70,0x30,0x30,0x30,0x78,0x00,
    0x00,0x1c,0x00,0x78,0xcc,0xcc,0x78,0x00,
    0x00,0x1c,0x00,0xcc,0xcc,0xcc,0x7e,0x00,
    0x00,0xf8,0x00,0xf8,0xcc,0xcc,0xcc,0x00,
    0xfc,0x00,0xcc,0xec,0xfc,0xdc,0xcc,0x00,
    0x3c,0x6c,0x6c,0x3e,0x00,0x7e,0x00,0x00,
    0x38,0x6c,0x6c,0x38,0x00,0x7c,0x00,0x00,
    0x30,0x00,0x30,0x60,0xc0,0xcc,0x78,0x00,
    0x00,0x00,0x00,0xfc,0xc0,0xc0,0x00,0x00,
    0x00,0x00,0x00,0xfc,0x0c,0x0c,0x00,0x00,
    0xc3,0xc6,0xcc,0xde,0x33,0x66,0xcc,0x0f,
    0xc3,0xc6,0xcc,0xdb,0x37,0x6f,0xcf,0x03,
    0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x00,
    0x00,0x33,0x66,0xcc,0x66,0x33,0x00,0x00,
    0x00,0xcc,0x66,0x33,0x66,0xcc,0x00,0x00,
    0x22,0x88,0x22,0x88,0x22,0x88,0x22,0x88,
    0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa,
    0xdb,0x77,0xdb,0xee,0xdb,0x77,0xdb,0xee,
    0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
    0x18,0x18,0x18,0x18,0xf8,0x18,0x18,0x18,
    0x18,0x18,0xf8,0x18,0xf8,0x18,0x18,0x18,
    0x36,0x36,0x36,0x36,0xf6,0x36,0x36,0x36,
    0x00,0x00,0x00,0x00,0xfe,0x36,0x36,0x36,
    0x00,0x00,0xf8,0x18,0xf8,0x18,0x18,0x18,
    0x36,0x36,0xf6,0x06,0xf6,0x36,0x36,0x36,
    0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
    0x00,0x00,0xfe,0x06,0xf6,0x36,0x36,0x36,
    0x36,0x36,0xf6,0x06,0xfe,0x00,0x00,0x00,
    0x36,0x36,0x36,0x36,0xfe,0x00,0x00,0x00,
    0x18,0x18,0xf8,0x18,0xf8,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0xf8,0x18,0x18,0x18,
    0x18,0x18,0x18,0x18,0x1f,0x00,0x00,0x00,
    0x18,0x18,0x18,0x18,0xff,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0xff,0x18,0x18,0x18,
    0x18,0x18,0x18,0x18,0x1f,0x18,0x18,0x18,
    0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,
    0x18,0x18,0x18,0x18,0xff,0x18,0x18,0x18,
    0x18,0x18,0x1f,0x18,0x1f,0x18,0x18,0x18,
    0x36,0x36,0x36,0x36,0x37,0x36,0x36,0x36,
    0x36,0x36,0x37,0x30,0x3f,0x00,0x00,0x00,
    0x00,0x00,0x3f,0x30,0x37,0x36,0x36,0x36,
    0x36,0x36,0xf7,0x00,0xff,0x00,0x00,0x00,
    0x00,0x00,0xff,0x00,0xf7,0x36,0x36,0x36,
    0x36,0x36,0x37,0x30,0x37,0x36,0x36,0x36,
    0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,
    0x36,0x36,0xf7,0x00,0xf7,0x36,0x36,0x36,
    0x18,0x18,0xff,0x00,0xff,0x00,0x00,0x00,
    0x36,0x36,0x36,0x36,0xff,0x00,0x00,0x00,
    0x00,0x00,0xff,0x00,0xff,0x18,0x18,0x18,
    0x00,0x00,0x00,0x00,0xff,0x36,0x36,0x36,
    0x36,0x36,0x36,0x36,0x3f,0x00,0x00,0x00,
    0x18,0x18,0x1f,0x18,0x1f,0x00,0x00,0x00,
    0x00,0x00,0x1f,0x18,0x1f,0x18,0x18,0x18,
    0x00,0x00,0x00,0x00,0x3f,0x36,0x36,0x36,
    0x36,0x36,0x36,0x36,0xff,0x36,0x36,0x36,
    0x18,0x18,0xff,0x18,0xff,0x18,0x18,0x18,
    0x18,0x18,0x18,0x18,0xf8,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x1f,0x18,0x18,0x18,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,
    0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,
    0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
    0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,
    0x00,0x00,0x76,0xdc,0xc8,0xdc,0x76,0x00,
    0x00,0x78,0xcc,0xf8,0xcc,0xf8,0xc0,0xc0,
    0x00,0xfc,0xcc,0xc0,0xc0,0xc0,0xc0,0x00,
    0x00,0xfe,0x6c,0x6c,0x6c,0x6c,0x6c,0x00,
    0xfc,0xcc,0x60,0x30,0x60,0xcc,0xfc,0x00,
    0x00,0x00,0x7e,0xd8,0xd8,0xd8,0x70,0x00,
    0x00,0x66,0x66,0x66,0x66,0x7c,0x60,0xc0,
    0x00,0x76,0xdc,0x18,0x18,0x18,0x18,0x00,
    0xfc,0x30,0x78,0xcc,0xcc,0x78,0x30,0xfc,
    0x38,0x6c,0xc6,0xfe,0xc6,0x6c,0x38,0x00,
    0x38,0x6c,0xc6,0xc6,0x6c,0x6c,0xee,0x00,
    0x1c,0x30,0x18,0x7c,0xcc,0xcc,0x78,0x00,
    0x00,0x00,0x7e,0xdb,0xdb,0x7e,0x00,0x00,
    0x06,0x0c,0x7e,0xdb,0xdb,0x7e,0x60,0xc0,
    0x38,0x60,0xc0,0xf8,0xc0,0x60,0x38,0x00,
    0x78,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0x00,
    0x00,0xfc,0x00,0xfc,0x00,0xfc,0x00,0x00,
    0x30,0x30,0xfc,0x30,0x30,0x00,0xfc,0x00,
    0x60,0x30,0x18,0x30,0x60,0x00,0xfc,0x00,
    0x18,0x30,0x60,0x30,0x18,0x00,0xfc,0x00,
    0x0e,0x1b,0x1b,0x18,0x18,0x18,0x18,0x18,
    0x18,0x18,0x18,0x18,0x18,0xd8,0xd8,0x70,
    0x30,0x30,0x00,0xfc,0x00,0x30,0x30,0x00,
    0x00,0x76,0xdc,0x00,0x76,0xdc,0x00,0x00,
    0x38,0x6c,0x6c,0x38,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,
    0x0f,0x0c,0x0c,0x0c,0xec,0x6c,0x3c,0x1c,
    0x78,0x6c,0x6c,0x6c,0x6c,0x00,0x00,0x00,
    0x70,0x18,0x30,0x60,0x78,0x00,0x00,0x00,
    0x00,0x00,0x3c,0x3c,0x3c,0x3c,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
  ]);

  // 9x14 フォント
  video_font_9x14 = new Uint16Array([
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x0fc,0x102,0x14a,0x102,0x102,0x17a,0x132,0x102,0x0fc,0x000,0x000,0x000,
    0x000,0x000,0x0fc,0x1fe,0x1b6,0x1fe,0x1fe,0x186,0x1ce,0x1fe,0x0fc,0x000,0x000,0x000,
    0x000,0x000,0x000,0x06c,0x0fe,0x0fe,0x0fe,0x0fe,0x07c,0x038,0x010,0x000,0x000,0x000,
    0x000,0x000,0x000,0x010,0x038,0x07c,0x0fe,0x07c,0x038,0x010,0x000,0x000,0x000,0x000,
    0x000,0x000,0x030,0x078,0x078,0x1ce,0x1ce,0x1ce,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x000,0x030,0x078,0x0fc,0x1fe,0x1fe,0x0fc,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x030,0x078,0x078,0x030,0x000,0x000,0x000,0x000,0x000,
    0x1fe,0x1fe,0x1fe,0x1fe,0x1fe,0x1ce,0x186,0x186,0x1ce,0x1fe,0x1fe,0x1fe,0x1fe,0x1fe,
    0x000,0x000,0x000,0x000,0x078,0x0cc,0x084,0x084,0x0cc,0x078,0x000,0x000,0x000,0x000,
    0x1fe,0x1fe,0x1fe,0x1fe,0x186,0x132,0x17a,0x17a,0x132,0x186,0x1fe,0x1fe,0x1fe,0x1fe,
    0x000,0x000,0x01e,0x00e,0x01a,0x032,0x078,0x0cc,0x0cc,0x0cc,0x078,0x000,0x000,0x000,
    0x000,0x000,0x078,0x0cc,0x0cc,0x0cc,0x078,0x030,0x0fc,0x030,0x030,0x000,0x000,0x000,
    0x000,0x000,0x07e,0x066,0x07e,0x060,0x060,0x060,0x0e0,0x1e0,0x1c0,0x000,0x000,0x000,
    0x000,0x000,0x0fe,0x0c6,0x0fe,0x0c6,0x0c6,0x0c6,0x0ce,0x1ce,0x1cc,0x180,0x000,0x000,
    0x000,0x000,0x030,0x030,0x1b6,0x078,0x1ce,0x078,0x1b6,0x030,0x030,0x000,0x000,0x000,
    0x000,0x000,0x080,0x0c0,0x0e0,0x0f8,0x0fe,0x0f8,0x0e0,0x0c0,0x080,0x000,0x000,0x000,
    0x000,0x000,0x002,0x006,0x00e,0x03e,0x0fe,0x03e,0x00e,0x006,0x002,0x000,0x000,0x000,
    0x000,0x000,0x030,0x078,0x0fc,0x030,0x030,0x030,0x0fc,0x078,0x030,0x000,0x000,0x000,
    0x000,0x000,0x066,0x066,0x066,0x066,0x066,0x066,0x000,0x066,0x066,0x000,0x000,0x000,
    0x000,0x000,0x0fe,0x1b6,0x1b6,0x1b6,0x0f6,0x036,0x036,0x036,0x036,0x000,0x000,0x000,
    0x000,0x07c,0x0c6,0x060,0x038,0x06c,0x0c6,0x0c6,0x06c,0x038,0x00c,0x0c6,0x07c,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x0fe,0x0fe,0x0fe,0x000,0x000,0x000,
    0x000,0x000,0x030,0x078,0x0fc,0x030,0x030,0x030,0x0fc,0x078,0x030,0x0fc,0x000,0x000,
    0x000,0x000,0x030,0x078,0x0fc,0x030,0x030,0x030,0x030,0x030,0x030,0x000,0x000,0x000,
    0x000,0x000,0x030,0x030,0x030,0x030,0x030,0x030,0x0fc,0x078,0x030,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x018,0x00c,0x0fe,0x00c,0x018,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x030,0x060,0x0fe,0x060,0x030,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0c0,0x0c0,0x0c0,0x0fe,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x048,0x0cc,0x1fe,0x0cc,0x048,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x010,0x038,0x038,0x07c,0x07c,0x0fe,0x0fe,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x0fe,0x0fe,0x07c,0x07c,0x038,0x038,0x010,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x030,0x078,0x078,0x078,0x030,0x030,0x000,0x030,0x030,0x000,0x000,0x000,
    0x000,0x0c6,0x0c6,0x0c6,0x044,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x06c,0x06c,0x0fe,0x06c,0x06c,0x06c,0x0fe,0x06c,0x06c,0x000,0x000,0x000,
    0x018,0x018,0x07c,0x0c6,0x0c2,0x0c0,0x07c,0x006,0x086,0x0c6,0x07c,0x018,0x018,0x000,
    0x000,0x000,0x000,0x000,0x0c2,0x0c6,0x00c,0x018,0x030,0x066,0x0c6,0x000,0x000,0x000,
    0x000,0x000,0x038,0x06c,0x06c,0x038,0x076,0x0dc,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x060,0x060,0x060,0x0c0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x018,0x030,0x060,0x060,0x060,0x060,0x060,0x030,0x018,0x000,0x000,0x000,
    0x000,0x000,0x030,0x018,0x00c,0x00c,0x00c,0x00c,0x00c,0x018,0x030,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x0cc,0x078,0x1fe,0x078,0x0cc,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x030,0x030,0x030,0x1fe,0x030,0x030,0x030,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x030,0x030,0x030,0x060,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x1fe,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x030,0x030,0x000,0x000,0x000,
    0x000,0x000,0x002,0x006,0x00c,0x018,0x030,0x060,0x0c0,0x080,0x000,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x0ce,0x0de,0x0f6,0x0e6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x018,0x038,0x078,0x018,0x018,0x018,0x018,0x018,0x07e,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x006,0x00c,0x018,0x030,0x060,0x0c6,0x0fe,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x006,0x006,0x03c,0x006,0x006,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x00c,0x01c,0x03c,0x06c,0x0cc,0x0fe,0x00c,0x00c,0x01e,0x000,0x000,0x000,
    0x000,0x000,0x0fe,0x0c0,0x0c0,0x0c0,0x0fc,0x006,0x006,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x038,0x060,0x0c0,0x0c0,0x0fc,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x0fe,0x0c6,0x006,0x00c,0x018,0x030,0x030,0x030,0x030,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x07c,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x07e,0x006,0x006,0x00c,0x078,0x000,0x000,0x000,
    0x000,0x000,0x000,0x030,0x030,0x000,0x000,0x000,0x030,0x030,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x030,0x030,0x000,0x000,0x000,0x030,0x030,0x060,0x000,0x000,0x000,
    0x000,0x000,0x00c,0x018,0x030,0x060,0x0c0,0x060,0x030,0x018,0x00c,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0fc,0x000,0x000,0x0fc,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x0c0,0x060,0x030,0x018,0x00c,0x018,0x030,0x060,0x0c0,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x0c6,0x00c,0x018,0x018,0x000,0x018,0x018,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x0c6,0x0de,0x0de,0x0de,0x0dc,0x0c0,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x010,0x038,0x06c,0x0c6,0x0c6,0x0fe,0x0c6,0x0c6,0x0c6,0x000,0x000,0x000,
    0x000,0x000,0x0fc,0x066,0x066,0x066,0x07c,0x066,0x066,0x066,0x0fc,0x000,0x000,0x000,
    0x000,0x000,0x03c,0x066,0x0c2,0x0c0,0x0c0,0x0c0,0x0c2,0x066,0x03c,0x000,0x000,0x000,
    0x000,0x000,0x0f8,0x06c,0x066,0x066,0x066,0x066,0x066,0x06c,0x0f8,0x000,0x000,0x000,
    0x000,0x000,0x0fe,0x066,0x062,0x068,0x078,0x068,0x062,0x066,0x0fe,0x000,0x000,0x000,
    0x000,0x000,0x0fe,0x066,0x062,0x068,0x078,0x068,0x060,0x060,0x0f0,0x000,0x000,0x000,
    0x000,0x000,0x03c,0x066,0x0c2,0x0c0,0x0c0,0x0de,0x0c6,0x066,0x03a,0x000,0x000,0x000,
    0x000,0x000,0x0c6,0x0c6,0x0c6,0x0c6,0x0fe,0x0c6,0x0c6,0x0c6,0x0c6,0x000,0x000,0x000,
    0x000,0x000,0x078,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x000,0x01e,0x00c,0x00c,0x00c,0x00c,0x00c,0x0cc,0x0cc,0x078,0x000,0x000,0x000,
    0x000,0x000,0x0e6,0x066,0x06c,0x06c,0x078,0x06c,0x06c,0x066,0x0e6,0x000,0x000,0x000,
    0x000,0x000,0x0f0,0x060,0x060,0x060,0x060,0x060,0x062,0x066,0x0fe,0x000,0x000,0x000,
    0x000,0x000,0x186,0x1ce,0x1fe,0x1b6,0x186,0x186,0x186,0x186,0x186,0x000,0x000,0x000,
    0x000,0x000,0x0c6,0x0e6,0x0f6,0x0fe,0x0de,0x0ce,0x0c6,0x0c6,0x0c6,0x000,0x000,0x000,
    0x000,0x000,0x038,0x06c,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x06c,0x038,0x000,0x000,0x000,
    0x000,0x000,0x0fc,0x066,0x066,0x066,0x07c,0x060,0x060,0x060,0x0f0,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x0c6,0x0d6,0x0de,0x07c,0x00c,0x00e,0x000,0x000,
    0x000,0x000,0x0fc,0x066,0x066,0x066,0x07c,0x06c,0x066,0x066,0x0e6,0x000,0x000,0x000,
    0x000,0x000,0x07c,0x0c6,0x0c6,0x060,0x038,0x00c,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x1fe,0x1b6,0x132,0x030,0x030,0x030,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x000,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x186,0x186,0x186,0x186,0x186,0x186,0x0cc,0x078,0x030,0x000,0x000,0x000,
    0x000,0x000,0x186,0x186,0x186,0x186,0x1b6,0x1b6,0x1fe,0x0cc,0x0cc,0x000,0x000,0x000,
    0x000,0x000,0x186,0x186,0x0cc,0x078,0x030,0x078,0x0cc,0x186,0x186,0x000,0x000,0x000,
    0x000,0x000,0x186,0x186,0x186,0x0cc,0x078,0x030,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x000,0x1fe,0x186,0x10c,0x018,0x030,0x060,0x0c2,0x186,0x1fe,0x000,0x000,0x000,
    0x000,0x000,0x078,0x060,0x060,0x060,0x060,0x060,0x060,0x060,0x078,0x000,0x000,0x000,
    0x000,0x000,0x080,0x0c0,0x0e0,0x070,0x038,0x01c,0x00e,0x006,0x002,0x000,0x000,0x000,
    0x000,0x000,0x078,0x018,0x018,0x018,0x018,0x018,0x018,0x018,0x078,0x000,0x000,0x000,
    0x010,0x038,0x06c,0x0c6,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x1fe,0x000,
    0x030,0x030,0x018,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x078,0x00c,0x07c,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x000,0x0e0,0x060,0x060,0x078,0x06c,0x066,0x066,0x066,0x0dc,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x07c,0x0c6,0x0c0,0x0c0,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x01c,0x00c,0x00c,0x03c,0x06c,0x0cc,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x07c,0x0c6,0x0fe,0x0c0,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x038,0x06c,0x064,0x060,0x0f8,0x060,0x060,0x060,0x0f0,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x076,0x0cc,0x0cc,0x0cc,0x07c,0x00c,0x0cc,0x078,0x000,
    0x000,0x000,0x0e0,0x060,0x060,0x06c,0x076,0x066,0x066,0x066,0x0e6,0x000,0x000,0x000,
    0x000,0x000,0x018,0x018,0x000,0x038,0x018,0x018,0x018,0x018,0x03c,0x000,0x000,0x000,
    0x000,0x000,0x00c,0x00c,0x000,0x01c,0x00c,0x00c,0x00c,0x00c,0x0cc,0x0cc,0x078,0x000,
    0x000,0x000,0x0e0,0x060,0x060,0x066,0x06c,0x078,0x06c,0x066,0x0e6,0x000,0x000,0x000,
    0x000,0x000,0x038,0x018,0x018,0x018,0x018,0x018,0x018,0x018,0x03c,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x1cc,0x1fe,0x1b6,0x1b6,0x1b6,0x1b6,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0dc,0x066,0x066,0x066,0x066,0x066,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0dc,0x066,0x066,0x066,0x07c,0x060,0x060,0x0f0,0x000,
    0x000,0x000,0x000,0x000,0x000,0x076,0x0cc,0x0cc,0x0cc,0x07c,0x00c,0x00c,0x01e,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0dc,0x076,0x066,0x060,0x060,0x0f0,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x07c,0x0c6,0x070,0x01c,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x010,0x030,0x030,0x0fc,0x030,0x030,0x030,0x036,0x01c,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0cc,0x0cc,0x0cc,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x186,0x186,0x186,0x0cc,0x078,0x030,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x186,0x186,0x1b6,0x1b6,0x1fe,0x0cc,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0c6,0x06c,0x038,0x038,0x06c,0x0c6,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0c6,0x0c6,0x0c6,0x0c6,0x07e,0x006,0x00c,0x078,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0fe,0x0cc,0x018,0x030,0x066,0x0fe,0x000,0x000,0x000,
    0x000,0x000,0x01c,0x030,0x030,0x030,0x0e0,0x030,0x030,0x030,0x01c,0x000,0x000,0x000,
    0x000,0x000,0x030,0x030,0x030,0x030,0x000,0x030,0x030,0x030,0x030,0x000,0x000,0x000,
    0x000,0x000,0x0e0,0x030,0x030,0x030,0x01c,0x030,0x030,0x030,0x0e0,0x000,0x000,0x000,
    0x000,0x000,0x076,0x0dc,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x010,0x038,0x06c,0x0c6,0x0c6,0x0fe,0x000,0x000,0x000,0x000,
    0x000,0x000,0x03c,0x066,0x0c2,0x0c0,0x0c0,0x0c2,0x066,0x03c,0x00c,0x006,0x07c,0x000,
    0x000,0x000,0x0cc,0x0cc,0x000,0x0cc,0x0cc,0x0cc,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x00c,0x018,0x030,0x000,0x07c,0x0c6,0x0fe,0x0c0,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x010,0x038,0x06c,0x000,0x078,0x00c,0x07c,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x000,0x0cc,0x0cc,0x000,0x078,0x00c,0x07c,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x060,0x030,0x018,0x000,0x078,0x00c,0x07c,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x038,0x06c,0x038,0x000,0x078,0x00c,0x07c,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x078,0x0cc,0x0c0,0x0cc,0x078,0x018,0x00c,0x078,0x000,0x000,
    0x000,0x010,0x038,0x06c,0x000,0x07c,0x0c6,0x0fe,0x0c0,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x0cc,0x0cc,0x000,0x07c,0x0c6,0x0fe,0x0c0,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x060,0x030,0x018,0x000,0x07c,0x0c6,0x0fe,0x0c0,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x0cc,0x0cc,0x000,0x070,0x030,0x030,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x030,0x078,0x0cc,0x000,0x070,0x030,0x030,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x0c0,0x060,0x030,0x000,0x070,0x030,0x030,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x0c6,0x0c6,0x010,0x038,0x06c,0x0c6,0x0c6,0x0fe,0x0c6,0x0c6,0x000,0x000,0x000,
    0x038,0x06c,0x038,0x000,0x038,0x06c,0x0c6,0x0c6,0x0fe,0x0c6,0x0c6,0x000,0x000,0x000,
    0x018,0x030,0x060,0x000,0x0fe,0x066,0x060,0x07c,0x060,0x066,0x0fe,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x0dc,0x076,0x036,0x0fc,0x1b0,0x1b8,0x0ee,0x000,0x000,0x000,
    0x000,0x000,0x03e,0x06c,0x0cc,0x0cc,0x0fe,0x0cc,0x0cc,0x0cc,0x0ce,0x000,0x000,0x000,
    0x000,0x010,0x038,0x06c,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x0c6,0x0c6,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x060,0x030,0x018,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x030,0x078,0x0cc,0x000,0x0cc,0x0cc,0x0cc,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x060,0x030,0x018,0x000,0x0cc,0x0cc,0x0cc,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x000,0x0c6,0x0c6,0x000,0x0c6,0x0c6,0x0c6,0x0c6,0x07e,0x006,0x00c,0x078,0x000,
    0x000,0x0c6,0x0c6,0x038,0x06c,0x0c6,0x0c6,0x0c6,0x0c6,0x06c,0x038,0x000,0x000,0x000,
    0x000,0x0c6,0x0c6,0x000,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x030,0x030,0x0fc,0x186,0x180,0x180,0x186,0x0fc,0x030,0x030,0x000,0x000,0x000,
    0x000,0x038,0x06c,0x064,0x060,0x0f0,0x060,0x060,0x060,0x0e6,0x0fc,0x000,0x000,0x000,
    0x000,0x000,0x186,0x0cc,0x078,0x030,0x1fe,0x030,0x1fe,0x030,0x030,0x000,0x000,0x000,
    0x000,0x1f8,0x0cc,0x0cc,0x0f8,0x0c4,0x0cc,0x0de,0x0cc,0x0cc,0x1e6,0x000,0x000,0x000,
    0x000,0x01c,0x036,0x030,0x030,0x030,0x0fc,0x030,0x030,0x030,0x030,0x1b0,0x0e0,0x000,
    0x000,0x018,0x030,0x060,0x000,0x078,0x00c,0x07c,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x018,0x030,0x060,0x000,0x070,0x030,0x030,0x030,0x030,0x078,0x000,0x000,0x000,
    0x000,0x018,0x030,0x060,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x018,0x030,0x060,0x000,0x0cc,0x0cc,0x0cc,0x0cc,0x0cc,0x076,0x000,0x000,0x000,
    0x000,0x000,0x076,0x0dc,0x000,0x0dc,0x066,0x066,0x066,0x066,0x066,0x000,0x000,0x000,
    0x076,0x0dc,0x000,0x0c6,0x0e6,0x0f6,0x0fe,0x0de,0x0ce,0x0c6,0x0c6,0x000,0x000,0x000,
    0x000,0x078,0x0d8,0x0d8,0x07c,0x000,0x0fc,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x070,0x0d8,0x0d8,0x070,0x000,0x0f8,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x030,0x030,0x000,0x030,0x030,0x060,0x0c6,0x0c6,0x07c,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x0fe,0x0c0,0x0c0,0x0c0,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x0fe,0x006,0x006,0x006,0x000,0x000,0x000,0x000,
    0x000,0x0c0,0x1c0,0x0c6,0x0cc,0x0d8,0x030,0x060,0x0dc,0x186,0x00c,0x018,0x03e,0x000,
    0x000,0x0c0,0x1c0,0x0c6,0x0cc,0x0d8,0x030,0x066,0x0ce,0x19e,0x03e,0x006,0x006,0x000,
    0x000,0x000,0x030,0x030,0x000,0x030,0x030,0x078,0x078,0x078,0x030,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x036,0x06c,0x0d8,0x06c,0x036,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x0d8,0x06c,0x036,0x06c,0x0d8,0x000,0x000,0x000,0x000,0x000,
    0x022,0x088,0x022,0x088,0x022,0x088,0x022,0x088,0x022,0x088,0x022,0x088,0x022,0x088,
    0x0aa,0x154,0x0aa,0x154,0x0aa,0x154,0x0aa,0x154,0x0aa,0x154,0x0aa,0x154,0x0aa,0x154,
    0x1ba,0x0ee,0x1ba,0x0ee,0x1ba,0x0ee,0x1ba,0x0ee,0x1ba,0x0ee,0x1ba,0x0ee,0x1ba,0x0ee,
    0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,
    0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x1f0,0x030,0x030,0x030,0x030,0x030,0x030,
    0x030,0x030,0x030,0x030,0x030,0x1f0,0x030,0x1f0,0x030,0x030,0x030,0x030,0x030,0x030,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x1ec,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x1fc,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x000,0x000,0x000,0x000,0x000,0x1f0,0x030,0x1f0,0x030,0x030,0x030,0x030,0x030,0x030,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x1ec,0x00c,0x1ec,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x000,0x000,0x000,0x000,0x000,0x1fc,0x00c,0x1ec,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x1ec,0x00c,0x1fc,0x000,0x000,0x000,0x000,0x000,0x000,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x1fc,0x000,0x000,0x000,0x000,0x000,0x000,
    0x030,0x030,0x030,0x030,0x030,0x1f0,0x030,0x1f0,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x1f0,0x030,0x030,0x030,0x030,0x030,0x030,
    0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x03f,0x000,0x000,0x000,0x000,0x000,0x000,
    0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x1ff,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x1ff,0x030,0x030,0x030,0x030,0x030,0x030,
    0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x03f,0x030,0x030,0x030,0x030,0x030,0x030,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x1ff,0x000,0x000,0x000,0x000,0x000,0x000,
    0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x1ff,0x030,0x030,0x030,0x030,0x030,0x030,
    0x030,0x030,0x030,0x030,0x030,0x03f,0x030,0x03f,0x030,0x030,0x030,0x030,0x030,0x030,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06f,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06f,0x060,0x07f,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x07f,0x060,0x06f,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x1ef,0x000,0x1ff,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x1ff,0x000,0x1ef,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06f,0x060,0x06f,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x000,0x000,0x000,0x000,0x000,0x1ff,0x000,0x1ff,0x000,0x000,0x000,0x000,0x000,0x000,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x1ef,0x000,0x1ef,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x030,0x030,0x030,0x030,0x030,0x1ff,0x000,0x1ff,0x000,0x000,0x000,0x000,0x000,0x000,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x1ff,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x1ff,0x000,0x1ff,0x030,0x030,0x030,0x030,0x030,0x030,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x1ff,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x07f,0x000,0x000,0x000,0x000,0x000,0x000,
    0x030,0x030,0x030,0x030,0x030,0x03f,0x030,0x03f,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x03f,0x030,0x03f,0x030,0x030,0x030,0x030,0x030,0x030,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x07f,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x1ff,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,
    0x030,0x030,0x030,0x030,0x030,0x1ff,0x030,0x1ff,0x030,0x030,0x030,0x030,0x030,0x030,
    0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x1f0,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x03f,0x030,0x030,0x030,0x030,0x030,0x030,
    0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,
    0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,0x1e0,
    0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,0x01f,
    0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x1ff,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x076,0x0dc,0x0d8,0x0d8,0x0dc,0x076,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x07c,0x0c6,0x0fc,0x0c6,0x0c6,0x0fc,0x0c0,0x0c0,0x040,0x000,
    0x000,0x000,0x0fe,0x0c6,0x0c6,0x0c0,0x0c0,0x0c0,0x0c0,0x0c0,0x0c0,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x0fe,0x06c,0x06c,0x06c,0x06c,0x06c,0x06c,0x000,0x000,0x000,
    0x000,0x000,0x0fe,0x0c6,0x060,0x030,0x018,0x030,0x060,0x0c6,0x0fe,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x07e,0x0d8,0x0d8,0x0d8,0x0d8,0x070,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x066,0x066,0x066,0x066,0x07c,0x060,0x060,0x0c0,0x000,0x000,
    0x000,0x000,0x000,0x000,0x076,0x0dc,0x018,0x018,0x018,0x018,0x018,0x000,0x000,0x000,
    0x000,0x000,0x0fc,0x030,0x078,0x0cc,0x0cc,0x0cc,0x078,0x030,0x0fc,0x000,0x000,0x000,
    0x000,0x000,0x038,0x06c,0x0c6,0x0c6,0x0fe,0x0c6,0x0c6,0x06c,0x038,0x000,0x000,0x000,
    0x000,0x000,0x038,0x06c,0x0c6,0x0c6,0x0c6,0x06c,0x06c,0x06c,0x0ee,0x000,0x000,0x000,
    0x000,0x000,0x03c,0x060,0x030,0x018,0x07c,0x0cc,0x0cc,0x0cc,0x078,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x0fc,0x1b6,0x1b6,0x0fc,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x006,0x00c,0x0fc,0x1b6,0x1b6,0x1e6,0x0fc,0x0c0,0x180,0x000,0x000,0x000,
    0x000,0x000,0x038,0x060,0x0c0,0x0c0,0x0f8,0x0c0,0x0c0,0x060,0x038,0x000,0x000,0x000,
    0x000,0x000,0x000,0x07c,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x0c6,0x000,0x000,0x000,
    0x000,0x000,0x000,0x0fe,0x000,0x000,0x0fe,0x000,0x000,0x0fe,0x000,0x000,0x000,0x000,
    0x000,0x000,0x030,0x030,0x030,0x1fe,0x030,0x030,0x030,0x000,0x1fe,0x000,0x000,0x000,
    0x000,0x000,0x060,0x030,0x018,0x00c,0x018,0x030,0x060,0x000,0x0fc,0x000,0x000,0x000,
    0x000,0x000,0x018,0x030,0x060,0x0c0,0x060,0x030,0x018,0x000,0x0fc,0x000,0x000,0x000,
    0x000,0x000,0x01c,0x036,0x036,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,
    0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x030,0x1b0,0x1b0,0x0e0,0x000,0x000,0x000,
    0x000,0x000,0x030,0x030,0x000,0x000,0x1fe,0x000,0x000,0x030,0x030,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x076,0x0dc,0x000,0x076,0x0dc,0x000,0x000,0x000,0x000,0x000,
    0x000,0x070,0x0d8,0x0d8,0x070,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x030,0x030,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x030,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x01e,0x018,0x018,0x018,0x018,0x018,0x1d8,0x0d8,0x078,0x038,0x000,0x000,0x000,
    0x000,0x1b0,0x0d8,0x0d8,0x0d8,0x0d8,0x0d8,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x0e0,0x1b0,0x060,0x0c0,0x190,0x1f0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x07c,0x07c,0x07c,0x07c,0x07c,0x07c,0x000,0x000,0x000,0x000,
    0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000
  ]);

  // CGA 16色カラー
  video_cga_color_8 = new Uint8Array([
    0x00,0x00,0x00,0xff,  // Black
    0x00,0x00,0xaa,0xff,  // Blue
    0x00,0xaa,0x00,0xff,  // Green
    0x00,0xaa,0xaa,0xff,  // Cyan
    0xaa,0x00,0x00,0xff,  // Red
    0xaa,0x00,0xaa,0xff,  // Magenta
    0xaa,0x55,0x00,0xff,  // Brown
    0xaa,0xaa,0xaa,0xff,  // Light Gray
    0x55,0x55,0x55,0xff,  // Gray
    0x55,0x55,0xff,0xff,  // Light Blue
    0x55,0xff,0x55,0xff,  // Light Green
    0x55,0xff,0xff,0xff,  // Light Cyan
    0xff,0x55,0x55,0xff,  // Light Red
    0xff,0x55,0xff,0xff,  // Light Magenta
    0xff,0xff,0x55,0xff,  // Yellow
    0xff,0xff,0xff,0xff   // White
  ]);
  video_cga_color = new Uint32Array(video_cga_color_8.buffer);
  video_cga_black    = video_cga_color[0];
  video_cga_green    = video_cga_color[2];
  video_cga_cyan     = video_cga_color[3];
  video_cga_red      = video_cga_color[4];
  video_cga_magenta  = video_cga_color[5];
  video_cga_brown    = video_cga_color[6];
  video_cga_lgray    = video_cga_color[7];
  video_cga_gray     = video_cga_color[8];
  video_cga_lgreen   = video_cga_color[10];
  video_cga_lcyan    = video_cga_color[11];
  video_cga_lred     = video_cga_color[12];
  video_cga_lmagenta = video_cga_color[13];
  video_cga_yellow   = video_cga_color[14];
  video_cga_white    = video_cga_color[15];

  // CGA カラー パレット
  video_cga_palette = new Uint32Array(4);

  // MDA 輝度
  video_mda_int0 = video_cga_black;
  video_mda_int1 = video_cga_gray;
  video_mda_int2 = video_cga_lgray;
  video_mda_int3 = video_cga_white;

  // コンポジット モニタ用 色差データ
  video_comp_cr = new Int16Array([ 10356,  13530,  14645,  13530,  10356,   5605,      0,  -5605,
                                  -10356, -13530, -14645, -13530, -10356,  -5605,      0,   5605]);
  video_comp_cg = new Int16Array([ -1689,  -4952,  -7460,  -8833,  -8860,  -7540,  -5071,  -1830,
                                    1689,   4952,   7460,   8833,   8860,   7540,   5071,   1830]);
  video_comp_cb = new Int16Array([-18462,  -9992,      0,   9992,  18462,  24122,  26110,  24122,
                                   18462,   9992,      0,  -9992, -18462, -24122, -26110, -24122]);

  // コンポジット モニタ用 8色カラー
  video_comp_phase = new Uint8Array([0x00, 0x1e, 0xc3, 0x87, 0x78, 0x3c, 0xe1, 0xff]);
  video_comp_phase_green = 0xc3;
  video_comp_phase_cyan = 0x87;
  video_comp_phase_red = 0x78;
  video_comp_phase_magenta = 0x3c;
  video_comp_phase_yellow = 0xe1;

  // コンポジット モニタ用 カラー パレット
  video_comp_palette_phase = new Uint8Array(5);  // 位相
  video_comp_palette_int = new Uint8Array(5);  // 輝度
  video_comp_palette_phase[4] = 0x00;
  video_comp_palette_int[4] = 0x0;

  video_comp_lpf_int = new Uint16Array(8);
  video_comp_lpf_chrom = new Uint16Array(4);

  video_mem_work = new Uint8Array(2);
  video_mem_work_2 = new Uint16Array(video_mem_work.buffer, 0, 1);
  // テキスト モード ビデオ メモリ クリア用
  video_mem_work[0] = 0x20;
  video_mem_work[1] = 0x07;
  video_text_clear = video_mem_work_2[0];

  video_composite = false;

// ビデオ初期化
function video_init() {
  mem[MEM_SCR_ROW_MAX] = 24;
  mem[MEM_SCR_COLS_1] = 0;
  mem[MEM_VBUF_SIZE_0] = 0x00;
  mem[MEM_VBUF_START_0] = 0x00;

  // 初期ビデオ モード セット
  regs[I_AH] = 0x00;
  regs[I_AL] = INIT_MODE;
  bios_video();

  video_frame_cnt = 0;
  video_refresh_req1 = video_refresh_req2 = false;
}

// 画面更新
function video_refresh() {
  var char_blink = video_frame_cnt & 0x10;

  var cursor;
  switch(video_cursor_mode) {
  case 0x20:
    cursor = 0;
    break;
  case 0x40:
    cursor = video_frame_cnt & 0x8;
    break;
  case 0x60:
    cursor = video_frame_cnt & 0x10;
    break;
  default:
    cursor = 1;
  }

  var video_buff;
  var i_buff = 0;
  var i_buff2;
  var video_mem = window.video_mem;
  var video_mem_o;
  var i_mem;
  var i_mem_cur;
  var col;
  var i_font;
  var font1;
  var line;
  var attr;
  var fore, back;
  var bit;
  var video_comp_color;
  var pixels;
  var lpf_int;
  var lpf_chrom;
  var e_o;
  var phase;
  var phase4;
  var pal_phase;
  var l_h;
  var cr, cg, cb;
  var prev_chrom;
  var prev_diff;
  var chg_phase;
  var chg_chrom;
  var chg_cnt;
  var color;
  var pre;
  var npost;
  var vid_int, vid_chrom;
  var chrom;
  var diff;
  var amp;
  var int;
  var int0;

  switch(video_adapter_mode) {  // ビデオ モード(ハードウェア設定)
  case 0:
  case 1:
    i_mem_cur = -1;
    if(cursor) {
      if(video_cursor_start < video_scan_lines
           && video_cursor_end >= video_cursor_start && video_cursor_end < video_scan_lines)
        i_mem_cur = video_cursor_addr << 1;
    }

    video_buff = window.video_buff;
    i_mem = video_start_addr << 1;
    while(i_buff < video_i_buff_end) {
      for(col = 40; col; col--) {
        i_font = video_mem[i_mem] << 3;  // 文字
        attr = video_mem[i_mem + 1];  // 属性
        fore = video_cga_color[attr & 0xf];
        if(video_blink) {
          back = video_cga_color[(attr & 0x70) >> 4];
          if(attr & 0x80) {  // ブリンク
            if(char_blink)
              fore = back;
          }
        }
        else {
          back = video_cga_color[attr >> 4];
        }

        i_buff2 = i_buff;
        for(line = video_scan_lines; line; line--) {
          font1 = video_font_8x8[i_font++];
          for(bit = 0x80; bit; bit >>= 1)
            video_buff[i_buff2++] = (font1 & bit) ? fore : back;
          i_buff2 += 312/* 320-8 */;
        }

        // カーソル
        if(i_mem == i_mem_cur) {
          i_buff2 = i_buff + video_cursor_start * 320;
          for(line = video_cursor_end - video_cursor_start + 1; line; line--) {
            for(bit = 8; bit; bit--)
              video_buff[i_buff2++] = fore;
            i_buff2 += 312/* 320-8 */;
          }
        }

        i_buff += 8;
        i_mem = (i_mem + 2) & 0x7fff;
      }
      i_buff += video_i_buff_adj;
    }
    break;

  case 2:
  case 3:
    i_mem_cur = -1;
    if(cursor) {
      if(video_cursor_start < video_scan_lines
           && video_cursor_end >= video_cursor_start && video_cursor_end < video_scan_lines)
        i_mem_cur = video_cursor_addr << 1;
    }

    video_buff = window.video_buff;
    i_mem = video_start_addr << 1;
    while(i_buff < video_i_buff_end) {
      for(col = 80; col; col--) {
        i_font = video_mem[i_mem] << 3;  // 文字
        attr = video_mem[i_mem + 1];  // 属性
        fore = video_cga_color[attr & 0xf];
        if(video_blink) {
          back = video_cga_color[(attr & 0x70) >> 4];
          if(attr & 0x80) {  // ブリンク
            if(char_blink)
              fore = back;
          }
        }
        else {
          back = video_cga_color[attr >> 4];
        }

        i_buff2 = i_buff;
        for(line = video_scan_lines; line; line--) {
          font1 = video_font_8x8[i_font++];
          for(bit = 0x80; bit; bit >>= 1)
            video_buff[i_buff2++] = (font1 & bit) ? fore : back;
          i_buff2 += 632/* 640-8 */;
        }

        // カーソル
        if(i_mem == i_mem_cur) {
          i_buff2 = i_buff + video_cursor_start * 640;
          for(line = video_cursor_end - video_cursor_start + 1; line; line--) {
            for(bit = 8; bit; bit--)
              video_buff[i_buff2++] = fore;
            i_buff2 += 632/* 640-8 */;
          }
        }

        i_buff += 8;
        i_mem = (i_mem + 2) & 0x7fff;
      }
      i_buff += video_i_buff_adj;
    }
    break;

  case 4:
  case 5:
    if(video_composite) {
      // 320x200 コンポジット モニタの処理
      video_buff = video_data.data;
      i_mem = (video_start_addr << 1) & 0x1fff;
      var video_comp_palette_phase = window.video_comp_palette_phase;
      var video_comp_palette_int = window.video_comp_palette_int;
      video_comp_color = window.video_comp_color;
      lpf_int = video_comp_lpf_int;
      lpf_chrom = video_comp_lpf_chrom;
      while(i_buff < 256000/* 200*320*4 */) {
        for(; ; ) {  // 偶数ライン/奇数ライン
          lpf_int.fill(0);
          int = 0;
          lpf_chrom.fill(0);
          chrom = 0;
          phase = 0;
          bit = -1;
          cr = cg = cb = 0;
          prev_chrom = 0;
          prev_diff = 0;
          chg_cnt = -1;
          color = false;
          pre = npost = true;
          for(i_buff2 = i_buff + 1276/* 320*4-4 */; ; ) {
            var i;
            if(bit < 0) {
              if(npost) {
                pixels = video_mem[i_mem];
                i_mem = (i_mem + 1) & 0x1fff;
                i = pixels >> 6;
                bit = 4;
              }
              else {
                i = 4;
              }
            }
            else {
              i = (pixels >> bit) & 0x3;
              bit -= 2;
            }
            pal_phase = video_comp_palette_phase[i];
            if(l_h = (pal_phase << phase) & 0x80) {
              vid_int = 0x9f0;
              vid_chrom = 683;
            }
            else {
              vid_int = vid_chrom = 0;
            }
            if(video_comp_palette_int[i]) {  // 高輝度
              vid_int += 0x500;
              vid_chrom += 341;
            }

            phase4 = 0;
            for(; ; ) {
              int = int - lpf_int[phase] + vid_int;
              lpf_int[phase] = vid_int;
              if(!phase4)
                int0 = int;

              if(video_comp_color) {  // 色信号あり
                chrom = chrom - lpf_chrom[phase4] + vid_chrom;
                lpf_chrom[phase4] = vid_chrom;

                if     (chrom > prev_chrom)
                  diff = 1;
                else if(chrom < prev_chrom)
                  diff = -1;
                else
                  diff = 0;

                if(diff != prev_diff) {
                  if(chg_cnt > 1) {
                    if(chg_cnt < 4)
                      chg_phase = (chg_phase - (4 - chg_cnt)) & 0xf;
                    amp = (prev_diff > 0) ? prev_chrom - chg_chrom : chg_chrom - prev_chrom;
                    // RGB の各計算値は 0〜255 の範囲外になるものもあるが,Uint8ClampedArray によりクランプされる
                    cr = (video_comp_cr[chg_phase] * amp) >> 12;
                    cg = (video_comp_cg[chg_phase] * amp) >> 12;
                    cb = (video_comp_cb[chg_phase] * amp) >> 12;

                    chg_cnt = -1;
                    color = true;
                  }

                  if(diff) {
                    chg_phase = phase << 1;
                    if(diff < 0)  // 立ち下がり
                      chg_phase ^= 0x8;  // 位相を 180°ずらす
                    chg_chrom = prev_chrom;
                    chg_cnt = 0;
                  }

                  prev_diff = diff;
                }

                prev_chrom = chrom;

                if(chg_cnt >= 0)
                  chg_cnt++;
              }

              phase = (phase + 1) & 0x7;
              phase4 = phase & 0x3;
              if(!phase4)
                break;

              if(l_h != ((pal_phase << phase) & 0x80)) {
                // 色信号反転
                if(l_h) {
                  vid_int -= 0x9f0;
                  vid_chrom -= 683;
                }
                else {
                  vid_int += 0x9f0;
                  vid_chrom += 683;
                }
                l_h = pal_phase = 0x00;
              }
            }

            if(pre) {
              pre = false;
            }
            else {
              if(int0) {
                video_buff[i_buff]     = (int0 + cr + 0x40) >> 7;
                video_buff[i_buff + 1] = (int0 + cg + 0x40) >> 7;
                video_buff[i_buff + 2] = (int0 + cb + 0x40) >> 7;
              }
              else {
                video_buff[i_buff] = video_buff[i_buff + 1] = video_buff[i_buff + 2] = 0;
              }
              if((i_buff += 4) >= i_buff2) {
                if(i_buff > i_buff2)
                  break;
                npost = false;
              }
            }

            if(color) {
              cr = cg = cb = 0;
              color = false;
            }
          }

          if(video_mem == window.video_mem_o) {
            video_mem = window.video_mem;
            break;
          }
          video_mem = window.video_mem_o;
          i_mem = (i_mem - 80) & 0x1fff;
        }
      }
    }
    else {
      video_buff = window.video_buff;
      video_mem_o = window.video_mem_o;
      i_mem = (video_start_addr << 1) & 0x1fff;
      var video_cga_palette = window.video_cga_palette;
      while(i_buff < 64000/* 200*320 */) {
        for(i_buff2 = i_buff + 320; i_buff < i_buff2; ) {
          pixels = video_mem[i_mem];
          video_buff[i_buff]     = video_cga_palette[pixels >> 6];
          video_buff[i_buff + 1] = video_cga_palette[(pixels & 0x30) >> 4];
          video_buff[i_buff + 2] = video_cga_palette[(pixels & 0x0c) >> 2];
          video_buff[i_buff + 3] = video_cga_palette[pixels & 0x03];
          pixels = video_mem_o[i_mem];
          video_buff[i_buff + 320] = video_cga_palette[pixels >> 6];
          video_buff[i_buff + 321] = video_cga_palette[(pixels & 0x30) >> 4];
          video_buff[i_buff + 322] = video_cga_palette[(pixels & 0x0c) >> 2];
          video_buff[i_buff + 323] = video_cga_palette[pixels & 0x03];
          i_buff += 4;
          i_mem = (i_mem + 1) & 0x1fff;
        }
        i_buff += 320;
      }
    }
    break;

  case 6:
    if(video_composite) {
      // 640x200 コンポジット モニタの処理
      video_buff = video_data.data;
      i_mem = (video_start_addr << 1) & 0x1fff;
      video_comp_color = window.video_comp_color;
      lpf_int = video_comp_lpf_int;
      lpf_chrom = video_comp_lpf_chrom;
      pal_phase = window.video_comp_palette_phase[0];

      var fore_vid_int_l, fore_vid_int_h;
      var fore_vid_chrom_l, fore_vid_chrom_h;
      if(window.video_comp_palette_int[0]) {  // 高輝度
        fore_vid_int_l = 0x500;
        fore_vid_int_h = 0xef0;
        fore_vid_chrom_l = 341;
        fore_vid_chrom_h = 1024;
      }
      else {
        fore_vid_int_l = fore_vid_chrom_l = 0;
        fore_vid_int_h = 0x9f0;
        fore_vid_chrom_h = 683;
      }

      while(i_buff < 512000/* 200*640*4 */) {
        for(; ; ) {  // 偶数ライン/奇数ライン
          lpf_int.fill(0);
          int = 0;
          lpf_chrom.fill(0);
          chrom = 0;
          phase = 0;
          bit = 0;
          var alt;
          cr = cg = cb = 0;
          prev_chrom = 0;
          prev_diff = 0;
          chg_cnt = -1;
          color = 0;
          pre = 2;
          npost = true;
          for(i_buff2 = i_buff + 2552/* 640*4-8 */; ; ) {
            if(!bit) {
              if(npost) {
                pixels = video_mem[i_mem];
                i_mem = (i_mem + 1) & 0x1fff;
                bit = 0x80;
              }
            }

            if(pixels & bit) {
              if(l_h = (pal_phase << phase) & 0x80) {
                vid_int = fore_vid_int_h;
                vid_chrom = fore_vid_chrom_h;
              }
              else {
                vid_int = fore_vid_int_l;
                vid_chrom = fore_vid_chrom_l;
              }
              alt = (l_h != ((pal_phase << ((phase + 1) & 0x7)) & 0x80));
            }
            else {
              vid_int = vid_chrom = 0;
              alt = false;
            }
            bit >>= 1;

            phase4 = phase & 0x3;
            for(; ; ) {
              int = int - lpf_int[phase] + vid_int;
              lpf_int[phase] = vid_int;

              if(video_comp_color) {  // 色信号あり
                chrom = chrom - lpf_chrom[phase4] + vid_chrom;
                lpf_chrom[phase4] = vid_chrom;

                if     (chrom > prev_chrom)
                  diff = 1;
                else if(chrom < prev_chrom)
                  diff = -1;
                else
                  diff = 0;

                if(diff != prev_diff) {
                  if(chg_cnt > 1) {
                    if(chg_cnt < 4)
                      chg_phase = (chg_phase - (4 - chg_cnt)) & 0xf;
                    amp = (prev_diff > 0) ? prev_chrom - chg_chrom : chg_chrom - prev_chrom;
                    // RGB の各計算値は 0〜255 の範囲外になるものもあるが,Uint8ClampedArray によりクランプされる
                    cr = (video_comp_cr[chg_phase] * amp) >> 12;
                    cg = (video_comp_cg[chg_phase] * amp) >> 12;
                    cb = (video_comp_cb[chg_phase] * amp) >> 12;

                    chg_cnt = -1;
                    color = 2;
                  }

                  if(diff) {
                    chg_phase = phase << 1;
                    if(diff < 0)  // 立ち下がり
                      chg_phase ^= 0x8;  // 位相を 180°ずらす
                    chg_chrom = prev_chrom;
                    chg_cnt = 0;
                  }

                  prev_diff = diff;
                }

                prev_chrom = chrom;

                if(chg_cnt >= 0)
                  chg_cnt++;
              }

              phase = (phase + 1) & 0x7;
              if(!(phase & 0x1))
                break;
              phase4++;
              int0 = int;

              if(alt) {
                // 色信号反転
                if(l_h) {
                  vid_int -= 0x9f0;
                  vid_chrom -= 683;
                }
                else {
                  vid_int += 0x9f0;
                  vid_chrom += 683;
                }
              }
            }

            if(pre) {
              pre--;
            }
            else {
              if(int0) {
                video_buff[i_buff]     = (int0 + cr + 0x40) >> 7;
                video_buff[i_buff + 1] = (int0 + cg + 0x40) >> 7;
                video_buff[i_buff + 2] = (int0 + cb + 0x40) >> 7;
              }
              else {
                video_buff[i_buff] = video_buff[i_buff + 1] = video_buff[i_buff + 2] = 0;
              }
              if((i_buff += 4) >= i_buff2) {
                if(i_buff == i_buff2 + 8)
                  break;
                npost = false;
              }
            }

            if(color) {
              if(!--color)
                cr = cg = cb = 0;
            }
          }

          if(video_mem == window.video_mem_o) {
            video_mem = window.video_mem;
            break;
          }
          video_mem = window.video_mem_o;
          i_mem = (i_mem - 80) & 0x1fff;
        }
      }
    }
    else {
      video_buff = window.video_buff;
      video_mem_o = window.video_mem_o;
      i_mem = (video_start_addr << 1) & 0x1fff;
      fore = window.video_cga_palette[0];
      back = video_cga_black;
      while(i_buff < 128000/* 200*640 */) {
        for(i_buff2 = i_buff + 640; i_buff < i_buff2; ) {
          pixels = video_mem[i_mem];
          var pixels_o = video_mem_o[i_mem];
          for(bit = 0x80; bit; bit >>= 1) {
            video_buff[i_buff]       = (pixels & bit)   ? fore : back;
            video_buff[i_buff + 640] = (pixels_o & bit) ? fore : back;
            i_buff++;
          }
          i_mem = (i_mem + 1) & 0x1fff;
        }
        i_buff += 640;
      }
    }
    break;

  case 7:
    i_mem_cur = -1;
    if(cursor) {
      if(video_cursor_start < 14 && video_cursor_end >= video_cursor_start && video_cursor_end < 14)
        i_mem_cur = video_cursor_addr << 1;
    }

    video_buff = window.video_buff;
    i_mem = video_start_addr << 1;
    while(i_buff < 252000/* 350*720 */) {
      for(col = 80; col; col--) {
        i_font = video_mem[i_mem] * 14;  // 文字
        attr = video_mem[i_mem + 1];  // 属性
        if((attr & 0x77) == 0x00) {
          fore = back = video_mda_int0;
        }
        else {
          switch(attr) {
          case 0x70:
            fore = video_mda_int0;
            back = video_mda_int2;
            break;

          case 0x78:
            fore = video_mda_int1;
            back = video_mda_int2;
            break;

          case 0xf0:
            if(video_blink) {
              back = video_mda_int2;
              fore = (char_blink) ? back : video_mda_int0;
            }
            else {
              fore = video_mda_int0;
              back = video_mda_int3;
            }
            break;

          case 0xf8:
            if(video_blink) {
              back = video_mda_int2;
              fore = (char_blink) ? back : video_mda_int1;
            }
            else {
              fore = video_mda_int1;
              back = video_mda_int3;
            }
            break;

          default:
            fore = (attr & 0x08) ? video_mda_int3 : video_mda_int2;
            back = video_mda_int0;
          }
        }

        i_buff2 = i_buff;
        for(line = 14; line; line--) {
          font1 = video_font_9x14[i_font++];
          for(bit = 0x100; bit; bit >>= 1)
            video_buff[i_buff2++] = (font1 & bit) ? fore : back;
          i_buff2 += 711/* 720-9 */;
        }

        if((attr & 0x7) == 0x1) {  // 下線
          i_buff2 = i_buff + 8640/* 12*720 */;
          for(bit = 9; bit; bit--)
            video_buff[i_buff2++] = fore;
        }

        // カーソル
        if(i_mem == i_mem_cur) {
          i_buff2 = i_buff + video_cursor_start * 720;
          for(line = video_cursor_end - video_cursor_start + 1; line; line--) {
            for(bit = 9; bit; bit--)
              video_buff[i_buff2++] = fore;
            i_buff2 += 711/* 720-9 */;
          }
        }

        i_buff += 9;
        i_mem = (i_mem + 2) & 0x7fff;
      }
      i_buff += 9360/* 13*720 */;
    }
  }
}

// 画面更新  カラー パレット変更時用
function video_refresh_1l() {
  var i_line = ((video_line_cnt > 240) ? 502 : 240) - video_line_cnt;
  if(i_line >= 200)
    return;

  var video_buff;
  var i_buff, i_buff2;
  var video_mem = (i_line & 0x1) ? video_mem_o : window.video_mem;
  var i_mem = ((video_start_addr << 1) + (i_line & 0xfe) * 40) & 0x1fff;
  var bit;
  var video_comp_color;
  var pixels;
  var lpf_int;
  var lpf_chrom;
  var phase;
  var phase4;
  var pal_phase;
  var l_h;
  var cr, cg, cb;
  var prev_chrom;
  var prev_diff;
  var chg_phase;
  var chg_chrom;
  var chg_cnt;
  var color;
  var pre;
  var npost;
  var vid_int, vid_chrom;
  var chrom;
  var diff;
  var amp;
  var int;
  var int0;

  if(video_adapter_mode == 6) {
    if(video_composite) {
      video_buff = video_data.data;
      i_buff = i_line * 2560/* 640*4 */;
      video_comp_color = window.video_comp_color;
      lpf_int = video_comp_lpf_int;
      lpf_chrom = video_comp_lpf_chrom;
      pal_phase = window.video_comp_palette_phase[0];

      var fore_vid_int_l, fore_vid_int_h;
      var fore_vid_chrom_l, fore_vid_chrom_h;
      if(window.video_comp_palette_int[0]) {  // 高輝度
        fore_vid_int_l = 0x500;
        fore_vid_int_h = 0xef0;
        fore_vid_chrom_l = 341;
        fore_vid_chrom_h = 1024;
      }
      else {
        fore_vid_int_l = fore_vid_chrom_l = 0;
        fore_vid_int_h = 0x9f0;
        fore_vid_chrom_h = 683;
      }

      lpf_int.fill(0);
      int = 0;
      lpf_chrom.fill(0);
      chrom = 0;
      phase = 0;
      bit = 0;
      var alt;
      cr = cg = cb = 0;
      prev_chrom = 0;
      prev_diff = 0;
      chg_cnt = -1;
      color = 0;
      pre = 2;
      npost = true;
      for(i_buff2 = i_buff + 2552/* 640*4-8 */; ; ) {
        if(!bit) {
          if(npost) {
            pixels = video_mem[i_mem];
            i_mem = (i_mem + 1) & 0x1fff;
            bit = 0x80;
          }
        }

        if(pixels & bit) {
          if(l_h = (pal_phase << phase) & 0x80) {
            vid_int = fore_vid_int_h;
            vid_chrom = fore_vid_chrom_h;
          }
          else {
            vid_int = fore_vid_int_l;
            vid_chrom = fore_vid_chrom_l;
          }
          alt = (l_h != ((pal_phase << ((phase + 1) & 0x7)) & 0x80));
        }
        else {
          vid_int = vid_chrom = 0;
          alt = false;
        }
        bit >>= 1;

        phase4 = phase & 0x3;
        for(; ; ) {
          int = int - lpf_int[phase] + vid_int;
          lpf_int[phase] = vid_int;

          if(video_comp_color) {  // 色信号あり
            chrom = chrom - lpf_chrom[phase4] + vid_chrom;
            lpf_chrom[phase4] = vid_chrom;

            if     (chrom > prev_chrom)
              diff = 1;
            else if(chrom < prev_chrom)
              diff = -1;
            else
              diff = 0;

            if(diff != prev_diff) {
              if(chg_cnt > 1) {
                if(chg_cnt < 4)
                  chg_phase = (chg_phase - (4 - chg_cnt)) & 0xf;
                amp = (prev_diff > 0) ? prev_chrom - chg_chrom : chg_chrom - prev_chrom;
                cr = (video_comp_cr[chg_phase] * amp) >> 12;
                cg = (video_comp_cg[chg_phase] * amp) >> 12;
                cb = (video_comp_cb[chg_phase] * amp) >> 12;

                chg_cnt = -1;
                color = 2;
              }

              if(diff) {
                chg_phase = phase << 1;
                if(diff < 0)  // 立ち下がり
                  chg_phase ^= 0x8;  // 位相を 180°ずらす
                chg_chrom = prev_chrom;
                chg_cnt = 0;
              }

              prev_diff = diff;
            }

            prev_chrom = chrom;

            if(chg_cnt >= 0)
              chg_cnt++;
          }

          phase = (phase + 1) & 0x7;
          if(!(phase & 0x1))
            break;
          phase4++;
          int0 = int;

          if(alt) {
            // 色信号反転
            if(l_h) {
              vid_int -= 0x9f0;
              vid_chrom -= 683;
            }
            else {
              vid_int += 0x9f0;
              vid_chrom += 683;
            }
          }
        }

        if(pre) {
          pre--;
        }
        else {
          if(int0) {
            video_buff[i_buff]     = (int0 + cr + 0x40) >> 7;
            video_buff[i_buff + 1] = (int0 + cg + 0x40) >> 7;
            video_buff[i_buff + 2] = (int0 + cb + 0x40) >> 7;
          }
          else {
            video_buff[i_buff] = video_buff[i_buff + 1] = video_buff[i_buff + 2] = 0;
          }
          if((i_buff += 4) >= i_buff2) {
            if(i_buff == i_buff2 + 8)
              break;
            npost = false;
          }
        }

        if(color) {
          if(!--color)
            cr = cg = cb = 0;
        }
      }
    }
    else {
      video_buff = window.video_buff;
      i_buff = i_line * 640;
      var fore = window.video_cga_palette[0];
      var back = video_cga_black;
      for(i_buff2 = i_buff + 640; i_buff < i_buff2; ) {
        pixels = video_mem[i_mem];
        for(bit = 0x80; bit; bit >>= 1)
          video_buff[i_buff++] = (pixels & bit) ? fore : back;
        i_mem = (i_mem + 1) & 0x1fff;
      }
    }
  }
  else {
    if(video_composite) {
      video_buff = video_data.data;
      i_buff = i_line * 1280/* 320*4 */;
      var video_comp_palette_phase = window.video_comp_palette_phase;
      var video_comp_palette_int = window.video_comp_palette_int;
      video_comp_color = window.video_comp_color;
      lpf_int = video_comp_lpf_int;
      lpf_chrom = video_comp_lpf_chrom;
      lpf_int.fill(0);
      int = 0;
      lpf_chrom.fill(0);
      chrom = 0;
      phase = 0;
      bit = -1;
      cr = cg = cb = 0;
      prev_chrom = 0;
      prev_diff = 0;
      chg_cnt = -1;
      color = false;
      pre = npost = true;
      for(i_buff2 = i_buff + 1276/* 320*4-4 */; ; ) {
        var i;
        if(bit < 0) {
          if(npost) {
            pixels = video_mem[i_mem];
            i_mem = (i_mem + 1) & 0x1fff;
            i = pixels >> 6;
            bit = 4;
          }
          else {
            i = 4;
          }
        }
        else {
          i = (pixels >> bit) & 0x3;
          bit -= 2;
        }
        pal_phase = video_comp_palette_phase[i];
        if(l_h = (pal_phase << phase) & 0x80) {
          vid_int = 0x9f0;
          vid_chrom = 683;
        }
        else {
          vid_int = vid_chrom = 0;
        }
        if(video_comp_palette_int[i]) {  // 高輝度
          vid_int += 0x500;
          vid_chrom += 341;
        }

        phase4 = 0;
        for(; ; ) {
          int = int - lpf_int[phase] + vid_int;
          lpf_int[phase] = vid_int;
          if(!phase4)
            int0 = int;

          if(video_comp_color) {  // 色信号あり
            chrom = chrom - lpf_chrom[phase4] + vid_chrom;
            lpf_chrom[phase4] = vid_chrom;

            if     (chrom > prev_chrom)
              diff = 1;
            else if(chrom < prev_chrom)
              diff = -1;
            else
              diff = 0;

            if(diff != prev_diff) {
              if(chg_cnt > 1) {
                if(chg_cnt < 4)
                  chg_phase = (chg_phase - (4 - chg_cnt)) & 0xf;
                amp = (prev_diff > 0) ? prev_chrom - chg_chrom : chg_chrom - prev_chrom;
                cr = (video_comp_cr[chg_phase] * amp) >> 12;
                cg = (video_comp_cg[chg_phase] * amp) >> 12;
                cb = (video_comp_cb[chg_phase] * amp) >> 12;

                chg_cnt = -1;
                color = true;
              }

              if(diff) {
                chg_phase = phase << 1;
                if(diff < 0)  // 立ち下がり
                  chg_phase ^= 0x8;  // 位相を 180°ずらす
                chg_chrom = prev_chrom;
                chg_cnt = 0;
              }

              prev_diff = diff;
            }

            prev_chrom = chrom;

            if(chg_cnt >= 0)
              chg_cnt++;
          }

          phase = (phase + 1) & 0x7;
          phase4 = phase & 0x3;
          if(!phase4)
            break;

          if(l_h != ((pal_phase << phase) & 0x80)) {
            // 色信号反転
            if(l_h) {
              vid_int -= 0x9f0;
              vid_chrom -= 683;
            }
            else {
              vid_int += 0x9f0;
              vid_chrom += 683;
            }
            l_h = pal_phase = 0x00;
          }
        }

        if(pre) {
          pre = false;
        }
        else {
          if(int0) {
            video_buff[i_buff]     = (int0 + cr + 0x40) >> 7;
            video_buff[i_buff + 1] = (int0 + cg + 0x40) >> 7;
            video_buff[i_buff + 2] = (int0 + cb + 0x40) >> 7;
          }
          else {
            video_buff[i_buff] = video_buff[i_buff + 1] = video_buff[i_buff + 2] = 0;
          }
          if((i_buff += 4) >= i_buff2) {
            if(i_buff > i_buff2)
              break;
            npost = false;
          }
        }

        if(color) {
          cr = cg = cb = 0;
          color = false;
        }
      }
    }
    else {
      video_buff = window.video_buff;
      i_buff = i_line * 320;
      var video_cga_palette = window.video_cga_palette;
      for(i_buff2 = i_buff + 320; i_buff < i_buff2; i_buff += 4) {
        pixels = video_mem[i_mem];
        video_buff[i_buff]     = video_cga_palette[pixels >> 6];
        video_buff[i_buff + 1] = video_cga_palette[(pixels & 0x30) >> 4];
        video_buff[i_buff + 2] = video_cga_palette[(pixels & 0x0c) >> 2];
        video_buff[i_buff + 3] = video_cga_palette[pixels & 0x03];
        i_mem = (i_mem + 1) & 0x1fff;
      }
    }
  }
}

// ---- キーボード エミュレーション ----

  keyboard_keys_norm = {
    "0":[0x30,0x0b],"1":[0x31,0x02],"2":[0x32,0x03],"3":[0x33,0x04],"4":[0x34,0x05],
    "5":[0x35,0x06],"6":[0x36,0x07],"7":[0x37,0x08],"8":[0x38,0x09],"9":[0x39,0x0a],
    "A":[0x61,0x1e],"B":[0x62,0x30],"C":[0x63,0x2e],"D":[0x64,0x20],"E":[0x65,0x12],"F":[0x66,0x21],
    "G":[0x67,0x22],"H":[0x68,0x23],"I":[0x69,0x17],"J":[0x6a,0x24],"K":[0x6b,0x25],"L":[0x6c,0x26],
    "M":[0x6d,0x32],"N":[0x6e,0x31],"O":[0x6f,0x18],"P":[0x70,0x19],"Q":[0x71,0x10],"R":[0x72,0x13],
    "S":[0x73,0x1f],"T":[0x74,0x14],"U":[0x75,0x16],"V":[0x76,0x2f],"W":[0x77,0x11],"X":[0x78,0x2d],
    "Y":[0x79,0x15],"Z":[0x7a,0x2c],
    "a":[0x61,0x1e],"b":[0x62,0x30],"c":[0x63,0x2e],"d":[0x64,0x20],"e":[0x65,0x12],"f":[0x66,0x21],
    "g":[0x67,0x22],"h":[0x68,0x23],"i":[0x69,0x17],"j":[0x6a,0x24],"k":[0x6b,0x25],"l":[0x6c,0x26],
    "m":[0x6d,0x32],"n":[0x6e,0x31],"o":[0x6f,0x18],"p":[0x70,0x19],"q":[0x71,0x10],"r":[0x72,0x13],
    "s":[0x73,0x1f],"t":[0x74,0x14],"u":[0x75,0x16],"v":[0x76,0x2f],"w":[0x77,0x11],"x":[0x78,0x2d],
    "y":[0x79,0x15],"z":[0x7a,0x2c],
    " ":[0x20,0x39],"!":[0x21,0x02],"\"":[0x22,0x28],"#":[0x23,0x04],"$":[0x24,0x05],"%":[0x25,0x06],
    "&":[0x26,0x08],"'":[0x27,0x28],"(":[0x28,0x0a],")":[0x29,0x0b],"*":[0x2a,0x09],"+":[0x2b,0x0d],
    ",":[0x2c,0x33],"-":[0x2d,0x0c],".":[0x2e,0x34],"/":[0x2f,0x35],":":[0x3a,0x27],";":[0x3b,0x27],
    "<":[0x3c,0x33],"=":[0x3d,0x0d],">":[0x3e,0x34],"?":[0x3f,0x35],"@":[0x40,0x03],"[":[0x5b,0x1a],
    "\\":[0x5c,0x2b],"]":[0x5d,0x1b],"^":[0x5e,0x07],"_":[0x5f,0x0c],"`":[0x60,0x29],"{":[0x7b,0x1a],
    "|":[0x7c,0x2b],"}":[0x7d,0x1b],"~":[0x7e,0x29],
    "Backspace":[0x08,0x0e],"Tab":[0x09,0x0f],"Enter":[0x0d,0x1c],"Esc":[0x1b,0x01],
    "F1":[0x00,0x3b],"F2":[0x00,0x3c],"F3":[0x00,0x3d],"F4":[0x00,0x3e],"F5":[0x00,0x3f],"F6":[0x00,0x40],
    "F7":[0x00,0x41],"F8":[0x00,0x42],"F9":[0x00,0x43],"F10":[0x00,0x44],"F11":[0x00,0x85],"F12":[0x00,0x86],
    "Insert":[0xe0,0x52],"Del":[0xe0,0x53],"Home":[0xe0,0x47],"End":[0xe0,0x4f],
    "PageUp":[0xe0,0x49],"PageDown":[0xe0,0x51],
    "Up":[0xe0,0x48],"Down":[0xe0,0x50],"Left":[0xe0,0x4b],"Right":[0xe0,0x4d],
    "Escape":[0x1b,0x01],
    "ArrowUp":[0xe0,0x48],"ArrowDown":[0xe0,0x50],"ArrowLeft":[0xe0,0x4b],"ArrowRight":[0xe0,0x4d]
  };

  keyboard_keys_norm_num = {
    "0":[0x30,0x52],"1":[0x31,0x4f],"2":[0x32,0x50],"3":[0x33,0x51],"4":[0x34,0x4b],
    "5":[0x35,0x4c],"6":[0x36,0x4d],"7":[0x37,0x47],"8":[0x38,0x48],"9":[0x39,0x49],
    "*":[0x2a,0x37],"+":[0x2b,0x4e],"-":[0x2d,0x4a],".":[0x2e,0x53],"/":[0x2f,0xe0],"Enter":[0x0d,0xe0],
    "Insert":[0x00,0x52],"Del":[0x00,0x53],"Home":[0x00,0x47],"End":[0x00,0x4f],
    "PageUp":[0x00,0x49],"PageDown":[0x00,0x51],
    "Up":[0x00,0x48],"Down":[0x00,0x50],"Left":[0x00,0x4b],"Right":[0x00,0x4d],"Clear":[0xf0,0x4c],
    "ArrowUp":[0x00,0x48],"ArrowDown":[0x00,0x50],"ArrowLeft":[0x00,0x4b],"ArrowRight":[0x00,0x4d]
  };

  keyboard_keys_shift = {
    "0":[0x30,0x0b],"1":[0x31,0x02],"2":[0x32,0x03],"3":[0x33,0x04],"4":[0x34,0x05],
    "5":[0x35,0x06],"6":[0x36,0x07],"7":[0x37,0x08],"8":[0x38,0x09],"9":[0x39,0x0a],
    "A":[0x41,0x1e],"B":[0x42,0x30],"C":[0x43,0x2e],"D":[0x44,0x20],"E":[0x45,0x12],"F":[0x46,0x21],
    "G":[0x47,0x22],"H":[0x48,0x23],"I":[0x49,0x17],"J":[0x4a,0x24],"K":[0x4b,0x25],"L":[0x4c,0x26],
    "M":[0x4d,0x32],"N":[0x4e,0x31],"O":[0x4f,0x18],"P":[0x50,0x19],"Q":[0x51,0x10],"R":[0x52,0x13],
    "S":[0x53,0x1f],"T":[0x54,0x14],"U":[0x55,0x16],"V":[0x56,0x2f],"W":[0x57,0x11],"X":[0x58,0x2d],
    "Y":[0x59,0x15],"Z":[0x5a,0x2c],
    "a":[0x41,0x1e],"b":[0x42,0x30],"c":[0x43,0x2e],"d":[0x44,0x20],"e":[0x45,0x12],"f":[0x46,0x21],
    "g":[0x47,0x22],"h":[0x48,0x23],"i":[0x49,0x17],"j":[0x4a,0x24],"k":[0x4b,0x25],"l":[0x4c,0x26],
    "m":[0x4d,0x32],"n":[0x4e,0x31],"o":[0x4f,0x18],"p":[0x50,0x19],"q":[0x51,0x10],"r":[0x52,0x13],
    "s":[0x53,0x1f],"t":[0x54,0x14],"u":[0x55,0x16],"v":[0x56,0x2f],"w":[0x57,0x11],"x":[0x58,0x2d],
    "y":[0x59,0x15],"z":[0x5a,0x2c],
    " ":[0x20,0x39],"!":[0x21,0x02],"\"":[0x22,0x28],"#":[0x23,0x04],"$":[0x24,0x05],"%":[0x25,0x06],
    "&":[0x26,0x08],"'":[0x27,0x28],"(":[0x28,0x0a],")":[0x29,0x0b],"*":[0x2a,0x09],"+":[0x2b,0x0d],
    ",":[0x2c,0x33],"-":[0x2d,0x0c],".":[0x2e,0x34],"/":[0x2f,0x35],":":[0x3a,0x27],";":[0x3b,0x27],
    "<":[0x3c,0x33],"=":[0x3d,0x0d],">":[0x3e,0x34],"?":[0x3f,0x35],"@":[0x40,0x03],"[":[0x5b,0x1a],
    "\\":[0x5c,0x2b],"]":[0x5d,0x1b],"^":[0x5e,0x07],"_":[0x5f,0x0c],"`":[0x60,0x29],"{":[0x7b,0x1a],
    "|":[0x7c,0x2b],"}":[0x7d,0x1b],"~":[0x7e,0x29],
    "Backspace":[0x08,0x0e],"Tab":[0x00,0x0f],"Enter":[0x0d,0x1c],"Esc":[0x1b,0x01],
    "F1":[0x00,0x54],"F2":[0x00,0x55],"F3":[0x00,0x56],"F4":[0x00,0x57],"F5":[0x00,0x58],"F6":[0x00,0x59],
    "F7":[0x00,0x5a],"F8":[0x00,0x5b],"F9":[0x00,0x5c],"F10":[0x00,0x5d],"F11":[0x00,0x87],"F12":[0x00,0x88],
    "Insert":[0xe0,0x52],"Del":[0xe0,0x53],"Home":[0xe0,0x47],"End":[0xe0,0x4f],
    "PageUp":[0xe0,0x49],"PageDown":[0xe0,0x51],
    "Up":[0xe0,0x48],"Down":[0xe0,0x50],"Left":[0xe0,0x4b],"Right":[0xe0,0x4d],
    "Escape":[0x1b,0x01],
    "ArrowUp":[0xe0,0x48],"ArrowDown":[0xe0,0x50],"ArrowLeft":[0xe0,0x4b],"ArrowRight":[0xe0,0x4d]
  };

  keyboard_keys_shift_num = {
    "0":[0x30,0x52],"1":[0x31,0x4f],"2":[0x32,0x50],"3":[0x33,0x51],"4":[0x34,0x4b],
    "5":[0x35,0x4c],"6":[0x36,0x4d],"7":[0x37,0x47],"8":[0x38,0x48],"9":[0x39,0x49],
    "*":[0x2a,0x37],"+":[0x2b,0x4e],"-":[0x2d,0x4a],".":[0x2e,0x53],"/":[0x2f,0xe0],"Enter":[0x0d,0xe0],
    "Insert":[0xe0,0x52],"Del":[0xe0,0x53],"Home":[0xe0,0x47],"End":[0xe0,0x4f],
    "PageUp":[0xe0,0x49],"PageDown":[0xe0,0x51],
    "Up":[0xe0,0x48],"Down":[0xe0,0x50],"Left":[0xe0,0x4b],"Right":[0xe0,0x4d],
    "ArrowUp":[0xe0,0x48],"ArrowDown":[0xe0,0x50],"ArrowLeft":[0xe0,0x4b],"ArrowRight":[0xe0,0x4d]
  };

  keyboard_keys_ctrl = {
    "2":[0x00,0x03],"a":[0x01,0x1e],"b":[0x02,0x30],"c":[0x03,0x2e],"d":[0x04,0x20],"e":[0x05,0x12],
    "f":[0x06,0x21],"g":[0x07,0x22],"h":[0x08,0x23],"i":[0x09,0x17],"j":[0x0a,0x24],"k":[0x0b,0x25],
    "l":[0x0c,0x26],"m":[0x0d,0x32],"n":[0x0e,0x31],"o":[0x0f,0x18],"p":[0x10,0x19],"q":[0x11,0x10],
    "r":[0x12,0x13],"s":[0x13,0x1f],"t":[0x14,0x14],"u":[0x15,0x16],"v":[0x16,0x2f],"w":[0x17,0x11],
    "x":[0x18,0x2d],"y":[0x19,0x15],"z":[0x1a,0x2c],"[":[0x1b,0x1a],"\\":[0x1c,0x2b],"]":[0x1d,0x1b],
    "6":[0x1e,0x07],"-":[0x1f,0x0c],
                    "A":[0x01,0x1e],"B":[0x02,0x30],"C":[0x03,0x2e],"D":[0x04,0x20],"E":[0x05,0x12],
    "F":[0x06,0x21],"G":[0x07,0x22],"H":[0x08,0x23],"I":[0x09,0x17],"J":[0x0a,0x24],"K":[0x0b,0x25],
    "L":[0x0c,0x26],"M":[0x0d,0x32],"N":[0x0e,0x31],"O":[0x0f,0x18],"P":[0x10,0x19],"Q":[0x11,0x10],
    "R":[0x12,0x13],"S":[0x13,0x1f],"T":[0x14,0x14],"U":[0x15,0x16],"V":[0x16,0x2f],"W":[0x17,0x11],
    "X":[0x18,0x2d],"Y":[0x19,0x15],"Z":[0x1a,0x2c],
    "Backspace":[0x7f,0x0e],"Tab":[0x00,0x94],"Enter":[0x0a,0x1c],"Esc":[0x1b,0x01],
    "F1":[0x00,0x5e],"F2":[0x00,0x5f],"F3":[0x00,0x60],"F4":[0x00,0x61],"F5":[0x00,0x62],"F6":[0x00,0x63],
    "F7":[0x00,0x64],"F8":[0x00,0x65],"F9":[0x00,0x66],"F10":[0x00,0x67],"F11":[0x00,0x89],"F12":[0x00,0x8a],
    "Insert":[0xe0,0x92],"Del":[0xe0,0x93],"Home":[0xe0,0x77],"End":[0xe0,0x75],
    "PageUp":[0xe0,0x84],"PageDown":[0xe0,0x76],
    "Up":[0xe0,0x8d],"Down":[0xe0,0x91],"Left":[0xe0,0x73],"Right":[0xe0,0x74],"Cancel":[0x00,0x00],
    "Escape":[0x1b,0x01],
    "ArrowUp":[0xe0,0x8d],"ArrowDown":[0xe0,0x91],"ArrowLeft":[0xe0,0x73],"ArrowRight":[0xe0,0x74]
  };

  keyboard_keys_ctrl_num = {
    "-":[0x00,0x8e],"*":[0x00,0x96],"+":[0x00,0x90],".":[0x00,0x93],"/":[0x00,0x95],"Enter":[0x0a,0xe0],
    "Insert":[0x00,0x92],"Del":[0x00,0x93],"Home":[0x00,0x77],"End":[0x00,0x75],
    "PageUp":[0x00,0x84],"PageDown":[0x00,0x76],
    "Up":[0x00,0x8d],"Down":[0x00,0x91],"Left":[0x00,0x73],"Right":[0x00,0x74],"Clear":[0x00,0x8f],
    "ArrowUp":[0x00,0x8d],"ArrowDown":[0x00,0x91],"ArrowLeft":[0x00,0x73],"ArrowRight":[0x00,0x74]
  };

  keyboard_keys_alt = {
    "0":[0x00,0x81],"1":[0x00,0x78],"2":[0x00,0x79],"3":[0x00,0x7a],"4":[0x00,0x7b],
    "5":[0x00,0x7c],"6":[0x00,0x7d],"7":[0x00,0x7e],"8":[0x00,0x7f],"9":[0x00,0x80],
    "A":[0x00,0x1e],"B":[0x00,0x30],"C":[0x00,0x2e],"D":[0x00,0x20],"E":[0x00,0x12],"F":[0x00,0x21],
    "G":[0x00,0x22],"H":[0x00,0x23],"I":[0x00,0x17],"J":[0x00,0x24],"K":[0x00,0x25],"L":[0x00,0x26],
    "M":[0x00,0x32],"N":[0x00,0x31],"O":[0x00,0x18],"P":[0x00,0x19],"Q":[0x00,0x10],"R":[0x00,0x13],
    "S":[0x00,0x1f],"T":[0x00,0x14],"U":[0x00,0x16],"V":[0x00,0x2f],"W":[0x00,0x11],"X":[0x00,0x2d],
    "Y":[0x00,0x15],"Z":[0x00,0x2c],
    "a":[0x00,0x1e],"b":[0x00,0x30],"c":[0x00,0x2e],"d":[0x00,0x20],"e":[0x00,0x12],"f":[0x00,0x21],
    "g":[0x00,0x22],"h":[0x00,0x23],"i":[0x00,0x17],"j":[0x00,0x24],"k":[0x00,0x25],"l":[0x00,0x26],
    "m":[0x00,0x32],"n":[0x00,0x31],"o":[0x00,0x18],"p":[0x00,0x19],"q":[0x00,0x10],"r":[0x00,0x13],
    "s":[0x00,0x1f],"t":[0x00,0x14],"u":[0x00,0x16],"v":[0x00,0x2f],"w":[0x00,0x11],"x":[0x00,0x2d],
    "y":[0x00,0x15],"z":[0x00,0x2c],
    " ":[0x20,0x39],
    "Backspace":[0x00,0x0e],"Tab":[0x00,0xa5],"Enter":[0x00,0x1c],"Esc":[0x00,0x01],
    "F1":[0x00,0x68],"F2":[0x00,0x69],"F3":[0x00,0x6a],"F4":[0x00,0x6b],"F5":[0x00,0x6c],"F6":[0x00,0x6d],
    "F7":[0x00,0x6e],"F8":[0x00,0x6f],"F9":[0x00,0x70],"F10":[0x00,0x71],"F11":[0x00,0x8b],"F12":[0x00,0x8c],
    "Insert":[0x00,0xa2],"Del":[0x00,0xa3],"Home":[0x00,0x97],"End":[0x00,0x9f],
    "PageUp":[0x00,0x99],"PageDown":[0x00,0xa1],
    "Up":[0x00,0x98],"Down":[0x00,0xa0],"Left":[0x00,0x9b],"Right":[0x00,0x9d],
    "Escape":[0x00,0x01],
    "ArrowUp":[0x00,0x98],"ArrowDown":[0x00,0xa0],"ArrowLeft":[0x00,0x9b],"ArrowRight":[0x00,0x9d]
  };

  keyboard_keys_alt_num = {
    "*":[0x00,0x37],"+":[0x00,0x4e],"-":[0x00,0x4a],"/":[0x00,0xa4],
    "Enter":[0x00,0xa6],
    "Insert":[0x00,0xa2],"Del":[0x00,0xa3],"Home":[0x00,0x97],"End":[0x00,0x9f],
    "PageUp":[0x00,0x99],"PageDown":[0x00,0xa1],
    "Up":[0x00,0x98],"Down":[0x00,0xa0],"Left":[0x00,0x9b],"Right":[0x00,0x9d],
    "ArrowUp":[0x00,0x98],"ArrowDown":[0x00,0xa0],"ArrowLeft":[0x00,0x9b],"ArrowRight":[0x00,0x9d]
  };

  keyboard_keys_raw = {
    "0":0x0b,"1":0x02,"2":0x03,"3":0x04,"4":0x05,"5":0x06,"6":0x07,"7":0x08,"8":0x09,"9":0x0a,
    "A":0x1e,"B":0x30,"C":0x2e,"D":0x20,"E":0x12,"F":0x21,"G":0x22,"H":0x23,"I":0x17,"J":0x24,
    "K":0x25,"L":0x26,"M":0x32,"N":0x31,"O":0x18,"P":0x19,"Q":0x10,"R":0x13,"S":0x1f,"T":0x14,
    "U":0x16,"V":0x2f,"W":0x11,"X":0x2d,"Y":0x15,"Z":0x2c,
    "a":0x1e,"b":0x30,"c":0x2e,"d":0x20,"e":0x12,"f":0x21,"g":0x22,"h":0x23,"i":0x17,"j":0x24,
    "k":0x25,"l":0x26,"m":0x32,"n":0x31,"o":0x18,"p":0x19,"q":0x10,"r":0x13,"s":0x1f,"t":0x14,
    "u":0x16,"v":0x2f,"w":0x11,"x":0x2d,"y":0x15,"z":0x2c,
    " ":0x39,"Backspace":0x0e,"Tab":0x0f,"Enter":0x1c,"Esc":0x01,
    "F1":0x3b,"F2":0x3c,"F3":0x3d,"F4":0x3e,"F5":0x3f,"F6":0x40,"F7":0x41,"F8":0x42,"F9":0x43,"F10":0x44,
    "F11":0x85,"F12":0x86,
    "Insert":0x52,"Del":0x53,"Home":0x47,"End":0x4f,"PageUp":0x49,"PageDown":0x51,
    "Up":0x48,"Down":0x50,"Left":0x4b,"Right":0x4d,"Clear":0x4c,
    "Control":0x1d,"Alt":0x38,"CapsLock":0x3a,"NumLock":0x45,"ScrollLock":0x46,"PrintScreen":0x37,
    "Escape":0x01,
    "ArrowUp":0x48,"ArrowDown":0x50,"ArrowLeft":0x4b,"ArrowRight":0x4d
  };

  // オート リピート レート
  keyboard_rep_rate_tbl = [
     33.36,  37.53,  41.70,  45.87,  50.04,  54.21,  58.38,  62.55,
     66.72,  75.06,  83.40,  91.74, 100.08, 108.42, 116.76, 125.10,
    133.44, 150.12, 166.80, 183.48, 200.16, 216.84, 233.52, 250.20,
    266.88, 300.24, 333.60, 366.96, 400.32, 433.68, 467.04, 500.40
  ];

  keyboard_buff = new Uint8Array(mem.buffer, MEM_KB_BUFF, 32);  // BIOS キー入力バッファ

  keyboard_input = [];  // キー入力

  addEventListener("keydown", keyboard_event, false);
  addEventListener("keyup",   keyboard_event, false);
  addEventListener("mousemove", keyboard_get_lock, false);

// キーボード初期化
function keyboard_init() {
  // BIOS キー入力バッファ
  keyboard_i_buff_head = keyboard_i_buff_tail = 0;
  mem[MEM_KB_BUFF_HEAD_0] = mem[MEM_KB_BUFF_TAIL_0] = KB_BUFF_OFF & 0xff;
  mem[MEM_KB_BUFF_HEAD_1] = mem[MEM_KB_BUFF_TAIL_1] = (KB_BUFF_OFF & 0xff00) >> 8;

  mem[MEM_KB_STATUS_0] = mem[MEM_KB_STATUS_1] = 0;
  mem[MEM_KB_FLAGS] = 0x10;

  keyboard_input.length = 0;
  keyboard_data = undefined;
  keyboard_rep_delay = 500;
  keyboard_rep_rate = keyboard_rep_rate_tbl[11];
  keyboard_rep_next = Number.MAX_VALUE;

  keyboard_status = 0x00;
}

// キー入力イベント
function keyboard_event(e) {
  if(!run_state)  // 停止中
    return;

  e.preventDefault();

  if(e.repeat)
    return;

  var data = {down:(e.type == "keydown"), key:e.key, loc:e.location,
              shift:e.shiftKey, ctrl:e.ctrlKey, alt:e.altKey,
              caps:e.getModifierState("CapsLock"),
              num:e.getModifierState("NumLock"),
              scroll:e.getModifierState("ScrollLock")};
  if(data.down) {
    keyboard_rep_next = timer.now() + keyboard_rep_delay;
    keyboard_rep_data = data;
  }
  else {
    keyboard_rep_next = Number.MAX_VALUE;
  }

  if(keyboard_input.length < 4)
    keyboard_input.push(data);
}

// キーボード割り込み
// INT 09H 割り込みをフックするプログラムのため,83 キーボード(と F11,F12)の
// INT 09H 割り込みをエミュレートする.
// 動作環境によりキーボードのキー配列が異なることと,Shift キー押下状態と文字の
// 対応が一般には 83 キーボードとは合わないことにより,83 キーボードの動作を完全
// にエミュレートすることはできない.
function keyboard_int() {
  pic_m_isr |= 0x02;
  pic_m_irr &= 0xfd;
  if(run_hwint_ss < 0) {
    run_hwint_ss = ss4;
    run_hwint_sp = regs_2[I_SP_2];
  }
  run_halt = false;

  keyboard_data = keyboard_input.shift();  // キー入力データ取り出し

  if((keyboard_scan = keyboard_keys_raw[keyboard_data.key]) == undefined) {
    if(keyboard_data.key == "Shift")
      keyboard_scan = (keyboard_data.loc == KeyboardEvent.DOM_KEY_LOCATION_RIGHT) ? 0x36 : 0x2a;
    else
      keyboard_scan = 0;
  }
  if(keyboard_scan) {
    if(!keyboard_data.down)
      keyboard_scan |= 0x80;
    keyboard_status |= 0x01;

    int(0x09);
    return false;
  }
  keyboard_int_def();
  return true;
}

// キーボード割り込み デフォルト処理
function keyboard_int_def() {
  keyboard_scan = 0;
  keyboard_status &= 0xfe;

  pic_m_isr &= 0xfd;  // EOI

  var data;
  if((data = keyboard_data) == undefined)
    return;
  keyboard_data = undefined;

  var down = data.down;
  var key  = data.key;

  if(down) {
    if(run_pause) {  // Pause 中
      run_pause = false;
      return;
    }
    // Pause キーの処理
    if(key == "Pause") {
      run_pause = true;
      return;
    }
  }

  // modifier key
  var status_0 = mem[MEM_KB_STATUS_0] & 0xf3;
  var status_1 = mem[MEM_KB_STATUS_1];
  var flags = mem[MEM_KB_FLAGS];

  switch(key) {
  case "Shift":
    if(data.loc == KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
      if(down)
        status_0 |= 0x01;
      else
        status_0 &= 0xfe;
    }
    else {  // STANDARD,LEFT
      if(down)
        status_0 |= 0x02;
      else
        status_0 &= 0xfd;
    }
    break;

  case "Control":
    if(data.loc == KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
      if(down)
        flags |= 0x04;
      else
        flags &= 0xfb;
    }
    else {  // STANDARD,LEFT
      if(down)
        status_1 |= 0x01;
      else
        status_1 &= 0xfe;
    }
    break;

  case "Alt":
    if(data.loc == KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
      if(down)
        flags |= 0x08;
      else
        flags &= 0xf7;
    }
    else {  // STANDARD,LEFT
      if(down)
        status_1 |= 0x02;
      else
        status_1 &= 0xfd;
    }
    break;

  case "CapsLock":
    if(down)
      status_1 |= 0x40;
    else
      status_1 &= 0xbf;
    if(data.caps)
      status_0 |= 0x40;
    else
      status_0 &= 0xbf;
    break;

  case "NumLock":
    if(down)
      status_1 |= 0x20;
    else
      status_1 &= 0xdf;
    if(data.num)
      status_0 |= 0x20;
    else
      status_0 &= 0xdf;
    break;

  case "ScrollLock":
    if(down)
      status_1 |= 0x10;
    else
      status_1 &= 0xef;
    if(data.scroll)
      status_0 |= 0x10;
    else
      status_0 &= 0xef;
    break;

  case "Insert":
    if(down)
      status_1 |= 0x80;
    else
      status_1 &= 0x7f;

    status_0 ^= 0x80;
    break;

  case "PrintScreen":  // SysRq
    if(down)
      status_1 |= 0x04;
    else
      status_1 &= 0xfb;
    break;
  }

  mem[MEM_KB_STATUS_0] = status_0 | ((status_1 & 0x03) << 2) | flags & 0x0c;
  mem[MEM_KB_STATUS_1] = status_1;
  mem[MEM_KB_FLAGS] = flags;

  if(down) {
    // スキャン コード/文字コードを BIOS のキー入力バッファに書き込む.
    // Shift キー押下状態と文字の対応は,一般には 101 キーボードとは合わない.

    var numpad = (data.loc == KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);

    var keys;
    if(data.alt)
      // Alt
      keys = (numpad) ? keyboard_keys_alt_num : keyboard_keys_alt;
    else if(data.ctrl)
      // Ctrl
      keys = (numpad) ? keyboard_keys_ctrl_num : keyboard_keys_ctrl;
    else if(data.shift)
      // Shift
      keys = (numpad) ? keyboard_keys_shift_num : keyboard_keys_shift;
    else
      keys = (numpad) ? keyboard_keys_norm_num : keyboard_keys_norm;
    var code = keys[key];
    if(code != undefined) {
      var char = code[0];  // 文字コード
      if(status_0 & 0x40) {  // CapsLock
        var char2 = char & 0xdf;
        if(char2 >= 0x41 && char2 <= 0x5a)  // 'A' 〜 'Z'
          char ^= 0x20;
      }

      var new_tail = (keyboard_i_buff_tail + 2) & 0x1f;
      if(new_tail != keyboard_i_buff_head) {  // バッファ一杯でない
        keyboard_buff[keyboard_i_buff_tail] = char;
        keyboard_buff[keyboard_i_buff_tail + 1] = code[1];  // スキャン コード
        keyboard_i_buff_tail = new_tail;
        var off = KB_BUFF_OFF + keyboard_i_buff_tail;
        mem[MEM_KB_BUFF_TAIL_0] = off & 0xff;
        mem[MEM_KB_BUFF_TAIL_1] = (off & 0xff00) >> 8;

        if(run_wait == 2)  // 入力待ち
          run_wait = 0x10002;
      }
    }
  }
}

function keyboard_get_lock(e) {
  var status_0 = mem[MEM_KB_STATUS_0] & 0x8f;
  if(e.getModifierState("CapsLock"))
    status_0 |= 0x40;
  if(e.getModifierState("NumLock"))
    status_0 |= 0x20;
  if(e.getModifierState("ScrollLock"))
    status_0 |= 0x10;
  mem[MEM_KB_STATUS_0] = status_0;

  removeEventListener("mousemove", keyboard_get_lock, false);
}

// ---- I/O エミュレーション ----

// 1 バイト入力
function in_1(port) {
  var al = 0;

  var count;
  switch(port) {
  case I_DMA_STATUS:
    al = io_dma_status;
    io_dma_status ^= 0x04;  // チャネル 2 TC
    break;

  case I_PIC_M_STATUS_0:
    al = (pic_m_ris) ? pic_m_isr : pic_m_irr;
    break;

  case I_PIC_M_STATUS_1:
    al = pic_m_imr;
    break;

  case I_TIMER_0_COUNT:
    count = (timer_0_latch < 0) ? get_timer_0_count() : timer_0_latch;
    switch(timer_0_control & 0x30) {
    case 0x10:  // 下位バイト
      al = count & 0xff;
      timer_0_latch = -1;
      break;
    case 0x30:  // 2 バイト
      if(timer_0_i_lh = !timer_0_i_lh) {
        al = count & 0xff;
        break;
      }
      // fall thru
    case 0x20:  // 上位バイト
      al = (count & 0xff00) >> 8;
      timer_0_latch = -1;
      break;
    }
    break;

  case I_TIMER_2_COUNT:
    count = (timer_2_latch < 0) ? get_timer_2_count() : timer_2_latch;
    switch(timer_2_control & 0x30) {
    case 0x10:  // 下位バイト
      al = count & 0xff;
      timer_2_latch = -1;
      break;
    case 0x30:  // 2 バイト
      if(timer_2_i_lh = !timer_2_i_lh) {
        al = count & 0xff;
        break;
      }
      // fall thru
    case 0x20:  // 上位バイト
      al = (count & 0xff00) >> 8;
      timer_2_latch = -1;
      break;
    }
    break;

  case I_PARALLEL_A:
    if(io_parallel_b & 0x80) {
      al = conf_sw1;
    }
    else {
      al = keyboard_scan;
      keyboard_scan = 0;
      keyboard_status &= 0xfe;
    }
    break;

  case I_PARALLEL_B:
    al = io_parallel_b & 0x0f;
    break;

  case I_KB_STATUS:
    al = keyboard_status;
    break;

  case I_JOYSTICK:
    al = 0xff;
    break;

  case I_MDA_DATA:
    if(video_mode == 7) {
      switch(video_crtc_index) {
      case 14:  // Cursor (H)
        al = video_cursor_addr >> 8;
        break;
      case 15:  // Cursor (L)
        al = video_cursor_addr & 0xff;
        break;
      }
    }
    break;

  case I_MDA_STATUS:
    if(video_mode == 7)
      al = (timer_now >= video_line_next - 0.008857722827089869
         && timer_now <  video_line_next - 0.0005536076766931168)  // 水平同期
             ? 0x01 : 0x00;
    break;

  case I_CGA_DATA:
    if(video_mode != 7) {
      switch(video_crtc_index) {
      case 14:  // Cursor (H)
        al = video_cursor_addr >> 8;
        break;
      case 15:  // Cursor (L)
        al = video_cursor_addr & 0xff;
        break;
      case 16:  // Light Pen (H)
        al = video_lp_latch >> 8;
        break;
      case 17:  // Light Pen (L)
        al = video_lp_latch & 0xff;
        break;
      }
    }
    break;

  case I_CGA_UNKNOWN:
    if(video_mode != 7)
      al = 0x08;
    break;

  case I_CGA_STATUS:
    if(video_mode != 7) {
      if(video_line_cnt <= 16)  // 垂直同期
        al = 0x09;
      else if((video_line_cnt <= 40 || video_line_cnt > 240)  // 垂直帰線区間
                || timer_now >= video_line_next - 0.018996827809120993)  // 水平帰線区間
        al = 0x01;
      else
        al = 0x00;
      al |= video_lp_trigger;
    }
    break;

  case I_FDC_STATUS:
    al = 0x80;
    break;
  }

  regs[I_AL] = al;
}

// 2 バイト入力
function in_2(port) {
  var ax = 0;

  switch(port) {
  // 1 バイトの I/O を行うべきところを 2 バイトの I/O を行っている
  // プログラムがあるので,2 バイト I/O にも対応しておく.
  case I_MDA_DATA:
  case I_MDA_STATUS:
  case I_CGA_DATA:
  case I_CGA_STATUS:
    in_1(port);
    regs[I_AH] = 0;
    return;
  }

  set_reg2(I_AX, ax);
}

// 1 バイト出力
function out_1(port) {
  var al = regs[I_AL];

  var new_count;
  switch(port) {
  case O_PIC_M_COMMAND_0:
    // EOI は Non-Specific/Specific EOI のみ対応する
    switch(al & 0xf8) {
    case 0x20:  // Non-Specific EOI
      if(pic_m_isr & 0x01)
        pic_m_isr &= 0xfe;
      else
        pic_m_isr &= 0xfd;
      break;
    case 0x60:  // Specific EOI
      switch(al & 0x07) {  // IR レベル
      case 0:
        pic_m_isr &= 0xfe;
        break;
      case 1:
        pic_m_isr &= 0xfd;
        break;
      }
      break;
    }
    if((al & 0x9a) == 0x0a)  // IRR/ISR 選択
      pic_m_ris = al & 0x01;
    break;

  case O_PIC_M_COMMAND_1:
    // PIC の再初期化には対応していない
    pic_m_imr = al;
    break;

  case O_TIMER_0_COUNT:
    switch(timer_0_control & 0x30) {
    case 0x10:  // 下位バイト
      new_count = al;
      break;
    case 0x20:  // 上位バイト
      new_count = al << 8;
      break;
    case 0x30:  // 2 バイト
      if(timer_0_o_lh = !timer_0_o_lh) {
        timer_0_count_low = al;
        timer_0_next = Number.MAX_VALUE;
        return;
      }
      new_count = timer_0_count_low | (al << 8);
      break;
    default:
      return;
    }
    if(!new_count)
      new_count = 0x10000;
    timer_0_latch = -1;

    switch(timer_0_control & 0x0e) {
    case 0x00:  // Mode 0
      break;
    case 0x04:  // Mode 2
    case 0x0c:
    case 0x06:  // Mode 3
    case 0x0e:
      if(timer_0_next < Number.MAX_VALUE) {  // カウント開始済み
        timer_0_new_count = new_count;
        return;
      }
      break;
    default:
      return;
    }
    timer_0_count = new_count;
    timer_0_org = timer_now;
    timer_0_osc_cnt = timer_0_count;
    timer_0_next = timer_0_org + timer_0_count * OSC_INT;
    timer_0_new_count = 0;
    break;

  case O_TIMER_2_COUNT:
    switch(timer_2_control & 0x30) {
    case 0x10:  // 下位バイト
      new_count = al;
      break;
    case 0x20:  // 上位バイト
      new_count = al << 8;
      break;
    case 0x30:  // 2 バイト
      if(timer_2_o_lh = !timer_2_o_lh) {
        timer_2_count_low = al;
        timer_2_org = Number.MAX_VALUE;
        return;
      }
      new_count = timer_2_count_low | (al << 8);
      break;
    default:
      return;
    }
    if(!new_count)
      new_count = 0x10000;
    timer_2_latch = -1;

    switch(timer_2_control & 0x0e) {
    case 0x00:  // Mode 0
      break;
    case 0x04:  // Mode 2
    case 0x0c:
    case 0x06:  // Mode 3
    case 0x0e:
      if((timer_2_control & 0x06) == 0x06) {  // Mode 3
        beep_osc.frequency.value = TIMER_FREQ / new_count;
        if(sound_state != 1) {  // ビープ音発生中でない
          if((io_parallel_b & 0x03) == 0x03) {  // ゲート オン,イネーブル
            beep_osc_gain.connect(aud_ctx.destination);
            sound_state = 1;
          }
        }
      }
      if(timer_2_org < Number.MAX_VALUE) {  // カウント開始済み
        if(!(io_parallel_b & 0x01)) {  // ゲート オフ
          timer_2_org += timer_now - timer_2_gate_off;
          timer_2_gate_off = timer_now;
        }
        if(timer_2_new_count) {
          var next = timer_2_org + timer_2_span;
          if(timer_now >= next) {
            timer_2_org = next;
            timer_2_count = timer_2_new_count;
          }
        }
        timer_2_new_count = new_count;
        timer_2_span = Math.ceil((timer_now - timer_2_org) / (timer_2_count * OSC_INT)) * timer_2_count * OSC_INT;
        return;
      }
      break;
    default:
      return;
    }
    timer_2_count = new_count;
    timer_2_org = timer_now;
    timer_2_new_count = 0;
    if(!(io_parallel_b & 0x01))  // ゲート オフ
      timer_2_gate_off = timer_now;
    break;

  case O_TIMER_CONTROL:
    if(al & 0x30) {
      switch(al & 0xc0) {
      case 0x00:  // タイマー 0
        timer_0_control = al;
        timer_0_o_lh = timer_0_i_lh = false;
        timer_0_latch = -1;
        switch(al & 0x0e) {
        case 0x00:  // Mode 0
        case 0x04:  // Mode 2
        case 0x0c:
        case 0x06:  // Mode 3
        case 0x0e:
          timer_0_next = Number.MAX_VALUE;
          break;
        }
        break;
      case 0x80:  // タイマー 2
        timer_2_control = al;
        timer_2_o_lh = timer_2_i_lh = false;
        timer_2_latch = -1;
        switch(al & 0x0e) {
        case 0x00:  // Mode 0
        case 0x04:  // Mode 2
        case 0x0c:
        case 0x06:  // Mode 3
        case 0x0e:
          timer_2_org = Number.MAX_VALUE;
          break;
        }
        break;
      }
    }
    else {  // ラッチ
      switch(al & 0xc0) {
      case 0x00:  // タイマー 0
        if(timer_0_latch < 0)
          timer_0_latch = get_timer_0_count();
        break;
      case 0x80:  // タイマー 2
        if(timer_2_latch < 0)
          timer_2_latch = get_timer_2_count();
        break;
      }
    }
    break;

  case O_PARALLEL_B:
    if((io_parallel_b ^ al) & 0x03) {  // タイマー 2/スピーカーの状態が変わった
      if(al & 0x01) {  // ゲート オン
        if(!(io_parallel_b & 0x01)) {  // ゲート オフだった
          if(timer_2_org < Number.MAX_VALUE)
            timer_2_org += timer_now - timer_2_gate_off;
        }
      }
      else {
        if(io_parallel_b & 0x01) {  // ゲート オンだった
          if(timer_2_org < Number.MAX_VALUE)
            timer_2_gate_off = timer_now;
        }
      }

      switch(al & 0x03) {
      case 0x03:  // ゲート オン,イネーブル
        if(sound_state == 2) {  // スピーカー直接出力中
          spk_out.onaudioprocess = null;
          spk_out_gain.disconnect();
        }
        if((timer_2_control & 0x06) == 0x06) {  // Mode 3
          if(timer_2_org < Number.MAX_VALUE) {
            // ビープ音発生
            beep_osc_gain.connect(aud_ctx.destination);
            sound_state = 1;
            break;
          }
        }
        sound_state = 0;
        break;

      case 0x01:  // ゲート オン,ディスエーブル
        switch(sound_state) {
        case 1:  // ビープ音発生中
          beep_osc_gain.disconnect();
          break;
        case 2:  // スピーカー直接出力中
          spk_out.onaudioprocess = null;
          spk_out_gain.disconnect();
          break;
        }
        sound_state = 0;
        break;

      default:  // ゲート オフ
        if(sound_state == 2) {  // スピーカー直接出力中
          spk_out_buff[spk_out_i_put] = timer_now - spk_out_put_prev;
          spk_out_i_put = (spk_out_i_put + 1) & 0x7ff;
          if(spk_out_i_put == spk_out_i_get)
            spk_out_i_get++;
          spk_out_put_prev = timer_now;
        }
        else {
          if(sound_state == 1)  // ビープ音発生中
            beep_osc_gain.disconnect();
          if(io_parallel_b & 0x01) {  // 最初のゲート オフ出力
            // ビープを停止しただけの場合もあるので,まだ直接出力は開始しない
            sound_state = 0;
          }
          else {
            // スピーカー直接出力開始
            spk_out_i_put = spk_out_i_get = 0;
            spk_out_put_prev = spk_out_get_prev = spk_out_time = timer_now;
            spk_out_val = (al & 0x02) ? 1 : 0;
            spk_out_gain.connect(aud_ctx.destination);
            spk_out.onaudioprocess = spk_out_dummy_proc;
            sound_state = 2;
          }
        }
      }
    }

    io_parallel_b = al;
    break;

  case O_MDA_INDEX:
    if(video_mode == 7)
      video_crtc_index = al;
    break;

  case O_MDA_DATA:
    if(video_mode == 7) {
      switch(video_crtc_index) {
      case 10:  // Cursor Start
        video_cursor_mode = al & 0x60;
        video_cursor_start = al & 0x1f;
        break;
      case 11:  // Cursor End
        video_cursor_end = al & 0x1f;
        break;
      case 12:  // Start Address (H)
        video_start_addr = video_start_addr & 0xff | ((al & 0x3f) << 8);
        break;
      case 13:  // Start Address (L)
        video_start_addr = video_start_addr & 0x3f00 | al;
        break;
      case 14:  // Cursor (H)
        video_cursor_addr = video_cursor_addr & 0xff | ((al & 0x3f) << 8);
        break;
      case 15:  // Cursor (L)
        video_cursor_addr = video_cursor_addr & 0x3f00 | al;
        break;
      }
    }
    break;

  case O_MDA_MODE:
    if(video_mode == 7) {
      if(video_disabled = !(al & 0x08)) {  // 非表示
        ctx_scr.fillRect(0, 0, elem_scr.width, elem_scr.height);
        video_refresh_req1 = video_refresh_req2 = false;
        break;
      }

      video_blink = ((al & 0x20) != 0);
    }
    break;

  case O_CGA_INDEX:
    if(video_mode != 7)
      video_crtc_index = al;
    break;

  case O_CGA_DATA:
    if(video_mode != 7) {
      switch(video_crtc_index) {
      case 6:  // Vertical Displayed
        video_vertical_disp = al & 0x7f;
        if(video_adapter_mode <= 3)
          text_work((video_adapter_mode < 2) ? 320 : 640);
        break;
      case 9:  // Max Scan Line Address
        video_scan_lines = (al & 0x1f) + 1;
        if(video_adapter_mode <= 3)
          text_work((video_adapter_mode < 2) ? 320 : 640);
        break;
      case 10:  // Cursor Start
        video_cursor_mode = al & 0x60;
        video_cursor_start = al & 0x1f;
        break;
      case 11:  // Cursor End
        video_cursor_end = al & 0x1f;
        break;
      case 12:  // Start Address (H)
        video_start_addr = video_start_addr & 0xff | ((al & 0x3f) << 8);
        break;
      case 13:  // Start Address (L)
        video_start_addr = video_start_addr & 0x3f00 | al;
        break;
      case 14:  // Cursor (H)
        video_cursor_addr = video_cursor_addr & 0xff | ((al & 0x3f) << 8);
        break;
      case 15:  // Cursor (L)
        video_cursor_addr = video_cursor_addr & 0x3f00 | al;
        break;
      }
    }
    break;

  case O_CGA_MODE:
    if(video_mode != 7) {
      if(video_disabled = !(al & 0x08)) {  // 非表示
        ctx_scr.fillRect(0, 0, elem_scr.width, elem_scr.height);
        video_refresh_req1 = video_refresh_req2 = false;
        break;
      }

      video_blink = ((al & 0x20) != 0);

      var old_mode = video_adapter_mode;
      if(al & 0x02) {  // グラフィック モード
        video_comp_color = !(al & 0x04);
        if(al & 0x10)
          video_adapter_mode = 6;
        else
          video_adapter_mode = (video_comp_color) ? 4 : 5;
      }
      else {  // テキスト モード
        video_adapter_mode = ((al & 0x01) << 1) | ((~al & 0x04) >> 2);
        video_comp_color = false;
      }

      set_palette();

      if(video_adapter_mode == old_mode)
        break;

      switch(video_adapter_mode) {
      case 0:
      case 1:
        elem_scr.width = width320;
        elem_scr.height = height200;
        elem_scr.style.width = String(width320 << 1) + "px";
        elem_scr.style.height = String(height200 << 1) + "px";
        text_work(320);
        break;
      case 2:
      case 3:
        elem_scr.width = width640;
        elem_scr.height = height200;
        elem_scr.style.width = String(width640) + "px";
        elem_scr.style.height = String(height200 << 1) + "px";
        text_work(640);
        break;
      case 4:
      case 5:
        elem_scr.width = width320;
        elem_scr.height = height200;
        elem_scr.style.width = String(width320 << 1) + "px";
        elem_scr.style.height = String(height200 << 1) + "px";
        break;
      default:  // 6
        elem_scr.width = width640;
        elem_scr.height = height200;
        elem_scr.style.width = String(width640) + "px";
        elem_scr.style.height = String(height200 << 1) + "px";
      }

      ctx_scr.fillRect(0, 0, elem_scr.width, elem_scr.height);
      video_data = ctx_scr.getImageData(0, 0, elem_scr.width, elem_scr.height);
      video_buff = new Uint32Array(video_data.data.buffer);
    }
    break;

  case O_CGA_COLOR:
    if(video_mode != 7) {
      video_cga_palette[0] = video_cga_color[al & 0xf];
      video_comp_palette_phase_0 = video_comp_phase[al & 0x7];
      video_comp_palette_int[0] = al & 0x8;

      video_palette = ((al & 0x20) != 0);
      video_bright = ((al & 0x10) != 0);

      video_comp_palette_int[1] = video_comp_palette_int[2] = video_comp_palette_int[3]
        = (video_bright) ? 0x8 : 0x0;

      set_palette();

      switch(video_adapter_mode) {
      case 4:
      case 5:
      case 6:
        video_color_chg = video_line_cnt;
        break;
      }
    }
    break;

  case O_CGA_LP_CLEAR:
    if(video_mode != 7)
      video_lp_trigger = 0x0;
    break;

  case O_CGA_LP_PRESET:
    if(video_mode != 7) {
      if(!video_lp_trigger) {
        var i_line = ((video_line_cnt > 240) ? 502 : 240) - video_line_cnt;
        var ma_c;
        switch(video_adapter_mode) {
        case 2:
        case 3:
          if((ma_c = 114 - Math.floor((video_line_next - timer_now) / 0.0005587302296800291)) > 113) {
            if(++i_line == 262)
              i_line = 0;
            ma_c = 0;
          }
          video_lp_latch = video_start_addr + ((i_line >> 3) * 80) + ma_c;
          break;
        default:
          if((ma_c = 57 - Math.floor((video_line_next - timer_now) / 0.0011174604593600583)) > 56) {
            if(++i_line == 262)
              i_line = 0;
            ma_c = 0;
          }
          video_lp_latch = video_start_addr + ((i_line >> ((video_adapter_mode < 4) ? 3 : 1)) * 40) + ma_c;
        }

        video_lp_trigger = 0x2;
      }
    }
    break;
  }

  function text_work(width) {
    var lines = video_vertical_disp * video_scan_lines;
    if(lines > 200)
      lines = 200 - 200 % video_scan_lines;
    video_i_buff_end = lines * width;
    video_i_buff_adj = (video_scan_lines - 1) * width;
  }

  function set_palette() {
    if(video_comp_color)
      video_comp_palette_phase[0] = video_comp_palette_phase_0;
    else
      video_comp_palette_phase[0] = (video_comp_palette_phase_0) ? 0xff : 0x00;

    if(video_adapter_mode == 5) {
      if(video_bright) {
        video_cga_palette[1] = video_cga_lcyan;
        video_cga_palette[2] = video_cga_lred;
        video_cga_palette[3] = video_cga_white;
      }
      else {
        video_cga_palette[1] = video_cga_cyan;
        video_cga_palette[2] = video_cga_red;
        video_cga_palette[3] = video_cga_lgray;
      }
      video_comp_palette_phase[1] = video_comp_palette_phase[2] = video_comp_palette_phase[3] = 0xff;
    }
    else {
      if(video_palette) {
        if(video_bright) {
          video_cga_palette[1] = video_cga_lcyan;
          video_cga_palette[2] = video_cga_lmagenta;
          video_cga_palette[3] = video_cga_white;
        }
        else {
          video_cga_palette[1] = video_cga_cyan;
          video_cga_palette[2] = video_cga_magenta;
          video_cga_palette[3] = video_cga_lgray;
        }
        if(video_comp_color) {
          video_comp_palette_phase[1] = video_comp_phase_cyan;
          video_comp_palette_phase[2] = video_comp_phase_magenta;
        }
        else {
          video_comp_palette_phase[1] = video_comp_palette_phase[2] = 0xff;
        }
        video_comp_palette_phase[3] = 0xff;
      }
      else {
        if(video_bright) {
          video_cga_palette[1] = video_cga_lgreen;
          video_cga_palette[2] = video_cga_lred;
          video_cga_palette[3] = video_cga_yellow;
        }
        else {
          video_cga_palette[1] = video_cga_green;
          video_cga_palette[2] = video_cga_red;
          video_cga_palette[3] = video_cga_brown;
        }
        if(video_comp_color) {
          video_comp_palette_phase[1] = video_comp_phase_green;
          video_comp_palette_phase[2] = video_comp_phase_red;
          video_comp_palette_phase[3] = video_comp_phase_yellow;
        }
        else {
          video_comp_palette_phase[1] = video_comp_palette_phase[2] = video_comp_palette_phase[3] = 0xff;
        }
      }
    }
  }
}

// 2 バイト出力
function out_2(port) {
//var ax = get_reg2(I_AX);

  switch(port) {
  // 1 バイトの I/O を行うべきところを 2 バイトの I/O を行っている
  // プログラムがあるので,2 バイト I/O にも対応しておく.
  case O_PIC_M_COMMAND_0:
  case O_PIC_M_COMMAND_1:
  case O_MDA_INDEX:
  case O_MDA_DATA:
  case O_MDA_MODE:
  case O_CGA_INDEX:
  case O_CGA_DATA:
  case O_CGA_MODE:
    out_1(port);
    return;
  }
}

// タイマー 0 カウント取得
function get_timer_0_count() {
  if(timer_0_next == Number.MAX_VALUE)
    return 0;

  var count = Math.ceil((timer_0_next - timer_now) / OSC_INT);
  if(count >= timer_0_count) {
    if(timer_0_count == 0x10000)
      return 0;
    return timer_0_count;
  }
  if(count <= 0)
    return 0;
  return count;
}

// タイマー 2 カウント取得
// タイマー 2 を CRT との同期に使うことはまず無いだろうと思うが,
// 一応タイマー 0 と同様の計算方法にしておく.
function get_timer_2_count() {
  if(timer_2_org == Number.MAX_VALUE)
    return 0;

  if(!(io_parallel_b & 0x01)) {  // ゲート オフ
    timer_2_org += timer_now - timer_2_gate_off;
    timer_2_gate_off = timer_now;
  }
  if(timer_2_new_count) {
    var next = timer_2_org + timer_2_span;
    if(timer_now >= next) {
      timer_2_org = next;
      timer_2_count = timer_2_new_count;
      timer_2_new_count = 0;
    }
  }

  var n = Math.floor((timer_now - timer_2_org) / (timer_2_count * OSC_INT));
  if(n) {
    if(!(timer_2_control & 0x04)) {  // Mode 2,3 以外
      timer_2_org = Number.MAX_VALUE;
      return 0;
    }
  }
  var count = timer_2_count - Math.floor((timer_now - (timer_2_org + (n * timer_2_count) * OSC_INT)) / OSC_INT);
  if(count >= timer_2_count) {
    if(timer_2_count == 0x10000)
      return 0;
    return timer_2_count;
  }
  if(count <= 0)
    return 0;
  return count;
}

//-->
</SCRIPT>

</BODY>

</HTML>