//========================================================================================
//  SRAM.java
//    en:SRAM
//    ja:SRAM
//  Copyright (C) 2003-2025 Makoto Kamada
//
//  This file is part of the XEiJ (X68000 Emulator in Java).
//  You can use, modify and redistribute the XEiJ if the conditions are met.
//  Read the XEiJ License for more details.
//  https://stdkmd.net/xeij/
//========================================================================================

package xeij;

import java.awt.event.*;  //ActionEvent,ActionListener
import java.io.*;  //File
import java.util.*;  //Arrays
import javax.swing.*;  //JMenu,JRadioButtonMenuItem

public class SRAM {

  public static int smrSramSizeRequest;  //SRAMのサイズ。16384,32768,65536のいずれか
  public static int smrSramCurrentSize;  //現在のSRAMのサイズ。16384,32768,65536のいずれか

  public static String smrSramName;  //SRAMイメージファイル名

  public static int smrBootDevice;  //起動デバイス。0x00ed0018。0x0000=STD,0x9070～0x9370=FDn,0x8000～0x8f00=HDn,0xa000=ROM,0xb000=RAM,-1=設定しない
  public static int smrROMBootHandle;  //ROM起動ハンドル。0x00ed000c。-1=設定しない
  public static int smrRAMBootAddress;  //RAM起動アドレス。0x00ed0010。-1=設定しない

  public static int smrRepeatDelay;  //リピート開始。0x00ed003a。-1=設定しない,0..15=200+100*n(ms)
  public static int smrRepeatInterval;  //リピート間隔。0x00ed003b。-1=設定しない,0..15=30+5*n^2(ms)
  public static boolean smrWriteEnableOn;  //true=SRAM書き込み可

  //-romdb
  public static int smrRomdbFlag;  //ROMデバッガ起動フラグ。0x00ed0058。-1=設定しない,0=OFF,255=AUX,1=CON(IPLROM 1.6のみ)

  //-setmemorysize
  public static boolean smrModifyMemorySizeOn;  //SRAMにあるメモリサイズを修正する

  //-srambuserror
  public static boolean smrSRAMBusErrorOn;  //SRAMへの書き込み時のバスエラー

  public static JMenu smrRepeatDelayMenu;  //リピート開始メニュー
  public static JMenu smrRepeatIntervalMenu;  //リピート間隔メニュー
  public static JMenu smrRomdbMenu;  //ROMデバッガ起動フラグメニュー
  public static JMenuItem smrModifyMemorySizeMenuItem;  //メモリサイズ修正
  public static JMenu smrMenu;  //SRAMメニュー
  public static JMenu smrBootMenu;  //起動デバイスメニュー
  public static JRadioButtonMenuItem smrSTDMenuItem;

  //smrInit ()
  //  SRAMを初期化する
  public static void smrInit () {

    //-sramsize
    int sizeKB = Settings.sgsGetInt ("sramsize");  //SRAMのサイズ
    smrSramSizeRequest = (sizeKB == 16 ||
                          sizeKB == 32 ||
                          sizeKB == 64 ? sizeKB << 10 : 16 << 10);
    smrSramCurrentSize = smrSramSizeRequest;
    System.out.printf (Multilingual.mlnJapanese ?
                       "SRAM のサイズは %dKB です\n" :
                       "SRAM size is %dKB\n",
                       smrSramSizeRequest >> 10);

    boolean initialized = false;

    //-sram
    smrSramName = "";
    if (!initialized) {
      smrSramName = Settings.sgsGetString ("sram");  //SRAMイメージファイル名
      if (smrSramName.length () != 0) {
        byte[] array = XEiJ.rscGetFile (smrSramName, smrSramSizeRequest);
        if (array != null) {
          System.arraycopy (array, 0, MainMemory.mmrM8, 0x00ed0000, smrSramSizeRequest);
          if (smrSramSizeRequest < 64 << 10) {
            Arrays.fill (MainMemory.mmrM8, 0x00ed0000 + smrSramSizeRequest, 0x00ed0000 + (64 << 10), (byte) 0);  //範囲外をゼロクリアする
          }
          initialized = true;
        }
      }
    }

    //-sramdata
    //  マジックの有無に関係なく復元する
    if (!initialized) {
      byte[] array = Settings.sgsGetData ("sramdata");  //SRAMの内容(gzip+base64)
      if (array.length != 0) {  //復元するデータがある
        System.out.println (Multilingual.mlnJapanese ?
                            "SRAM のデータを復元します" :
                            "SRAM data is restored");
        System.arraycopy (array, 0, MainMemory.mmrM8, 0x00ed0000, Math.min (array.length, smrSramSizeRequest));
        if (array.length < smrSramSizeRequest) {
          Arrays.fill (MainMemory.mmrM8, 0x00ed0000 + array.length, 0x00ed0000 + smrSramSizeRequest, (byte) 0);  //復元されなかった部分をゼロクリアする
        }
        if (smrSramSizeRequest < 64 << 10) {
          Arrays.fill (MainMemory.mmrM8, 0x00ed0000 + smrSramSizeRequest, 0x00ed0000 + (64 << 10), (byte) 0);  //範囲外をゼロクリアする
        }
        initialized = true;
      }
    }

    if (!initialized) {
      System.out.println (Multilingual.mlnJapanese ?
                          "SRAM をゼロクリアします" :
                          "SRAM is zero-cleared");
      Arrays.fill (MainMemory.mmrM8, 0x00ed0000, 0x00ed0000 + (64 << 10), (byte) 0);  //ゼロクリアする
      initialized = true;
    }

    //-keydly
    int repeatDelay = Settings.sgsGetInt ("keydly");
    smrRepeatDelay = -1 <= repeatDelay && repeatDelay <= 15 ? repeatDelay : -1;

    //-keyrep
    int repeatInterval = Settings.sgsGetInt ("keyrep");
    smrRepeatInterval = -1 <= repeatInterval && repeatInterval <= 15 ? repeatInterval : -1;

    //-romdb
    {
      String s = Settings.sgsGetString ("romdb").toLowerCase ();
      smrRomdbFlag = (s.equals ("off") ? 0 :
                      s.equals ("on") || s.equals ("aux") ? 255 :
                      s.equals ("con") ? 1 :
                      -1);
    }

    //-modifymemorysize
    smrModifyMemorySizeOn = Settings.sgsGetOnOff ("modifymemorysize");

    //-srambuserror
    smrSRAMBusErrorOn = Settings.sgsGetOnOff ("srambuserror");

    //-boot
    smrParseBootDevice (Settings.sgsGetString ("boot"));

  }  //smrInit

