--- nono/lib/mainapp.cpp 2026/04/29 17:05:12 1.1.1.13 +++ nono/lib/mainapp.cpp 2026/04/29 17:05:15 1.1.1.14 @@ -15,9 +15,13 @@ #include "monitor.h" #include "mystring.h" #include "mythread.h" +#include "sram.h" #include "vm_luna.h" +#include "vm_news.h" #include "vm_x68k.h" +#include #include +#include #include #include #if defined(__linux__) @@ -38,18 +42,23 @@ MainApp::~MainApp() // 逆順に解放 pVM.reset(); + + pConfig.reset(); + gConfig = NULL; + + pMonitorManager.reset(); + gMonitorManager = NULL; + #if 0 // 確認用 if (objects.empty() == false) { printf("~MainApp: undead objects are:"); for (const auto *obj : objects) { - printf(" %s", obj->GetName().c_str()); + printf(" %s", obj->GetIdStr()); } printf("\n"); } #endif - pConfig.reset(); - pMonitorManager.reset(); logger.reset(); } @@ -63,13 +72,13 @@ MainApp::ShowHelp(bool all) const printf("usage: %s []\n", getprogname()); p("-c vm directory or configuration file"); - p("-f fast mode"); + p("-f fast mode (same as '-V fast-mode=1')"); if (IsGUI()) - p("--fontsize fontsize in monitors {12,16} (default:12)"); + p("--fontsize fontsize (same as '-V monitor-fontsize=')"); p("-h show brief help message"); p("--help show all help message including for developers"); if (IsGUI()) - p("-s,--scale screen scale, default 1.0"); + p("-s mainview scale (same as '-V mainview-scale=')"); p("--show-config show configuration variables"); p("--show-hostnet show list of hostnet drivers"); p("-v show version"); @@ -78,7 +87,8 @@ MainApp::ShowHelp(bool all) const if (all) { printf("\n(options for developers)\n"); - p("-b [,] set breakpoint"); + p("-b [,][,]"); + p(" set breakpoint. is either 'main'(default) or 'xp'"); p("-B benchmark mode"); p("-C output log to console"); p("-d debugger prompt on startup"); @@ -87,6 +97,8 @@ MainApp::ShowHelp(bool all) const p("-L =[,..] set loglevel (-Lhelp displays names)"); p("--load-only load host binary (a.out or ELF) but not execute"); p("-M [,..] monitors to display at startup (-Mhelp displays names)"); + p(" memdump[=[.]] fmt := B/W/L/M(MMU)/I(Disasm)/Z(XPDisasm)"); + p("-S MSX-DOS console emulation"); p("--perf performance measure mode"); } } @@ -95,7 +107,9 @@ MainApp::ShowHelp(bool all) const // enum は getopt() の1文字のオプションと衝突しなければいいので適当に // 0x80 から始めておく。 enum { - OPT_fontsize = 0x80, + OPTstart = 0x80 - 1, + OPT_create_sram, + OPT_fontsize, OPT_load_only, OPT_help, OPT_perf, @@ -104,6 +118,7 @@ enum { OPT_show_hostnet, }; static struct option longopts[] = { + { "create-sram", no_argument, NULL, OPT_create_sram }, { "fontsize", required_argument, NULL, OPT_fontsize }, { "load-only", required_argument, NULL, OPT_load_only }, { "help", no_argument, NULL, OPT_help }, @@ -124,6 +139,8 @@ static struct option longopts[] = { int MainApp::Init1(bool is_cli_, int ac, char *av[]) { + int rv; + is_cli = is_cli_; #if defined(__linux__) @@ -132,8 +149,9 @@ MainApp::Init1(bool is_cli_, int ac, cha prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); #endif - if (!ParseOpt(ac, av)) { - return EXIT_FAILURE; + rv = ParseOpt(ac, av); + if (rv != MainApp::PASS) { + return rv; } // ログ機構は引数処理後なるはや @@ -146,44 +164,84 @@ MainApp::Init1(bool is_cli_, int ac, cha pMonitorManager.reset(new MonitorManager()); gMonitorManager = pMonitorManager.get(); - // 設定を作成。コンストラクタで初期値を用意 + // 設定を作成。 + // c0. コンストラクタで初期値を用意。 pConfig.reset(new Config()); gConfig = pConfig.get(); - // 引数で指定されたディレクトリの設定ファイルがあれば読み込んで設定を更新 - ConfigFile file(vmfile); - if (file.Load() == false) { - return EXIT_FAILURE; + // c1. ホームディレクトリに設定ファイルがあれば読み込んで設定を更新。 + struct passwd *passwd = getpwuid(getuid()); + // 見付からないことはないはずだが、一応。 + if (passwd != NULL) { + std::string filename = std::string(passwd->pw_dir) + "/.nono.cfg"; + ConfigFile dotfile(filename); + if (dotfile.Load()) { + if (gConfig->Update(dotfile) == false) { + return EXIT_FAILURE; + } + } } - if (gConfig->Update(file) == false) { - return EXIT_FAILURE; + + // c2. VM ディレクトリに設定ファイルがあれば読み込んで設定を更新。 + // vmfile が存在するかどうかはここで調べるまで分からない。 + ConfigFile cfgfile(vmfile); + if (cfgfile.Load()) { + if (gConfig->Update(cfgfile) == false) { + return EXIT_FAILURE; + } } - // 最後にコマンドライン引数で更新 - if (gConfig->Update(config_options) == false) { - return EXIT_FAILURE; + + // c3. 最後にコマンドライン引数で更新 + for (const auto& pair : config_options) { + if (gConfig->Update(pair.first, pair.second) == false) { + return EXIT_FAILURE; + } + } + + // MSX-DOS モードならここでいくつかパラメータを強制的に変更。 + if (msxdos_mode) { + // LUNA-I 最小構成でいい + gConfig->Update("vmtype=luna", "-S"); + gConfig->Update("ram-size=16", "-S"); + gConfig->Update("luna-video-plane=1", "-S"); + // ホスト側には干渉しない。 + gConfig->Update("hostcom-driver=none", "-S"); + gConfig->Update("hostnet-driver=none", "-S"); } // 0. VM 種別を決定 // VM 種別文字列から vmtype を決定 const ConfigItem& item = gConfig->Find("vmtype"); - std::string vmstr = string_tolower(item.AsString()); + vmstr = string_tolower(item.AsString()); if (vmstr == "x68030") { vmtype = VMType::X68030; } else if (vmstr == "luna") { vmtype = VMType::LUNA1; } else if (vmstr == "luna88k") { vmtype = VMType::LUNA88K; + } else if (vmstr == "news") { + vmtype = VMType::NEWS; } else { if (item.GetFrom() == ConfigItem::FromInitial) { // 未指定の時 warnx("vmtype must be specified"); } else { // ユーザ由来の時 - item.Err("invalid vmtype"); + item.Err("Invalid vmtype"); } return EXIT_FAILURE; } + // 0.5. VM が確定したところで SRAM 作成。 + if (create_sram) { + if (vmtype == VMType::X68030) { + return CreateSRAM(); + } else { + warnx("--create-sram is only for X68030 mode"); + return EXIT_FAILURE; + } + } + // 1. VM 作成 switch (vmtype) { case VMType::X68030: @@ -198,8 +256,12 @@ MainApp::Init1(bool is_cli_, int ac, cha pVM.reset(new VM_LUNA88K()); break; + case VMType::NEWS: + pVM.reset(new VM_NEWS()); + break; + default: - __unreachable(); + PANIC("corrupted vmtype=%s", vmstr.c_str()); } gVM = pVM.get(); @@ -214,7 +276,10 @@ MainApp::Init1(bool is_cli_, int ac, cha // 設定内容を表示して終了 // (VM コンストラクタで変数の増減があるのでそれより後、 // Create() でも SCSI パラメータを減らすので、それより後) - gConfig->Fix(); + if (gConfig->Fix() == false) { + pVM.reset(); + return EXIT_SUCCESS; + } if (show_config) { gConfig->Show(show_config - 1); pVM.reset(); @@ -261,7 +326,9 @@ MainApp::Init2() // コマンドライン引数を処理する。 // 知らない引数とかがあればこちらで usage を表示して false を返す。 -bool +// 戻り値は、MainApp::PASS なら実行を継続、 +// EXIT_SUCCESS/EXIT_FAILURE ならこのコードで終了。 +int MainApp::ParseOpt(int ac, char *av[]) { struct stat st; @@ -269,11 +336,9 @@ MainApp::ParseOpt(int ac, char *av[]) int b; int c; - fontsize = 12; - screen_scale = 1.0; cpath = "."; - while ((c = getopt_long(ac, av, "b:B:c:CdDfhHL:M:s:vV:X:", + while ((c = getopt_long(ac, av, "b:B:c:CdDfhHL:M:s:SvV:X:", longopts, NULL)) != -1) { switch (c) { case 'b': @@ -284,7 +349,7 @@ MainApp::ParseOpt(int ac, char *av[]) b = atoi(optarg); if (b < 2 || b > 6) { fprintf(stderr, "-B : 2..6\n"); - return false; + return EXIT_FAILURE; } benchmark_mode = b; break; @@ -307,13 +372,12 @@ MainApp::ParseOpt(int ac, char *av[]) case 'D': // -D は -V debugger-driver=stdio と等価。 - config_options.push_back("debugger-driver=stdio"); - // エラー表示用に -D で指定されたことを覚えておく。 - opt_D = true; + config_options.emplace_back("debugger-driver=stdio", "-D"); break; case 'f': - fast_mode = true; + // -f は -V fast-mode=1 と等価。 + config_options.emplace_back("fast-mode=1", "-f"); break; case 'H': @@ -332,20 +396,14 @@ MainApp::ParseOpt(int ac, char *av[]) break; case 's': - if (IsGUI()) { - char *end; - errno = 0; - screen_scale = strtod(optarg, &end); - if (end == optarg || end[0] != '\0' || errno == ERANGE) { - warnx("-s: invalid argument"); - return false; - } - // 上限は適当 - if (screen_scale <= 0.0 || screen_scale >= 10.0) { - warnx("-s: invalid scale"); - return false; - } - } + { + auto line = string_format("mainview-scale=%s", optarg); + config_options.emplace_back(line, "-s"); + break; + } + + case 'S': + msxdos_mode = true; break; case 'X': @@ -367,36 +425,44 @@ MainApp::ParseOpt(int ac, char *av[]) exit(0); case 'V': - config_options.push_back(optarg); + config_options.emplace_back(optarg, "-V"); + break; + + case OPT_create_sram: + create_sram = true; break; case OPT_fontsize: - fontsize = atoi(optarg); + { + auto line = string_format("monitor-fontsize=%s", optarg); + config_options.emplace_back(line, "--fontsize"); break; + } case OPT_help: ShowHelp(true); - return false; + return EXIT_SUCCESS; case OPT_perf: // パフォーマンス確認用。 // -Vprom-image=PROM.DAT はパスの問題があるので各自で追加指定 // する必要がある。 - config_options.emplace_back(".rtc-force-fixed=1"); - config_options.emplace_back("clock-sync=virtual"); - config_options.emplace_back("ethernet-macaddr=02:00:00:00:00:01"); - config_options.emplace_back("hostcom-driver=none"); - config_options.emplace_back("hostnet-driver=none"); - config_options.emplace_back("luna-dipsw1=11110111"); - config_options.emplace_back("spc0-id0-writeignore=1"); - config_options.emplace_back("spc0-id1-writeignore=1"); - config_options.emplace_back("spc0-id2-writeignore=1"); - config_options.emplace_back("spc0-id3-writeignore=1"); - config_options.emplace_back("spc0-id4-writeignore=1"); - config_options.emplace_back("spc0-id5-writeignore=1"); - config_options.emplace_back("spc0-id6-writeignore=1"); - // -f -C - fast_mode = true; + config_options.emplace_back(".rtc-force-fixed=1", "--perf"); + config_options.emplace_back("clock-sync=virtual", "--perf"); + config_options.emplace_back("ethernet-macaddr=02:00:00:00:00:01", + "--perf"); + config_options.emplace_back("hostcom-driver=none", "--perf"); + config_options.emplace_back("hostnet-driver=none", "--perf"); + config_options.emplace_back("luna-dipsw1=11110111", "--perf"); + config_options.emplace_back("spc0-id0-writeignore=1", "--perf"); + config_options.emplace_back("spc0-id1-writeignore=1", "--perf"); + config_options.emplace_back("spc0-id2-writeignore=1", "--perf"); + config_options.emplace_back("spc0-id3-writeignore=1", "--perf"); + config_options.emplace_back("spc0-id4-writeignore=1", "--perf"); + config_options.emplace_back("spc0-id5-writeignore=1", "--perf"); + config_options.emplace_back("spc0-id6-writeignore=1", "--perf"); + config_options.emplace_back("fast-mode=1", "--perf"); + // -C log_to_console = true; break; @@ -410,12 +476,15 @@ MainApp::ParseOpt(int ac, char *av[]) case OPT_show_hostnet: ShowHostnet(); - return false; + return EXIT_SUCCESS; case 'h': + ShowHelp(false); + return EXIT_SUCCESS; + default: ShowHelp(false); - return false; + return EXIT_FAILURE; } } @@ -427,7 +496,7 @@ MainApp::ParseOpt(int ac, char *av[]) // -c DIR/FILE => vmdir = DIR, vmfile = FILE if (stat(cpath, &st) < 0) { warn("stat %s", cpath); - return false; + return EXIT_FAILURE; } if (S_ISDIR(st.st_mode)) { vmdir = std::string(cpath); @@ -445,7 +514,7 @@ MainApp::ParseOpt(int ac, char *av[]) } } else { warnx("-c %s: path must be file or directory", cpath); - return false; + return EXIT_FAILURE; } // CLI 版で -Mhelp つけても黙って起動するのはさすがにどうかと思う。 @@ -455,23 +524,38 @@ MainApp::ParseOpt(int ac, char *av[]) if (IsCLI()) { if (monitor_opt == "help") { warnx("-Mhelp is not available on CLI"); - return false; + return EXIT_FAILURE; } if (!monitor_opt.empty()) { warnx("-M option is ignored on CLI"); - return true; + return EXIT_FAILURE; } } - // Human モードでは実行ファイル名が必要 + // Human モード、MSX-DOS モードでは実行ファイル名が必要 if (human_mode) { if (exec_file == NULL) { warnx("-H option needs -X"); - return false; + return EXIT_FAILURE; + } + } + if (msxdos_mode) { + if (exec_file == NULL) { + warnx("-S option needs -X"); + return EXIT_FAILURE; } } - return true; + // 実行ファイルは、ファイル名を間違えたくらいならここでエラーに出来る。 + if (exec_file) { + int r = access(exec_file, R_OK); + if (r != 0) { + warn("-X %s", exec_file); + return EXIT_FAILURE; + } + } + + return MainApp::PASS; } // バージョンを表示 @@ -769,6 +853,48 @@ MainApp::SearchFile(const std::string& n return path; } +// 初回起動時用に SRAM.DAT を作成する。 +// 戻り値は EXIT_SUCCESS / EXIT_FAILURE。 +int +MainApp::CreateSRAM() +{ + char buf[16 * 1024]; + std::string filename; + autofd fd; + int r; + + filename = GetVMDir() + "SRAM.DAT"; + + // 存在確認のため一旦読み込み専用で開いてみる。 + // オープン出来たら何もしない。 + fd = open(filename.c_str(), O_RDONLY); + if (fd >= 0) { + warnx("%s: Already exists", filename.c_str()); + return EXIT_FAILURE; + } + + fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + warn("%s: open failed", filename.c_str()); + return EXIT_FAILURE; + } + + memset(&buf[0], 0, sizeof(buf)); + for (int i = 0; i < SRAMDevice::InitialData.size(); i++) { + buf[i] = SRAMDevice::InitialData[i]; + } + + r = write(fd, buf, sizeof(buf)); + if (r < 0) { + warn("%s: write failed", filename.c_str()); + return EXIT_FAILURE; + } + + fd.Close(); + warnx("created %s", filename.c_str()); + return EXIT_SUCCESS; +} + // 現在の VM が指定のケーパビリティを持っているか? bool MainApp::Has(VMCap cap) const @@ -787,20 +913,28 @@ MainApp::ShowHostnet() const } } -// オブジェクトを追加 +// オブジェクト登録 void -MainApp::AddObject(Object *obj) +MainApp::RegistObject(Object *obj) { - obj->logger = GetLogger(); + // ID が重複していないかチェック (NONE なら重複可) + auto id = obj->GetId(); + if (id != OBJ_NONE) { + if (FindObject(id)) { + PANIC("%s already exists", Object::GetIdStr(id)); + } + } + + obj->logger = gMainApp.GetLogger(); objects.push_back(obj); } -// オブジェクトを削除 -// XXX マルチスレッドな状況からは呼ばれないはず? +// オブジェクト削除 void -MainApp::DeleteObject(Object *obj) +MainApp::UnregistObject(Object *obj) { + // 最初に見付かった一つを削除するだけでいい for (auto it = objects.begin(); it != objects.end(); ++it) { if (*it == obj) { objects.erase(it); @@ -808,3 +942,30 @@ MainApp::DeleteObject(Object *obj) } } } + +// 指定された id を持つオブジェクトを返す。なければ NULL を返す。 +Object * +MainApp::FindObject(int id) const +{ + // NONE はここで検索しない (SCSI デバイスなど、そっちで検索する) + if (id == OBJ_NONE) { + return NULL; + } + for (auto obj : objects) { + if (obj->GetId() == id) { + return obj; + } + } + return NULL; +} + +// 指定された id を持つオブジェクトを探す。なければ assert する。 +Object * +MainApp::GetObject(int id) const +{ + Object *obj = FindObject(id); + if (__predict_false(obj == NULL)) { + PANIC("objid=%s not found", Object::GetIdStr(id)); + } + return obj; +}