//========================================================================================
//  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のみ)

  //起動時のキャッシュモード
  public static int smrCacheMode;

  //起動音
  public static int smrStupsndMode;

  //-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 JMenu smrCacheMenu;  //キャッシュメニュー
  public static JMenu smrStupsndMenu;  //起動音
  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);
    }

    //起動時のキャッシュモード
    smrCacheMode = smrStringToCache (Settings.sgsGetString ("cache"));

    //起動音
    smrStupsndMode = smrStringToStupsnd (Settings.sgsGetString ("stupsnd"));

    //-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" :
                           "");

    //起動時のキャッシュモード
    Settings.sgsPutString ("cache", smrCacheToString (smrCacheMode));

    //起動音
    Settings.sgsPutString ("stupsnd", smrStupsndToString (smrStupsndMode));

    //-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) :
                           "");

  }

  //smrMakeMenu ()
  //  mnbMakeMenuより前
  //  mpuMakeMenuより前
  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 デバッガ起動フラグ");

    //起動時のキャッシュモード
    //  SRAM_CACHE  $00ED0090
    //  cache
    //    -1  none     CACHE.X の設定に従う
    //     0  off-off  データキャッシュと命令キャッシュは無効
    //     1  off-on   データキャッシュは無効、命令キャッシュは有効
    //     2  on-off   データキャッシュは有効、命令キャッシュは無効
    //     3  on-on    データキャッシュと命令キャッシュは有効
    ActionListener cacheListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        String command = ae.getActionCommand ();
        switch (command) {
        case "Follow settings in CACHE.X":
          smrCacheMode = -1;
          break;
        case "Disabled":
          smrCacheMode = 0;
          break;
        case "Data cache disabled and instruction cache enabled":
          smrCacheMode = 1;
          break;
        case "Data cache enabled and instruction cache disabled":
          smrCacheMode = 2;
          break;
        case "Enabled":
          smrCacheMode = 3;
          break;
        }
      }
    };
    ButtonGroup cacheGroup = new ButtonGroup ();
    smrCacheMenu =
      Multilingual.mlnText (
        ComponentFactory.createMenu (
          "Cache mode at startup",
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (
              cacheGroup,
              smrCacheMode == -1, "Follow settings in CACHE.X",
              cacheListener),
            "ja", "CACHE.X の設定に従う"),
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (
              cacheGroup,
              smrCacheMode == 0, "Disabled",
              cacheListener),
            "ja", "無効"),
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (
              cacheGroup,
              smrCacheMode == 1, "Data cache disabled and instruction cache enabled",
              cacheListener),
            "ja", "データキャッシュは無効、命令キャッシュは有効"),
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (
              cacheGroup,
              smrCacheMode == 2, "Data cache enabled and instruction cache disabled",
              cacheListener),
            "ja", "データキャッシュは有効、命令キャッシュは無効"),
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (
              cacheGroup,
              smrCacheMode == 3, "Enabled",
              cacheListener),
            "ja", "有効")
          ),
        "ja", "起動時のキャッシュモード");

    //起動音
    //  SRAM_STARTUP_SOUND  $00ED0091
    //  stupsnd
    //    -1  none  stupsnd.x の設定に従う
    //     0  off
    //     1  on
    //              12 o1c   28 o2c   44 o3c   60 o4c   76 o5c    92 o6c   108 o7c   124 o8c
    //              13 o1c#  29 o2c#  45 o3c#  61 o4c#  77 o5c#   93 o6c#  109 o7c#  125 o8c#
    //              14 o1d   30 o2d   46 o3d   62 o4d   78 o5d    94 o6d   110 o7d   126 o8d
    //              16 o1d#  32 o2d#  48 o3d#  64 o4d#  80 o5d#   96 o6d#  112 o7d#
    //              17 o1e   33 o2e   49 o3e   65 o4e   81 o5e    97 o6e   113 o7e
    //     2  o0f   18 o1f   34 o2f   50 o3f   66 o4f   82 o5f    98 o6f   114 o7f
    //     4  o0f#  20 o1f#  36 o2f#  52 o3f#  68 o4f#  84 o5f#  100 o6f#  116 o7f#
    //     5  o0g   21 o1g   37 o2g   53 o3g   69 o4g   85 o5g   101 o6g   117 o7g
    //     6  o0g#  22 o1g#  38 o2g#  54 o3g#  70 o4g#  86 o5g#  102 o6g#  118 o7g#
    //     8  o0a   24 o1a   40 o2a   56 o3a   72 o4a   88 o5a   104 o6a   120 o7a
    //     9  o0a#  25 o1a#  41 o2a#  57 o3a#  73 o4a#  89 o5a#  105 o6a#  121 o7a#
    //    10  o0b   26 o1b   42 o2b   58 o3b   74 o4b   90 o5b   106 o6b   122 o7b
    ActionListener stupsndListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        String command = ae.getActionCommand ();
        switch (command) {
        case "Follow settings in stupsnd.x":
          smrStupsndMode = -1;
          break;
        case "Disabled":
          smrStupsndMode = 0;
          break;
        case "Enabled (o5c)":
          smrStupsndMode = 1;
          break;
        default:
          smrStupsndMode = smrStringToStupsnd (command);
        }
      }
    };
    ButtonGroup stupsndGroup = new ButtonGroup ();
    smrStupsndMenu =
      Multilingual.mlnText (
        ComponentFactory.createMenu (
          "Startup sound",
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (
              stupsndGroup,
              smrStupsndMode == -1, "Follow settings in stupsnd.x",
              stupsndListener),
            "ja", "stupsnd.x の設定に従う"),
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (
              stupsndGroup,
              smrStupsndMode == 0, "Disabled",
              stupsndListener),
            "ja", "無効"),
          Multilingual.mlnText (
            ComponentFactory.createRadioButtonMenuItem (
              stupsndGroup,
              smrStupsndMode == 1, "Enabled (o5c)",
              stupsndListener),
            "ja", "有効 (o5c)")
          ),
        "ja", "起動音");
    JMenu stupsndSubMenu = null;
    for (int mode = 2; mode <= 126; mode++) {  //127は不可
      if (mode % 4 == 3) {
        continue;
      }
      if (mode == 2 || (mode + 4) % 16 == 0) {
        stupsndSubMenu = ComponentFactory.createMenu ("o" + (mode + 4) / 16);
        smrStupsndMenu.add (stupsndSubMenu);
      }
      stupsndSubMenu.add (
        ComponentFactory.createRadioButtonMenuItem (
          stupsndGroup,
          smrStupsndMode == mode, smrStupsndToString (mode),
          stupsndListener)
        );
    }

    //メモリサイズ修正
    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));
      }

      boolean iplrom16 = (XEiJ.currentAccelerator == XEiJ.ACCELERATOR_HYBRID ||
                          XEiJ.currentAccelerator == XEiJ.ACCELERATOR_060TURBO ||
                          XEiJ.currentAccelerator == XEiJ.ACCELERATOR_060TURBOPRO ||
                          ROM.romIPLROM16On);  //IPLROM 1.6か

      //ROMデバッガ起動フラグ
      if (smrRomdbFlag != -1) {  //設定する
        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");
        }
      }

      //起動時のキャッシュモード
      if (smrCacheMode != -1) {
        int address = 0x00ed0090;
        int oldData = MainMemory.mmrRbz (address);
        int newData = smrCacheMode;
        if (oldData != newData) {
          MainMemory.mmrWb (address, newData);
          System.out.printf (Multilingual.mlnJapanese ?
                             "SRAM にある起動時のキャッシュモードを %s から %s に変更しました\n" :
                             "Cache mode at startup in SRAM changed from %s to %s\n",
                             smrCacheToString (oldData),
                             smrCacheToString (newData));
        }
      }

      //起動音
      if (smrStupsndMode != -1) {
        int address = 0x00ed0091;
        int oldData = MainMemory.mmrRbz (address);
        int newData = (!iplrom16 && 2 <= smrStupsndMode ? 1 :  //IPLROM 1.6でないとき2-126を1に変更
                       smrStupsndMode);
        if (oldData != newData) {
          MainMemory.mmrWb (address, newData);
          System.out.printf (Multilingual.mlnJapanese ?
                             "SRAM にある起動音の設定を %s から %s に変更しました\n" :
                             "Startup sound setting in SRAM changed from %s to %s\n",
                             smrStupsndToString (oldData),
                             smrStupsndToString (newData));
        }
      }

      //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;
  }

  //smrCacheToString (mode)
  //  キャッシュモードを文字列に変換する
  public static String smrCacheToString (int mode) {
    return (mode == 0 ? "off-off" :
            mode == 1 ? "off-on" :
            mode == 2 ? "on-off" :
            mode == 3 ? "on-on" :
            "");
  }  //smrCacheToString

  //smrStringToCache (s)
  //  文字列をキャッシュモードに変換する
  public static int smrStringToCache (String s) {
    s = s.toLowerCase ();
    return (s.equals ("off-off") ? 0 :
            s.equals ("off-on") ? 1 :
            s.equals ("on-off") ? 2 :
            s.equals ("on-on") ? 3 :
            -1);
  }  //smrStringToCache

  //smrStupsndToString (mode)
  //  起動音を文字列に変換する
  public static String smrStupsndToString (int mode) {
    String s = "";
    if (mode == 0) {
      s = "off";
    } else if (mode == 1) {
      s = "on";
    } else if (2 <= mode && mode <= 127) {
      int i = mode - (mode >> 2);
      int o = i / 12;
      int n = i % 12;
      if (9 <= n) {
        o++;
      }
      s = "o" + o + (n == 0 ? "d#" :
                     n == 1 ? "e" :
                     n == 2 ? "f" :
                     n == 3 ? "f#" :
                     n == 4 ? "g" :
                     n == 5 ? "g#" :
                     n == 6 ? "a" :
                     n == 7 ? "a#" :
                     n == 8 ? "b" :
                     n == 9 ? "c" :
                     n == 10 ? "c#" :
                     "d");
    }
    return s;
  }  //smrStupsndToString

  //smrStringToStupsnd (s)
  //  文字列を起動音に変換する
  public static int smrStringToStupsnd (String s) {
    s = s.toLowerCase ();
    int mode = -1;
    if (s.equals ("off")) {
      mode = 0;
    } else if (s.equals ("on")) {
      mode = 1;
    } else {
      int l = s.length ();
      int c0 = l < 1 ? -1 : s.charAt (0);
      int c1 = l < 2 ? -1 : s.charAt (1);
      int c2 = l < 3 ? -1 : s.charAt (2);
      int c3 = l < 4 ? -1 : s.charAt (3);
      if (c0 == 'o' &&
          '0' <= c1 && c1 <= '8' &&
          'a' <= c2 && c2 <= 'g') {
        int o = c1 - '0';
        int n = (c2 == 'a' ? 6 :
                 c2 == 'b' ? 8 :
                 c2 == 'c' ? -3 :
                 c2 == 'd' ? -1 :
                 c2 == 'e' ? 1 :
                 c2 == 'f' ? 2 :
                 4);
        if (c3 == '#' || c3 == '+') {
          n++;
        } else if (c3 == '-') {
          n--;
        }
        int i = 12 * o + n;
        if (2 <= i && i <= 95) {
          mode = i + i / 3;
        }
      }
    }
    return mode;
  }  //smrStringToStupsnd

  static {
    if (false) {
      //起動音の文字列変換の確認
      for (int mode = 0; mode <= 126; mode++) {  //127は不可
        if (mode % 4 == 3) {
          continue;
        }
        String s = smrStupsndToString (mode);
        int i = smrStringToStupsnd (s);
        System.out.printf ("mode=%d s=%s i=%d\n", mode, s, i);
      }
    }
  }

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