  //smrTini ()
  //  SRAMの後始末
  public static void smrTini () {

    //-sramsize
    Settings.sgsPutInt ("sramsize", smrSramSizeRequest >> 10);

    //-sram
    Settings.sgsPutString ("sram", smrSramName);

    //-sramdata
    //  マジックの有無に関係なく保存する
    //  常に64KB保存する
    Settings.sgsPutData ("sramdata", MainMemory.mmrM8, 0x00ed0000, 64 << 10);

    //-keydly
    Settings.sgsPutInt ("keydly", smrRepeatDelay);  //リピート開始

    //-keyrep
    Settings.sgsPutInt ("keyrep", smrRepeatInterval);  //リピート間隔

    //-romdb
    Settings.sgsPutString ("romdb",
                           smrRomdbFlag == 0 ? "off" :
                           smrRomdbFlag == 255 ? "aux" :
                           smrRomdbFlag == 1 ? "con" :
                           "");

    //-modifymemorysize
    Settings.sgsPutOnOff ("modifymemorysize", smrModifyMemorySizeOn);

    //-srambuserror
    Settings.sgsPutOnOff ("srambuserror", smrSRAMBusErrorOn);

    //-boot
    Settings.sgsPutString ("boot",
                           smrBootDevice == -1 ? "default" :
                           smrBootDevice == 0x0000 ? "std" :
                           (smrBootDevice & 0xf000) == 0x9000 ? "fd" + (smrBootDevice >> 8 & 3) :
                           (smrBootDevice & 0xf000) == 0x8000 ? "hd" + (smrBootDevice >> 8 & 15) :
                           smrBootDevice == 0xa000 ?
                           (smrROMBootHandle & ~(7 << 2)) == SPC.SPC_HANDLE_EX ? "sc" + (smrROMBootHandle >> 2 & 7) :
                           (smrROMBootHandle & ~(7 << 2)) == SPC.SPC_HANDLE_IN ? "sc" + (smrROMBootHandle >> 2 & 7) :
                           smrROMBootHandle == HFS.HFS_BOOT_HANDLE ? "hf" + HFS.hfsBootUnit :
                           String.format ("rom$%08X", smrROMBootHandle) :
                           smrBootDevice == 0xb000 ? String.format ("ram$%08X", smrRAMBootAddress) :
                           "");

  }

  public static void smrMakeMenu () {

    //アクションリスナー
    ActionListener listener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Object source = ae.getSource ();
        String command = ae.getActionCommand ();
        switch (command) {
          //メモリサイズ修正
        case "Modify the memory size in SRAM":  //SRAM にあるメモリサイズを修正する
          smrModifyMemorySizeOn = ((JCheckBoxMenuItem) source).isSelected ();
          break;
          //SRAMメニュー
        case "Zero-clear":  //SRAM をゼロクリア
          smrClear ();
          break;
        case "Import":  //SRAM をインポート
          smrLoad ();
          break;
        case "Export":  //SRAM をエクスポート
          smrSave ();
          break;
        case "16KB":
          smrSramSizeRequest = 16384;
          break;
        case "32KB":
          smrSramSizeRequest = 32768;
          break;
        case "64KB":
          smrSramSizeRequest = 65536;
          break;
        case "Bus error when writing to SRAM":  //SRAM への書き込み時のバスエラー
          smrSRAMBusErrorOn = ((JCheckBoxMenuItem) source).isSelected ();
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }
    };

