--- nono/vm/romemu_luna88k.cpp 2026/04/29 17:04:55 1.1.1.7 +++ nono/vm/romemu_luna88k.cpp 2026/04/29 17:05:11 1.1.1.11 @@ -4,28 +4,126 @@ // Licensed under nono-license.txt // +// // LUNA88K の ROM エミュレーション +// -// IODevice -// | -// +----------+---------------------+ -// v v -// ROMDevice LunaPROMEmuDeivce (ROM エミュレーションの共通部分) -// | | -// | +------------+----------------+ -// v v v -// PROMDevice Luna1PROMEmuDevice Luna88kPROMEmuDevice -// (実ROM) (LUNA-I エミュレーション) (LUNA88K エミュレーション) +// IODevice +// | +// +-- ROMDevice (LoadROM()、ウェイト、マスク等を持つ) +// | | +// | +-- PROMDevice (LUNA* の PROM) +// | +-- IPLROM1Device (X680x0 の IPLROM 後半) +// | +-- IPLROM2Device (X680x0 の IPLROM 前半) +// | +-- CGROMDevice (X680x0 の CGROM) +// | +-- LunaPROMEmuDevice (LUNA PROM エミュレーションの共通部分) +// | | +// | +-- Luna1PROMEmuDevice (LUNA1 の PROM エミュレーション) +// | | +// | | +----------------------+ +// | +--| Luna88kPROMEmuDevice | (LUNA88K の PROM エミュレーション) +// | +----------------------+ +// | +// +-- PROM0Device (LUNA* のブートページ切り替え用プロキシ) +// +-- IPLROM0Device (X680x0 のブートページ切り替え用プロキシ) #include "romemu_luna88k.h" +#include "interrupt.h" #include "iodevstream.h" -#include "lunafb.h" #include "mainapp.h" #include "memorystream.h" #include "mk48t02.h" +#include "mpu88xx0.h" #include "pio.h" #include "ram.h" -#include "sysctlr.h" + +static bool luna88k_fline_callback(m88kcpu *cpu, void *arg); + +// m88k の命令コードを少し読みやすく書きたい。 +// 必要なものは適宜足すこと。 +enum class OPr { + r0, r1, r2, r3, r4, r5, r6, r7, + r8, r9, r10, r11, r12, r13, r14, r15, + r16, r17, r18, r19, r20, r21, r22, r23, + r24, r25, r26, r27, r28, r29, r30, r31, +}; +#define r0 OPr::r0 +#define r1 OPr::r1 +#define r2 OPr::r2 +#define r3 OPr::r3 +#define r4 OPr::r4 +#define r5 OPr::r5 +#define r6 OPr::r6 +#define r7 OPr::r7 +#define r8 OPr::r8 +#define r9 OPr::r9 +#define r10 OPr::r10 +enum OPm5 { + eq0 = 0x02, + ne0 = 0x0d, +}; +enum OPcr { + psr = 0x01, +}; + +#define USC __unused static constexpr + +USC uint32 OP3(uint32 base, uint32 rd, uint32 rs1, uint32 rs2) { + return (base) | (rd << 21) | (rs1 << 16) | rs2; +} +USC uint32 OP4(uint32 base, uint32 rd, uint32 rs1, + uint32 w, uint32 o) { + return (base) | (rd << 21) | (rs1 << 16) | (w << 5) | o; +} + +USC uint32 OP_addu(OPr rd, OPr rs1, uint32 imm) { + return OP3(0x60000000, (uint32)rd, (uint32)rs1, imm); +} +USC uint32 OP_addu(OPr rd, OPr rs1, OPr rs2) { + return OP3(0xf4006000, (uint32)rd, (uint32)rs1, (uint32)rs2); +} +USC uint32 OP_bcnd(OPm5 m5, OPr rs1, uint32 d16) { + return OP3(0xe8000000, (uint32)m5, (uint32)rs1, d16 & 0xffff); +} +USC uint32 OP_bsr(uint32 d26) { + return (0xc8000000 | (d26 & 0x03ffffff)); +} +USC uint32 OP_clr(OPr rd, OPr rs1, uint32 w, uint32 o) { + return OP4(0xf0008000, (uint32)rd, (uint32)rs1, w, o); +} +USC uint32 OP_jmp(OPr rs2) { + return OP3(0xf400c000, 0, 0, (uint32)rs2); +} +USC uint32 OP_ld(OPr rd, OPr rs1, uint32 imm) { + return OP3(0x14000000, (uint32)rd, (uint32)rs1, imm); +} +USC uint32 OP_ldcr(OPr rd, uint32 crs) { + return OP4(0x80004000, (uint32)rd, 0, crs, 0); +} +USC uint32 __unused OP_or(OPr rd, OPr rs1, OPr rs2) { + return OP3(0xf4005800, (uint32)rd, (uint32)rs1, (uint32)rs2); +} +USC uint32 OP_or(OPr rd, OPr rs1, uint32 imm) { + return OP3(0x58000000, (uint32)rd, (uint32)rs1, imm); +} +USC uint32 OP_or_u(OPr rd, OPr rs1, uint32 imm) { + return OP3(0x5c000000, (uint32)rd, (uint32)rs1, imm); +} +USC uint32 OP_rte() { + return 0xf400fc00; +} +USC uint32 OP_st(OPr rd, OPr rs1, OPr rs2) { + return OP3(0xf4002400, (uint32)rd, (uint32)rs1, (uint32)rs2); +} +USC uint32 OP_stcr(OPr rs1, uint32 crd) { + return OP4(0x80008000, 0, (uint32)rs1, crd, (uint32)rs1); +} +USC uint32 OP_doscall(OPr rd, OPr rs1, uint32 imm) { + return OP3(0xfc000400, (uint32)rd, (uint32)rs1, imm); +} +#define DOS_GETCHAR (1) +#define DOS_PUTCHAR (2) + // コンストラクタ Luna88kPROMEmuDevice::Luna88kPROMEmuDevice() @@ -35,7 +133,11 @@ Luna88kPROMEmuDevice::Luna88kPROMEmuDevi // ROM の特定アドレスを読み出す人がいるかも知れないので、 // サイズは実 ROM と同じにしておく。LUNA-I からの類推。 devlen = 256 * 1024; - romdata.reset(new uint8 [devlen]); + imagebuf.reset(new uint8 [devlen]); + memset(&imagebuf[0], 0, devlen); + mem = &imagebuf[0]; + + mask = devlen - 1; // プロンプトを初期化。 prompt = default_prompt; @@ -54,7 +156,10 @@ Luna88kPROMEmuDevice::Init() return false; } - MemoryStreamBE ms(romdata.get()); + // DOS call エミュレーションのコールバックを登録 + gMPU88xx0->SetFLineCallback(luna88k_fline_callback, this); + + MemoryStreamBE ms(imagebuf.get()); // リセット時のブートページには ROM (これ) が見えている。 // ROMIO_INIT へのアクセスでブートページを RAM に切り替えて、同時に RAM の @@ -63,63 +168,75 @@ Luna88kPROMEmuDevice::Init() // してよい。というか2ワードでは 0番地から ROM まで飛べないので使わないと // 面倒くさい。一体どういう設計してんだ…。 - // ; _ROM_START へジャンプ (ここは0番地) - ms.Write32(0x5cc04100); // or.u r6, r0, #0x4100 - ms.Write32(0x58c6000c); // or r6, r6, #0x000c - ms.Write32(0xf400c006); // jmp r6 - - // _ROM_START: (ここは ROM) - // ; 元の PSR を r10 に保存しつつ - // ; 割り込み許可 - ms.Write32(0x81404020); // ldcr r10, psr - ms.Write32(0xf04a8040); // clr r2, r10, 2<1> ; IND,SFRZ - ms.Write32(0x80028022); // stcr r2, psr - // ; r3 <- ROMIO_BASE - ms.Write32(0x5c600000 | // or.u r3, r0, #ROMIO_BASE.Hi - (ROMIO_BASE >> 16)); // - ms.Write32(0x58630000 | // or r3, r0, #ROMIO_BASE.Lo - (ROMIO_BASE & 0xffff)); // - // ; ROMIO_INIT を読み出す - ms.Write32(0x14430000); // ld r2, r3, #+INIT - // ; 起動先があれば GO - ms.Write32(0xe9a20005); // bcnd ne0, r2, _go - // _prompt: - // ; 疑似 STOP でキー入力を待つ - ms.Write32(0xc8000001); // bsr _stop - // ; この時点で r1 = _stop - // _stop: - ms.Write32(0xf400c001); // jmp r1 - // _back: - ms.Write32(0x14430000 | // ld r2, r3, #+ENTRY - (ROMIO_ENTRY - ROMIO_BASE));// - // ; 起動先がなければループ - ms.Write32(0xe842fffd); // bcnd eq0, r2, _prompt - // ; 起動先があれば後始末して - ms.Write32(0x14030000 | // _go: - (ROMIO_CLOSE - ROMIO_BASE));// ld r0, r3, #+CLOSE - // ; 割り込み禁止に戻す - ms.Write32(0x800a802a); // stcr r10, psr - // ; ジャンプ - ms.Write32(0xf400c002); // jmp r2 + // ここからややアセンブラっぽいインデント + + // _START: + // ここは 0番地。ここから _ROM_START へジャンプ + ms.Write32(OP_or_u (r6, r0, 0x4100)); + ms.Write32(OP_or (r6, r6, 0x000c)); + ms.Write32(OP_jmp (r6)); + + // _ROM_START: (ここは ROM) + // RAM に切り替え + ms.Write32(OP_st (r0, r6, r0)); + + // 元の PSR を r10 に保存し、r9 に割り込み許可の値を作る + ms.Write32(OP_ldcr (r10, psr)); + ms.Write32(OP_clr (r9, r10, 2, 0)); // clear IND,SFRZ + + ms.Write32(OP_or_u (r3, r0, ROMIO_BASE >> 16)); + ms.Write32(OP_or (r3, r3, ROMIO_BASE & 0xfff)); + // ROMIO_INIT を読み出す + ms.Write32(OP_ld (r2, r3, (ROMIO_INIT - ROMIO_BASE))); + // 起動先があれば GO + ms.Write32(OP_bcnd (ne0, r2, 7)); // 7 = _go + + // _prompt: + // 疑似 STOP でキー入力を待つ + ms.Write32(OP_stcr (r9, psr)); // 割り込み許可 + ms.Write32(OP_bsr (1)); // 1 = _stop + // この時点で r1 = _stop + + // _stop: + ms.Write32(OP_jmp (r1)); + + // _back: + // 割り込み禁止に戻す + ms.Write32(OP_stcr (r10, psr)); + + ms.Write32(OP_ld (r2, r3, (ROMIO_ENTRY - ROMIO_BASE))); + // 起動先がなければループ + ms.Write32(OP_bcnd (eq0, r2, -5)); // -5 = _prompt + + // _go: + // 起動先があれば後始末して + ms.Write32(OP_ld (r0, r3, (ROMIO_CLOSE - ROMIO_BASE))); + // ジャンプ + ms.Write32(OP_jmp (r2)); // ここから割り込みハンドラ。割り込みは SIO のみ許可にしてある。 // キーボード割り込みが上がったら KEYIN をコールして _back に戻る。 - ms.SetPtr(&romdata[0x200]); - ms.Write32(0x14030000 | // ld r0, r3, #+KEYIN - (ROMIO_KEYIN - ROMIO_BASE));// - // ; 戻ったところの jmp を抜ける - ms.Write32(0x60210004); // addu r1, r1, #4 - ms.Write32(0xf400fc00); // rte + ms.SetOffset(0x200); + ms.Write32(OP_ld (r0, r3, (ROMIO_KEYIN - ROMIO_BASE))); + // 戻ったところの jmp を抜ける + ms.Write32(OP_addu (r1, r1, 0x04)); + ms.Write32(OP_rte ()); - return true; -} + // ROM GETC エミュレータ + ms.SetOffset(0x300); + ms.Write32(OP_doscall(r2, r0, DOS_GETCHAR)); + ms.Write32(OP_jmp (r1)); -void -Luna88kPROMEmuDevice::ResetHard() -{ - inherited::ResetHard(); + // ROM PUTC エミュレータ + // 表示する文字は r3 に置かれている。 + ms.SetOffset(0x400); + ms.Write32(OP_doscall(r0, r3, DOS_PUTCHAR)); + ms.Write32(OP_jmp (r1)); - b_entrypoint = 0; + + // 変なインデントここまで + + return true; } // 起動時の処理を行う。 @@ -128,9 +245,11 @@ Luna88kPROMEmuDevice::ResetHard() uint32 Luna88kPROMEmuDevice::ROM_Init() { - IODeviceStream rs(gRAM.get()); + IODeviceStream rs(gRAM); uint32 entry; + b_entrypoint = 0; + // デバイスの初期化 (親クラス) InitDevice(); @@ -140,7 +259,7 @@ Luna88kPROMEmuDevice::ROM_Init() rs.SetAddr(0); // ベクタテーブルを rte で埋めておく for (int i = 0; i < 512 * 2; i++) { - rs.Write32(0xf400fc00 /* rte */); + rs.Write32(OP_rte()); } // +$08: 割り込みベクタ rs.Write32(8, 0xc00003fe); // br $00001000 @@ -149,34 +268,26 @@ Luna88kPROMEmuDevice::ROM_Init() // ベクタテーブルは各2ワードしかないのに、0 番地付近にあるベクタテーブル // から2ワードでは ROM まで飛べない。一体どういう設計してんだ…。 rs.SetAddr(0x1000); - rs.Write32(0x5cc04100); // or.u r6, r0, #0x4100 - rs.Write32(0x58c60200); // or r6, r6, #0x0200 - rs.Write32(0xf400c006); // jmp r6 + rs.Write32(OP_or_u (r6, r0, 0x4100)); + rs.Write32(OP_or (r6, r6, 0x0200)); + rs.Write32(OP_jmp (r6)); // ROM の機能テーブル - // 0x00001100 からあるようだ - // 0x0000110c GETC() - // 0x00001110 PUTC(c) + // 0x00001100 ? + // 0x00001104 ? + // 0x00001108 ? + // 0x0000110c GETC() のアドレス + // 0x00001110 PUTC(c) のアドレス // 0x00001114 ビットマッププレーンの有効状態が入っている。 // bit0: plane0 有効 // bit1..7: plane1..7 有効 rs.SetAddr(0x110c); - rs.Write32(0x00001300); - rs.Write32(0x00001400); - rs.Write32(0x00000001); - - // ROM GETC エミュレータ - rs.SetAddr(0x1300); - rs.Write32(0xfc000c00); // internal inst getc - rs.Write32(0xf400c001); // jmp r1 - - // ROM PUTC エミュレータ - rs.SetAddr(0x1400); - rs.Write32(0xfc001000); // internal inst putc - rs.Write32(0xf400c001); // jmp r1 + rs.Write32(0x41000300); + rs.Write32(0x41000400); + rs.Write32(0x00000001); // スクリーンを初期化 - InitScreen("LUNA88K"); + InitScreen(); entry = 0; @@ -184,42 +295,43 @@ Luna88kPROMEmuDevice::ROM_Init() if (VerifyNVRAM() == false) { // PROM 1.20 は初期化するかどうか確認してくるけど、とりあえず。 InitNVRAM(); - text.Print("** NVRAM Initialized.\n"); - text.Print("\n"); + errmsg = "NVRAM Initialized."; // NVRAM をクリアしたら DIPSW によらずプロンプトに降りる goto done; } - // DIPSW#1-1 が UP なら自動起動、DOWN なら ROM モニタ起動なので、 - // これを真似する。 - if ((gPIO0->GetDIPSW1() & 1)) { - if (gMainApp.host_file) { - // ホストのファイルを読み込んでエントリポイントアドレスを - // この Read アクセスの読み出し値として返す。 - // エラーがあればエラーメッセージは呼び出し先側で表示してある。 - entry = LoadHostFile(); - } else { + // -X file + // DIPSW の状態に関わらず、ファイルをロードし実行する。 + // --load-only file + // DIPSW の状態に関わらず、ファイルをロードし、プロンプトで止まる。 + if (gMainApp.exec_file) { + // ホストのファイルを読み込んでエントリポイントアドレスを + // この Read アクセスの読み出し値として返す。 + // エラーがあればエラーメッセージは呼び出し先側で表示してある。 + b_entrypoint = LoadHostFile(); + if (gMainApp.load_and_exec) { + entry = b_entrypoint; + } + } else { + // DIPSW#1-1 が UP なら自動起動、DOWN なら ROM モニタ起動なので、 + // これを真似する。 + if (gPIO0->IsDIPSW11Up()) { // NVRAM の設定に従ってブート先を探す。 errmsg.clear(); bootinfo_t bootinfo; GetNVRAM(bootinfo); entry = LoadGuestFile(bootinfo); - if (entry == 0) { - text.Print("** %s\n", errmsg.c_str()); - } } } - // ここまでタイトルなどを Print してはいるが、実際の画面更新は - // ConsoleUpdate() を呼ぶまで行われないことを利用(悪用?)して、 - // ここでエラーなくブートプログラムに制御を渡せる場合には何も表示せずに - // ゲストに処理を移す。意図して或いは意図せずプロンプトに降りる場合だけ - // ConsoleUpdate を呼ぶことでタイトルを含めて画面を表示する。 done: if (entry == 0) { + PrintTitle("LUNA88K"); + if (errmsg.empty() == false) { + Print("** %s\n\n", errmsg.c_str()); + } ClearPrompt(); - ConsoleUpdate(); } return entry; @@ -246,9 +358,8 @@ Luna88kPROMEmuDevice::GetChar(char ascii // 0 以外ならノンブロッキングモード // 入力されたキーを表示 - text.SetAttr(TA::Normal); if (' ' <= asciicode && asciicode < 0x7f) { - text.Putc(asciicode); + Putc(asciicode); } switch (prompt_idx) { @@ -268,10 +379,8 @@ Luna88kPROMEmuDevice::GetChar(char ascii // プロンプトを戻す prompt = default_prompt; prompt_idx = 0; - prompt_y = text.GetY(); - - // こっちは画面更新をする必要がある - ConsoleUpdate(); + prompt_y = cursor_y; + ClearPrompt(); } } @@ -324,7 +433,7 @@ Luna88kPROMEmuDevice::Command() return; } - text.Print("** Unknown command: %s\n", arg[0].c_str()); + Print("** Unknown command: %s\n", arg[0].c_str()); } // "h" コマンド @@ -336,7 +445,7 @@ Luna88kPROMEmuDevice::CommandH(const std if (arg.size() < 2) { // ヘルプメッセージ mainhelp: - text.Print( + Print( " *** Help Message ***\n" " b : load and execute program\n" " h : show this message\n" @@ -347,7 +456,7 @@ Luna88kPROMEmuDevice::CommandH(const std const std::string& key = arg[1]; if (key == "nvram") { - text.Print( + Print( " *** Help Message ***\n" " nvram [] : show\n" " nvram : set\n" @@ -373,18 +482,18 @@ Luna88kPROMEmuDevice::CommandB(const std GetNVRAM(bootinfo); b_entrypoint = LoadGuestFile(bootinfo); if (b_entrypoint == 0) { - text.Print("** %s\n", errmsg.c_str()); + Print("** %s\n", errmsg.c_str()); } } // "bh" コマンド (勝手に拡張) -// -A オプションで指定されたホストプログラムをロードして実行。 +// -X オプションで指定されたホストプログラムをロードして実行。 void Luna88kPROMEmuDevice::CommandBH() { // 指定されてたら読み込む - if (gMainApp.host_file == NULL) { - text.Print("** Cannot loaded; host program is not specified.\n"); + if (gMainApp.exec_file == NULL) { + Print("** Cannot loaded; host program is not specified.\n"); return; } b_entrypoint = LoadHostFile(); @@ -421,7 +530,7 @@ Luna88kPROMEmuDevice::CommandNvram(const } break; default: - text.Print("** Syntax error\n"); + Print("** Syntax error\n"); } } @@ -454,7 +563,7 @@ Luna88kPROMEmuDevice::CommandNvramShowAl continue; } std::string val = nvram->PeekString(offset + 16); - text.Print("%-15s : %s\n", key.c_str(), val.c_str()); + Print("%-15s : %s\n", key.c_str(), val.c_str()); } } @@ -469,9 +578,9 @@ Luna88kPROMEmuDevice::CommandNvramShow(c uint32 offset = FindNVRAM(key); if (offset > 0) { std::string val = nvram->PeekString(offset + 16); - text.Print("%-15s : %s\n", key.c_str(), val.c_str()); + Print("%-15s : %s\n", key.c_str(), val.c_str()); } else { - text.Print("** Not found\n"); + Print("** Not found\n"); } } @@ -488,11 +597,11 @@ Luna88kPROMEmuDevice::CommandNvramUpdate putmsg(1, "nvram Update |%s|%s|", nvram_key.c_str(), nvram_val.c_str()); if (nvram_key.size() > 16) { - text.Print("** Key too long\n"); + Print("** Key too long\n"); return; } if (nvram_val.size() > 16) { - text.Print("** Val too long\n"); + Print("** Val too long\n"); return; } @@ -512,7 +621,7 @@ Luna88kPROMEmuDevice::CommandNvramUpdate // 先に空き領域をチェックしている nvram_offset = FindNVRAM(""); if (nvram_offset == 0) { - text.Print("** No area available\n"); + Print("** No area available\n"); return; } @@ -533,20 +642,20 @@ Luna88kPROMEmuDevice::CommandNvramUpdate assert(nvram_offset > 0); assert(nvram_val.size() <= 16); - text.CRLF(); + Putc('\n'); if (asciicode != 'y' && asciicode != 'Y') { - text.Print("** Not updated\n"); + Print("** Not updated\n"); return; } if (nvram->WriteString(nvram_offset + 16, nvram_val) == false) { - text.Print("** Write error\n"); + Print("** Write error\n"); return; } WriteNVRAMCsum(); - text.Print("Updated\n"); + Print("Updated\n"); } // NVRAM 項目を追加 (キー入力後) @@ -559,21 +668,21 @@ Luna88kPROMEmuDevice::CommandNvramNew2(c assert(nvram_key.size() <= 16); assert(nvram_val.size() <= 16); - text.CRLF(); + Putc('\n'); if (asciicode != 'y' && asciicode != 'Y') { - text.Print("** Not added\n"); + Print("** Not added\n"); return; } if (nvram->WriteString(nvram_offset, nvram_key) == false || nvram->WriteString(nvram_offset + 16, nvram_val) == false) { - text.Print("** Write error\n"); + Print("** Write error\n"); return; } WriteNVRAMCsum(); - text.Print("Added\n"); + Print("Added\n"); } // NVRAM 項目削除 @@ -590,13 +699,13 @@ Luna88kPROMEmuDevice::CommandNvramDelete putmsg(1, "nvram Delete |%s|", key.c_str()); if (key.size() > 16) { - text.Print("** key too long\n"); + Print("** key too long\n"); return; } uint32 offset = FindNVRAM(key); if (offset == 0) { - text.Print("** Not found\n"); + Print("** Not found\n"); return; } @@ -639,15 +748,15 @@ Luna88kPROMEmuDevice::CommandNvramInit2( { assert(prompt_idx == PROMPT_NVRAM_INIT); - text.CRLF(); + Putc('\n'); if (asciicode != 'y' && asciicode != 'Y') { - text.Print("** Not initialized\n"); + Print("** Not initialized\n"); return; } InitNVRAM(); - text.Print("** Initialized\n"); + Print("** Initialized\n"); } // NVRAM を初期化する @@ -693,3 +802,49 @@ Luna88kPROMEmuDevice::GetNVRAM(bootinfo_ putmsg(1, "%s part=%c", __func__, bootinfo.dkpart + 'a'); putmsg(1, "%s file=|%s|", __func__, bootinfo.dkfile.c_str()); } + +// コールバック関数 (C のグローバル関数) +bool +luna88k_fline_callback(m88kcpu *cpu, void *arg) +{ + Luna88kPROMEmuDevice *dev = (Luna88kPROMEmuDevice *)arg; + return dev->FLineCallback(cpu); +} + +// コールバック関数 (こっちが実体) +bool +Luna88kPROMEmuDevice::FLineCallback(m88kcpu *cpu) +{ + // 111111_DDDDDSSSSS_000001_00nnnnnnnn + // + // 引数は rS1 から順番に格納。 + // m68k 的なバイト値やワード値の場合は下位詰めしてあるが、不要な上位 + // ビットの処置はゲストコードよりホストコードで行うほうが楽なので、 + // すべてこちらで責任持って処理すること。 + // 戻り値がある DOS call では戻り値は rD に格納。 + + uint32 D = (cpu->opX >> 21) & 0x1f; + uint32 S1 = (cpu->opX >> 16) & 0x1f; + uint32 num = cpu->opX & 0xff; + + switch (num) { + case 0x00: // _EXIT + break; + + case 0x01: // _GETCHAR + cpu->r[D] = getchar(); + break; + + case 0x02: // _PUTCHAR + Putc(cpu->r[S1++] & 0xff); + break; + + default: + putlog(0, "未実装 DOS call #$%02x", num); + cpu->r[D] = 0xffffffff; + break; + } + + // 存在しないコール番号、未実装コールも含めて処理済みとする。 + return true; +}