    //リピート開始メニュー
    ActionListener delayListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        String command = ae.getActionCommand ();
        switch (command) {
        case "Follow settings in SWITCH.X":  //SWITCH.X の設定に従う
          smrRepeatDelay = -1;
          Keyboard.kbdSetRepeatDelay (MainMemory.mmrRbs (0x00ed003a));
          break;
        case (200 + 100 *  0) + "ms":
        case (200 + 100 *  1) + "ms":
        case (200 + 100 *  2) + "ms":
        case (200 + 100 *  3) + "ms":
        case (200 + 100 *  4) + "ms":
        case (200 + 100 *  5) + "ms":
        case (200 + 100 *  6) + "ms":
        case (200 + 100 *  7) + "ms":
        case (200 + 100 *  8) + "ms":
        case (200 + 100 *  9) + "ms":
        case (200 + 100 * 10) + "ms":
        case (200 + 100 * 11) + "ms":
        case (200 + 100 * 12) + "ms":
        case (200 + 100 * 13) + "ms":
        case (200 + 100 * 14) + "ms":
        case (200 + 100 * 15) + "ms":
          {
            int ms = Integer.parseInt (command.substring (0, command.length () - 2));  //200..1700
            //perl optdiv.pl 1500 100
            //  x/100==x*1311>>>17 (0<=x<=4698) [1500*1311==1966500]
            smrRepeatDelay = (ms - 200) * 1311 >>> 17;  //0..15
            Keyboard.kbdSetRepeatDelay (smrRepeatDelay);
            MainMemory.mmrWb (0x00ed003a, smrRepeatDelay);
          }
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }
    };
    ButtonGroup delayGroup = new ButtonGroup ();
    smrRepeatDelayMenu =
      Multilingual.mlnText (
        ComponentFactory.createMenu (
          "Repeat delay",
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay == -1, "Follow settings in SWITCH.X", delayListener),
            "ja", "SWITCH.X の設定に従う"),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  0, (200 + 100 *  0) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  1, (200 + 100 *  1) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  2, (200 + 100 *  2) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  3, (200 + 100 *  3) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  4, (200 + 100 *  4) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  5, (200 + 100 *  5) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  6, (200 + 100 *  6) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  7, (200 + 100 *  7) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  8, (200 + 100 *  8) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay ==  9, (200 + 100 *  9) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay == 10, (200 + 100 * 10) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay == 11, (200 + 100 * 11) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay == 12, (200 + 100 * 12) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay == 13, (200 + 100 * 13) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay == 14, (200 + 100 * 14) + "ms", delayListener),
          ComponentFactory.createRadioButtonMenuItem (delayGroup, smrRepeatDelay == 15, (200 + 100 * 15) + "ms", delayListener)
          ),
        "ja", "リピート開始");

    //リピート間隔メニュー
    ActionListener intervalListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        String command = ae.getActionCommand ();
        switch (command) {
        case "Follow settings in SWITCH.X":  //SWITCH.X の設定に従う
          smrRepeatInterval =  -1;
          Keyboard.kbdSetRepeatInterval (MainMemory.mmrRbs (0x00ed003b));
          break;
        case (30 + 5 *  0 *  0) + "ms":
        case (30 + 5 *  1 *  1) + "ms":
        case (30 + 5 *  2 *  2) + "ms":
        case (30 + 5 *  3 *  3) + "ms":
        case (30 + 5 *  4 *  4) + "ms":
        case (30 + 5 *  5 *  5) + "ms":
        case (30 + 5 *  6 *  6) + "ms":
        case (30 + 5 *  7 *  7) + "ms":
        case (30 + 5 *  8 *  8) + "ms":
        case (30 + 5 *  9 *  9) + "ms":
        case (30 + 5 * 10 * 10) + "ms":
        case (30 + 5 * 11 * 11) + "ms":
        case (30 + 5 * 12 * 12) + "ms":
        case (30 + 5 * 13 * 13) + "ms":
        case (30 + 5 * 14 * 14) + "ms":
        case (30 + 5 * 15 * 15) + "ms":
          {
            int ms = Integer.parseInt (command.substring (0, command.length () - 2));  //30..1155
            //perl optdiv.pl 1125 5
            //  x/5==x*1639>>>13 (0<=x<=2733) [1125*1639==1843875]
            smrRepeatInterval = (int) Math.sqrt ((double) ((ms - 30) * 1639 >>> 13));  //0..15
            Keyboard.kbdSetRepeatInterval (smrRepeatInterval);
            MainMemory.mmrWb (0x00ed003b, smrRepeatInterval);
          }
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }
    };
    ButtonGroup intervalGroup = new ButtonGroup ();
    smrRepeatIntervalMenu =
      Multilingual.mlnText (
        ComponentFactory.createMenu (
          "Repeat interval",
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval == -1, "Follow settings in SWITCH.X", intervalListener),
            "ja", "SWITCH.X の設定に従う"),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  0, (30 + 5 *  0 *  0) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  1, (30 + 5 *  1 *  1) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  2, (30 + 5 *  2 *  2) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  3, (30 + 5 *  3 *  3) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  4, (30 + 5 *  4 *  4) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  5, (30 + 5 *  5 *  5) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  6, (30 + 5 *  6 *  6) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  7, (30 + 5 *  7 *  7) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  8, (30 + 5 *  8 *  8) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval ==  9, (30 + 5 *  9 *  9) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval == 10, (30 + 5 * 10 * 10) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval == 11, (30 + 5 * 11 * 11) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval == 12, (30 + 5 * 12 * 12) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval == 13, (30 + 5 * 13 * 13) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval == 14, (30 + 5 * 14 * 14) + "ms", intervalListener),
          ComponentFactory.createRadioButtonMenuItem (intervalGroup, smrRepeatInterval == 15, (30 + 5 * 15 * 15) + "ms", intervalListener)
          ),
        "ja", "リピート間隔");

    //ROMデバッガ起動フラグメニュー
    ActionListener romdbListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        String command = ae.getActionCommand ();
        switch (command) {
        case "Follow settings in SWITCH.X":  //SWITCH.X の設定に従う
          smrRomdbFlag = -1;
          break;
        case "OFF":
          smrRomdbFlag = 0;
          break;
        case "AUX":
          smrRomdbFlag = 255;
          break;
        case "CON (when using IPLROM 1.6)":  //CON (IPLROM 1.6 使用時)
          smrRomdbFlag = 1;
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }
    };
    ButtonGroup romdbGroup = new ButtonGroup ();
    smrRomdbMenu =
      Multilingual.mlnText (
        ComponentFactory.createMenu (
          "ROM debugger start flag",
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (romdbGroup, smrRomdbFlag == -1, "Follow settings in SWITCH.X", romdbListener),
            "ja", "SWITCH.X の設定に従う"),
          ComponentFactory.createRadioButtonMenuItem (romdbGroup, smrRomdbFlag == 0, "OFF", romdbListener),
          ComponentFactory.createRadioButtonMenuItem (romdbGroup, smrRomdbFlag == 255, "AUX", romdbListener),
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (romdbGroup, smrRomdbFlag == 1, "CON (when using IPLROM 1.6)", romdbListener),
            "ja", "CON (IPLROM 1.6 使用時)")
          ),
        "ja", "ROM デバッガ起動フラグ");

    //メモリサイズ修正
    smrModifyMemorySizeMenuItem =
      Multilingual.mlnText (
        ComponentFactory.createCheckBoxMenuItem (smrModifyMemorySizeOn, "Modify the memory size in SRAM", listener),
        "ja", "SRAM にあるメモリサイズを修正する");

    //SRAMメニュー
    ButtonGroup sizeGroup = new ButtonGroup ();
    smrMenu =
      ComponentFactory.createMenu (
        "SRAM",
        Multilingual.mlnText (
          ComponentFactory.createMenuItem ("Zero-clear", listener),
          "ja", "ゼロクリア"),
        Multilingual.mlnText (
          ComponentFactory.createMenuItem ("Import", listener),
          "ja", "インポート"),
        Multilingual.mlnText (
          ComponentFactory.createMenuItem ("Export", listener),
          "ja", "エクスポート"),
        ComponentFactory.createHorizontalSeparator (),
        Multilingual.mlnText (
          ComponentFactory.createRadioButtonMenuItem (sizeGroup, smrSramSizeRequest >> 10 == 16, "16KB", listener),
          "ja", "16KB"),
        Multilingual.mlnText (
          ComponentFactory.createRadioButtonMenuItem (sizeGroup, smrSramSizeRequest >> 10 == 32, "32KB", listener),
          "ja", "32KB"),
        Multilingual.mlnText (
          ComponentFactory.createRadioButtonMenuItem (sizeGroup, smrSramSizeRequest >> 10 == 64, "64KB", listener),
          "ja", "64KB"),
        ComponentFactory.createHorizontalSeparator (),
        Multilingual.mlnText (
          ComponentFactory.createCheckBoxMenuItem (smrSRAMBusErrorOn, "Bus error when writing to SRAM", listener),
          "ja", "SRAM への書き込み時のバスエラー")
        );

    //起動デバイスメニュー
    ButtonGroup bootGroup = new ButtonGroup ();
    ActionListener bootListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        String command = ae.getActionCommand ();
        smrParseBootDevice (
          command.startsWith ("FDD ") ? "fd" + command.substring (4) :
          command.startsWith ("SASI ") ? "hd" + command.substring (5) :
          command.startsWith ("SCSI ") ? "sc" + command.substring (5) :
          command.startsWith ("HFS ") ? "hf" + command.substring (4) :
          command.equals ("STD") ? "std" :
          "default");
        if (smrBootDevice != -1) {  //メニューで起動デバイスが既定以外に設定されたとき
          XEiJ.mpuSavedBootDevice = -1;  //保存されている起動デバイスを消す
          XEiJ.mpuSavedROMBootHandle = -1;
        }
      }
    };
    JMenu bootMenuFDD = ComponentFactory.createMenu ("FDD");
    for (int u = 0; u < FDC.FDC_MAX_UNITS; u++) {
      bootMenuFDD.add (ComponentFactory.createRadioButtonMenuItem (
        bootGroup, smrBootDevice == 0x9070 + (u << 8),
        "FDD " + u, bootListener));
    }
    JMenu bootMenuSASI = ComponentFactory.createMenu ("SASI");
    for (int u = 0; u < 16; u++) {
      bootMenuSASI.add (ComponentFactory.createRadioButtonMenuItem (
        bootGroup, smrBootDevice == 0x8000 + (u << 8),
        "SASI " + u, bootListener));
    }
    JMenu bootMenuSCSI = ComponentFactory.createMenu ("SCSI");
    for (int u = 0; u < 8; u++) {
      bootMenuSCSI.add (ComponentFactory.createRadioButtonMenuItem (
        bootGroup, smrBootDevice == 0xa000 && (smrROMBootHandle == SPC.SPC_HANDLE_EX + (u << 2) ||
                                               smrROMBootHandle == SPC.SPC_HANDLE_IN + (u << 2)), "SCSI " + u, bootListener));
    }
    JMenu bootMenuHFS = ComponentFactory.createMenu ("HFS");
    for (int u = 0; u < HFS.HFS_MAX_UNITS; u++) {
      bootMenuHFS.add (ComponentFactory.createRadioButtonMenuItem (
        bootGroup, smrBootDevice == 0xa000 && smrROMBootHandle == HFS.HFS_BOOT_HANDLE && HFS.hfsBootUnit == u,
        "HFS " + u, bootListener));
    }
    smrBootMenu =
      Multilingual.mlnText (
        ComponentFactory.createMenu (
          "Boot device",
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (bootGroup, smrBootDevice == -1, "Follow settings in SWITCH.X", bootListener),
            "ja", "SWITCH.X の設定に従う"),
          smrSTDMenuItem = ComponentFactory.createRadioButtonMenuItem (bootGroup, smrBootDevice == 0x0000, "STD", bootListener),
          bootMenuFDD,
          bootMenuSASI,
          bootMenuSCSI,
          bootMenuHFS
          ),
        "ja", "起動デバイス");

  }  //smrInit()

  //smrParseBootDevice ()
  //  起動デバイスの設定を読み取る
  public static void smrParseBootDevice (String boot) {
    smrBootDevice = -1;  //起動デバイス
    smrROMBootHandle = -1;  //ROM起動ハンドル
    smrRAMBootAddress = -1;  //RAM起動アドレス
    boot = boot.toLowerCase ();
    if (boot.equals ("std")) {  //std
      smrBootDevice = 0x0000;  //STD 起動
    } else if (boot.startsWith ("fd")) {  //fdN
      int u = XEiJ.fmtParseInt (boot, 2, 0, FDC.FDC_MAX_UNITS - 1, FDC.FDC_MAX_UNITS);  //起動ユニット番号
      if (u < FDC.FDC_MAX_UNITS) {
        smrBootDevice = 0x9070 + (u << 8);  //FDD起動
      }
    } else if (boot.startsWith ("hd")) {  //hdN
      int u = XEiJ.fmtParseInt (boot, 2, 0, 15, 16);  //起動ユニット番号
      if (u < 16) {
        smrBootDevice = 0x8000 + (u << 8);  //SASI起動
      }
    } else if (boot.startsWith ("sc")) {  //scN
      int u = XEiJ.fmtParseInt (boot, 2, 0, 7, 8);
      if (u < 8) {
        smrBootDevice = 0xa000;  //ROM起動
        smrROMBootHandle = SPC.SPC_HANDLE_EX + ((u & 7) << 2);  //仮に拡張SCSI起動にしておく。リセットしたとき拡張SCSIがなければ内蔵SCSIに読み替えられる
      }
    } else if (boot.startsWith ("hf")) {  //hfN
      int u = XEiJ.fmtParseInt (boot, 2, 0, HFS.HFS_MAX_UNITS - 1, HFS.HFS_MAX_UNITS);  //起動ユニット番号
      if (u < HFS.HFS_MAX_UNITS) {
        HFS.hfsBootUnit = u;
        smrBootDevice = 0xa000;  //ROM起動
        smrROMBootHandle = HFS.HFS_BOOT_HANDLE;  //IPL起動ハンドル
      }
    } else if (boot.startsWith ("rom$")) {  //rom$X
      int handle = XEiJ.fmtParseIntRadix (boot, 3, 0, 0x00ffffff, 0x01000000, 16);  //起動ハンドル
      if (handle < 0x01000000) {
        smrBootDevice = 0xa000;  //ROM起動
        smrROMBootHandle = handle;
      }
    } else if (boot.startsWith ("ram$")) {  //ram$X
      int handle = XEiJ.fmtParseIntRadix (boot, 3, 0, 0x00ffffff, 0x01000000, 16);  //起動ハンドル
      if (handle < 0x01000000) {
        smrBootDevice = 0xb000;  //RAM起動
        smrRAMBootAddress = handle;
      }
    }
  }  //smrParseBootDevice(String)

  //smrReset ()
  //  SRAMリセット
  //  ここでROMも上書きする
  //  ROMを初期化してから呼び出すこと
  //  SPC.spcReset()よりも後であること
  public static void smrReset () {
    smrWriteEnableOn = false;

    //SRAMの容量を変更する
    {
      smrSramCurrentSize = smrSramSizeRequest;
      XEiJ.busSuper (MemoryMappedDevice.MMD_SMR, 0x00ed0000, 0x00ed0000 + smrSramSizeRequest);  //SMR SRAM
      if (smrSramSizeRequest < 65536) {
        XEiJ.busSuper (MemoryMappedDevice.MMD_NUL, 0x00ed0000 + smrSramSizeRequest, 0x00ed0000 + 65536);  //空き
      }
      System.out.printf (Multilingual.mlnJapanese ?
                         "SRAM の容量は %dKB ($%08X-$%08X) です\n" :
                         "Capacity of SRAM is %dKB ($%08X-$%08X)\n",
                         smrSramCurrentSize >> 10, 0x00ed0000, 0x00ed0000 + smrSramCurrentSize - 1);
    }

    //ROM起動ハンドルを調整する
    //  ROM起動ハンドルが内蔵SCSIを指しているが内蔵SCSIがないとき
    //    拡張SCSIがあるとき
    //      ROM起動ハンドルを拡張SCSIにする
    //    拡張SCSIがないとき
    //      ROM起動ハンドルを消す
    //      ROM起動のとき
    //        STD起動にする
    //  ROM起動ハンドルが拡張SCSIを指しているが拡張SCSIがないとき
    //    内蔵SCSIがあるとき
    //      ROM起動ハンドルを内蔵SCSIにする
    //    内蔵SCSIがないとき
    //      ROM起動ハンドルを消す
    //      ROM起動のとき
    //        STD起動にする
    //  ROM起動ハンドルがHFSを指しているがHFSのディレクトリが設定されていないとき
    //    ROM起動ハンドルを消す
    //    ROM起動のとき
    //      STD起動にする
    //起動デバイス
    if ((smrROMBootHandle & ~(7 << 2)) == SPC.SPC_HANDLE_IN && !SPC.spcSCSIINOn) {  //ROM起動ハンドルが内蔵SCSIを指しているが内蔵SCSIがないとき
      if (SPC.spcSCSIEXOn) {  //拡張SCSIがあるとき
        smrROMBootHandle = SPC.SPC_HANDLE_EX + (smrROMBootHandle & (7 << 2));  //ROM起動ハンドルを拡張SCSIにする
        //MainMemory.mmrWb (0x00ed0070, MainMemory.mmrRbs (0x00ed0070) | 0x08);  //拡張フラグをセットする
      } else {  //拡張SCSIがないとき
        smrROMBootHandle = 0x00e80400;  //ROM起動ハンドルを消す
        if (smrBootDevice == 0xa000) {  //ROM起動のとき
          smrBootDevice = 0x0000;  //STD起動にする
          smrSTDMenuItem.setSelected (true);
        }
      }
    }
    if ((smrROMBootHandle & ~(7 << 2)) == SPC.SPC_HANDLE_EX && !SPC.spcSCSIEXOn) {  //ROM起動ハンドルが拡張SCSIを指しているが拡張SCSIがないとき
      if (SPC.spcSCSIINOn) {  //内蔵SCSIがあるとき
        smrROMBootHandle = SPC.SPC_HANDLE_IN + (smrROMBootHandle & (7 << 2));  //ROM起動ハンドルを内蔵SCSIにする
        //MainMemory.mmrWb (0x00ed0070, MainMemory.mmrRbs (0x00ed0070) & ~0x08);  //拡張フラグをクリアする
      } else {  //内蔵SCSIがないとき
        smrROMBootHandle = 0x00e80400;  //ROM起動ハンドルを消す
        if (smrBootDevice == 0xa000) {  //ROM起動のとき
          smrBootDevice = 0x0000;  //STD起動にする
          smrSTDMenuItem.setSelected (true);
        }
      }
    }
    if (smrROMBootHandle == HFS.HFS_BOOT_HANDLE &&  //ROM起動ハンドルがHFSを指しているが
        (!HFS.hfsUnitArray[HFS.hfsBootUnit].abuConnected ||
         !HFS.hfsUnitArray[HFS.hfsBootUnit].abuInserted)) {  //HFSのディレクトリが設定されていないとき
      smrROMBootHandle = 0x00e80400;  //ROM起動ハンドルを消す
      if (smrBootDevice == 0xa000) {  //ROM起動のとき
        smrBootDevice = 0x0000;  //STD起動にする
        smrSTDMenuItem.setSelected (true);
      }
    }
    //ここから再起動
    if ((XEiJ.mpuROMBootHandle & ~(7 << 2)) == SPC.SPC_HANDLE_IN && !SPC.spcSCSIINOn) {  //ROM起動ハンドルが内蔵SCSIを指しているが内蔵SCSIがないとき
      if (SPC.spcSCSIEXOn) {  //拡張SCSIがあるとき
        XEiJ.mpuROMBootHandle = SPC.SPC_HANDLE_EX + (XEiJ.mpuROMBootHandle & (7 << 2));  //ROM起動ハンドルを拡張SCSIにする
        //MainMemory.mmrWb (0x00ed0070, MainMemory.mmrRbs (0x00ed0070) | 0x08);  //拡張フラグをセットする
      } else {  //拡張SCSIがないとき
        XEiJ.mpuROMBootHandle = 0x00e80400;  //ROM起動ハンドルを消す
        if (XEiJ.mpuBootDevice == 0xa000) {  //ROM起動のとき
          XEiJ.mpuBootDevice = 0x0000;  //STD起動にする
        }
      }
    }
    if ((XEiJ.mpuROMBootHandle & ~(7 << 2)) == SPC.SPC_HANDLE_EX && !SPC.spcSCSIEXOn) {  //ROM起動ハンドルが拡張SCSIを指しているが拡張SCSIがないとき
      if (SPC.spcSCSIINOn) {  //内蔵SCSIがあるとき
        XEiJ.mpuROMBootHandle = SPC.SPC_HANDLE_IN + (XEiJ.mpuROMBootHandle & (7 << 2));  //ROM起動ハンドルを内蔵SCSIにする
        //MainMemory.mmrWb (0x00ed0070, MainMemory.mmrRbs (0x00ed0070) & ~0x08);  //拡張フラグをクリアする
      } else {  //内蔵SCSIがないとき
        XEiJ.mpuROMBootHandle = 0x00e80400;  //ROM起動ハンドルを消す
        if (XEiJ.mpuBootDevice == 0xa000) {  //ROM起動のとき
          XEiJ.mpuBootDevice = 0x0000;  //STD起動にする
        }
      }
    }
    if (XEiJ.mpuROMBootHandle == HFS.HFS_BOOT_HANDLE &&  //ROM起動ハンドルがHFSを指しているが
        (!HFS.hfsUnitArray[HFS.hfsBootUnit].abuConnected ||
         !HFS.hfsUnitArray[HFS.hfsBootUnit].abuInserted)) {  //HFSのディレクトリが設定されていないとき
      XEiJ.mpuROMBootHandle = 0x00e80400;  //ROM起動ハンドルを消す
      if (XEiJ.mpuBootDevice == 0xa000) {  //ROM起動のとき
        XEiJ.mpuBootDevice = 0x0000;  //STD起動にする
      }
    }

    smrOverride ();

  }  //smrReset()

  //smrOverride ()
  //  SRAMの設定を上書きする
  //  ここから再起動もSRAMに上書きする
  public static void smrOverride () {
    if (MainMemory.mmrRls (0x00ed0000) == 0x82773638 &&  //Ｘ68
        MainMemory.mmrRls (0x00ed0004) == 0x30303057) {  //000W  初期化されている。初期化されていないときに上書きしても意味がない
      //メモリサイズ
      if (smrModifyMemorySizeOn) {
        int memorySizeAddress = 0x00ed0008;
        int memorySizeOld = MainMemory.mmrRls (memorySizeAddress);
        int memorySizeNew = MainMemory.mmrMemorySizeCurrent;
        if (memorySizeOld != memorySizeNew) {
          MainMemory.mmrWl (memorySizeAddress, memorySizeNew);
          System.out.printf (Multilingual.mlnJapanese ?
                             "SRAM にあるメモリサイズを %dMB から %dMB に変更しました\n" :
                             "Changed the memory size in SRAM from %dMB to %dMB\n",
                             memorySizeOld >> 20,
                             memorySizeNew >> 20);
        }
      }
      //ROM起動ハンドル
      int romHandleAddress = 0x00ed000c;
      int romHandleOld = MainMemory.mmrRls (romHandleAddress);
      int romHandleNew = XEiJ.mpuROMBootHandle != -1 ? XEiJ.mpuROMBootHandle : smrROMBootHandle;
      if (romHandleNew != -1 &&
          romHandleOld != romHandleNew) {
        MainMemory.mmrWl (romHandleAddress, romHandleNew);
        System.out.printf (Multilingual.mlnJapanese ?
                           "SRAM にある ROM 起動ハンドルを $%08X から $%08X に変更しました\n" :
                           "Changed the ROM boot handle in SRAM from $%08X to $%08X\n",
                           romHandleOld,
                           romHandleNew);
      }
      //RAM起動アドレス
      int ramAddressAddress = 0x00ed0010;
      int ramAddressOld = MainMemory.mmrRls (ramAddressAddress);
      int ramAddressNew = smrRAMBootAddress;
      if (ramAddressNew != -1 &&
          ramAddressOld != ramAddressNew) {
        MainMemory.mmrWl (ramAddressAddress, ramAddressNew);
        System.out.printf (Multilingual.mlnJapanese ?
                           "SRAM にある RAM 起動アドレスを $%08X から $%08X に変更しました\n" :
                           "Changed the RAM boot address in SRAM from $%08X to $%08X\n",
                           ramAddressOld,
                           ramAddressNew);
      }
      //起動デバイス
      int deviceAddress = 0x00ed0018;
      int deviceOld = MainMemory.mmrRwz (deviceAddress);
      int deviceNew = XEiJ.mpuBootDevice != -1 ? XEiJ.mpuBootDevice : smrBootDevice;
      if (deviceNew != -1 &&
          deviceOld != deviceNew) {
        MainMemory.mmrWw (deviceAddress, deviceNew);
        System.out.printf (Multilingual.mlnJapanese ?
                           "SRAM にある起動デバイスを %s から %s に変更しました\n" :
                           "Changed the boot device in SRAM from %s to %s\n",
                           smrBootDescription (romHandleOld, ramAddressOld, deviceOld),
                           smrBootDescription (romHandleNew, ramAddressNew, deviceNew));
      }
      //リピート開始
      int repeatDelayAddress = 0x00ed003a;
      int repeatDelayOld = MainMemory.mmrRbz (repeatDelayAddress);
      int repeatDelayNew = smrRepeatDelay;
      if (repeatDelayNew != -1 &&
          repeatDelayOld != repeatDelayNew) {
        MainMemory.mmrWb (repeatDelayAddress, repeatDelayNew);
        System.out.printf (Multilingual.mlnJapanese ?
                           "SRAM にあるリピート開始を %dms から %dms に変更しました\n" :
                           "Changed the repeat delay in SRAM from %dms to %dms\n",
                           200 + 100 * (repeatDelayOld & 15),
                           200 + 100 * (repeatDelayNew & 15));
      }
      //リピート間隔
      int repeatIntervalAddress = 0x00ed003b;
      int repeatIntervalOld = MainMemory.mmrRbz (repeatIntervalAddress);
      int repeatIntervalNew = smrRepeatInterval;
      if (repeatIntervalNew != -1 &&
          repeatIntervalOld != repeatIntervalNew) {
        MainMemory.mmrWb (repeatIntervalAddress, repeatIntervalNew);
        System.out.printf (Multilingual.mlnJapanese ?
                           "SRAM にあるリピート間隔を %dms から %dms に変更しました\n" :
                           "Changed the repeat interval in SRAM from %dms to %dms\n",
                           30 + 5 * (repeatDelayOld & 15),
                           30 + 5 * (repeatDelayNew & 15));
      }
      //ROMデバッガ起動フラグ
      if (smrRomdbFlag != -1) {  //設定する
        boolean iplrom16 = (XEiJ.currentAccelerator == XEiJ.ACCELERATOR_HYBRID ||
                            XEiJ.currentAccelerator == XEiJ.ACCELERATOR_060TURBO ||
                            XEiJ.currentAccelerator == XEiJ.ACCELERATOR_060TURBOPRO ||
                            ROM.romIPLROM16On);  //IPLROM 1.6か
        boolean nonexistence = (XEiJ.currentModel.getIPLROM () == 130 && !iplrom16 &&
                                ROM.romROM30Data == null);  //ROMデバッガがないか。IPLROM 1.3でROM30.DATが指定されていない
        int address = 0x00ed0058;
        int oldData = MainMemory.mmrRbz (address);
        int newData = (nonexistence ? 0 :  //ROMデバッガがないときOFF
                       !iplrom16 && smrRomdbFlag == 1 ? 255 :  //IPLROM 1.6でないときCONをAUXに変更
                       smrRomdbFlag);
        if (oldData != newData) {
          MainMemory.mmrWb (address, newData);
          System.out.printf (Multilingual.mlnJapanese ?
                             "SRAM にある ROM デバッガ起動フラグを %s から %s に変更しました\n" :
                             "ROM debugger startup flag in SRAM changed from %s to %s\n",
                             oldData == 0 ? "OFF" : oldData == 255 ? "AUX" : "CON",
                             newData == 0 ? "OFF" : newData == 255 ? "AUX" : "CON");
        }
      }
      //SASIハードディスクの最大数
      int hdMaxAddress = 0x00ed005a;
      int hdMaxOld = MainMemory.mmrRbz (hdMaxAddress);
      int sasiFlag = MainMemory.mmrRbz (0x00ed006f) == 'V' ? MainMemory.mmrRbz (0x00ed0071) : 0;
      int hdMaxNew = SPC.spcSCSIINOn ? 2 * (32 - Integer.numberOfLeadingZeros (sasiFlag)) : HDC.hdcHDMax;
      if (hdMaxOld != hdMaxNew) {
        MainMemory.mmrWb (hdMaxAddress, hdMaxNew);
        System.out.printf (Multilingual.mlnJapanese ?
                           "SRAM にある SASI ハードディスクの最大数を %d から %d に変更しました\n" :
                           "Changed the maximum number of SASI hard disks in SRAM from %d to %d\n",
                           hdMaxOld, hdMaxNew);
      }
    }
    //「ここから再起動」をキャンセルする
    //XEiJ.mpuBootDevice = -1;
    //XEiJ.mpuROMBootHandle = -1;
  }

  //s = smrBootDescription (code, romHandle, ramAddress)
  //  device      $00ED0018  起動デバイス
  //                           $0000  STD  FD→HD→ROM→RAM
  //                           $8xxx  HD
  //                           $9xxx  FD
  //                           $Axxx  ROM  ROM起動ハンドルからROM起動アドレスを取り出して呼び出す
  //                                       ROM起動ハンドルがバスエラーになるときはSTDで起動する
  //                           $Bxxx  RAM  RAM起動アドレスを呼び出す。先頭が$60でなければならない
  //  romHandle   $00ED000C  ROM起動ハンドル
  //  ramAddress  $00ED0010  RAM起動アドレス
  public static String smrBootDescription (int romHandle, int ramAddress, int device) {
    switch (device & 0xf000) {
    case 0x8000:
      return "HD" + (device >> 8 & 15);
    case 0x9000:
      return "2HD" + (device >> 8 & 3);
    case 0xa000:
      return ((romHandle & ~(7 << 2)) == SPC.SPC_HANDLE_IN ? "SCSI" + ((romHandle >> 2) & 7) :   //内蔵SCSI
              (romHandle & ~(7 << 2)) == SPC.SPC_HANDLE_EX ? "SCSI" + ((romHandle >> 2) & 7) :   //拡張SCSI
              "ROM$" + XEiJ.fmtHex6 (romHandle));
    case 0xb000:
      return "RAM$" + XEiJ.fmtHex6 (ramAddress);
    default:
      return "STD";
    }
  }

  //smrClear ()
  //  SRAMクリア
  public static void smrClear () {
    XEiJ.pnlExitFullScreen (true);
    if (JOptionPane.showConfirmDialog (
      XEiJ.frmFrame,
      Multilingual.mlnJapanese ? "SRAM をクリアしますか？" : "Do you want to clear SRAM?",
      Multilingual.mlnJapanese ? "確認" : "Confirmation",
      JOptionPane.YES_NO_OPTION,
      JOptionPane.PLAIN_MESSAGE) == JOptionPane.YES_OPTION) {
      Arrays.fill (MainMemory.mmrM8, 0x00ed0000, 0x00ed0000 + 65536, (byte) 0x00);
    }
  }  //smrClear()

  //smrLoad ()
  //  SRAM読み込み
  public static void smrLoad () {
    JFileChooser2 fileChooser = new JFileChooser2 ();
    fileChooser.setFileFilter (new javax.swing.filechooser.FileFilter () {  //java.io.FileFilterと紛らわしい
      @Override public boolean accept (File file) {
        String name = file.getName ();
        String upperName = name.toUpperCase ();
        return (file.isDirectory () ||
                (file.isFile () &&
                 upperName.startsWith ("SRAM")));
      }
      @Override public String getDescription () {
        return (Multilingual.mlnJapanese ?
                "SRAM データファイル (SRAM*.*)" :
                "SRAM data files (SRAM*.*)");
      }
    });
    if (fileChooser.showOpenDialog (null) == JFileChooser.APPROVE_OPTION) {
      File file = fileChooser.getSelectedFile ();
      String name = file.getPath ();
      if (!smrLoadData (name)) {  //読み込めなかった
        XEiJ.pnlExitFullScreen (true);
        JOptionPane.showMessageDialog (null,
                                       Multilingual.mlnJapanese ?
                                       name + " のサイズが違います" :
                                       name + " has wrong size");
        return;
      }
    }
  }  //smrLoad()

  //success = smrLoadData (name)
  //  SRAMのイメージファイルを読み込む
  public static boolean smrLoadData (String name) {
    byte[] array = XEiJ.rscGetFile (name, smrSramSizeRequest);
    if (array != null) {  //読み込めた
      System.arraycopy (array, 0, MainMemory.mmrM8, 0x00ed0000, smrSramSizeRequest);  //SRAMにコピーする
      if (smrSramSizeRequest < 65536) {
        Arrays.fill (MainMemory.mmrM8, 0x00ed0000 + smrSramSizeRequest, 0x00ed0000 + 65536, (byte) 0x00);  //空き
      }
      return true;
    }
    return false;
  }

  //smrSave ()
  //  SRAM書き出し
  public static void smrSave () {
    JFileChooser2 fileChooser = new JFileChooser2 ();
    fileChooser.setFileFilter (new javax.swing.filechooser.FileFilter () {  //java.io.FileFilterと紛らわしい
      @Override public boolean accept (File file) {
        String name = file.getName ();
        String upperName = name.toUpperCase ();
        return (file.isDirectory () ||
                (file.isFile () &&
                 upperName.startsWith ("SRAM")));
      }
      @Override public String getDescription () {
        return (Multilingual.mlnJapanese ?
                "SRAM データファイル (SRAM*.*)" :
                "SRAM data files (SRAM*.*)");
      }
    });
    if (fileChooser.showSaveDialog (null) == JFileChooser.APPROVE_OPTION) {
      XEiJ.rscPutFile (fileChooser.getSelectedFile ().getPath (), MainMemory.mmrM8, 0x00ed0000, smrSramSizeRequest);
    }
  }  //smrSave()

}  //class SRAM
