|
|
1.1 root 1: //
2: // nono
1.1.1.2 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
1.1.1.13! root 7: //
1.1.1.7 root 8: // LUNA シリーズの ROM エミュレーション共通部分
1.1.1.10 root 9: //
1.1.1.13! root 10:
1.1.1.10 root 11: // IODevice
12: // |
13: // +-- ROMDevice (LoadROM()、ウェイト、マスク等を持つ)
14: // | |
15: // | +-- PROMDevice (LUNA* の PROM)
16: // | +-- IPLROM1Device (X680x0 の IPLROM 後半)
17: // | +-- IPLROM2Device (X680x0 の IPLROM 前半)
18: // | +-- CGROMDevice (X680x0 の CGROM)
19: // | |
20: // | | +-------------------+
21: // | +--| LunaPROMEmuDevice | (LUNA PROM エミュレーションの共通部分)
22: // | +-------------------+
23: // | |
24: // | +-- Luna1PROMEmuDevice (LUNA1 の PROM エミュレーション)
25: // | +-- Luna88kPROMEmuDevice (LUNA88K の PROM エミュレーション)
26: // |
27: // +-- PROM0Device (LUNA* のブートページ切り替え用プロキシ)
28: // +-- IPLROM0Device (X680x0 のブートページ切り替え用プロキシ)
1.1.1.2 root 29:
30: #include "romemu_luna.h"
1.1 root 31: #include "bt454.h"
1.1.1.9 root 32: #include "lunafb.h"
1.1 root 33: #include "mainapp.h"
34: #include "mk48t02.h"
1.1.1.13! root 35: #include "mpu.h"
! 36: #include "prom.h"
1.1 root 37: #include "ram.h"
38: #include "sio.h"
39: #include "spc.h"
1.1.1.7 root 40: #include "ufs.h"
1.1 root 41:
42: // コンストラクタ
43: LunaPROMEmuDevice::LunaPROMEmuDevice()
1.1.1.9 root 44: : inherited("PROM")
1.1 root 45: {
1.1.1.10 root 46: // LUNA では ROM への書き込みは何も起きない
47: write_op = 0;
1.1 root 48: }
49:
50: // デストラクタ
51: LunaPROMEmuDevice::~LunaPROMEmuDevice()
52: {
1.1.1.13! root 53: gPROM = NULL;
1.1 root 54: }
55:
56: bool
57: LunaPROMEmuDevice::Init()
58: {
59: // 都度都度 dynamic_cast は面倒なので、ここで一度代入しとく
1.1.1.13! root 60: nvram = dynamic_cast<MK48T02Device*>(gRTC);
1.1 root 61:
62: return true;
63: }
64:
1.1.1.2 root 65: uint64
1.1 root 66: LunaPROMEmuDevice::Read32(uint32 addr)
67: {
1.1.1.10 root 68: // ROM からのロングワードアクセスでだけ謎の I/O 空間が見える。
69: // addr をマスクする前に評価すること。
1.1.1.9 root 70: if ((gMPU->GetPaddr() & 0xff000000) == baseaddr) {
71: switch (addr) {
72: case ROMIO_INIT:
1.1.1.2 root 73: return ROM_Init();
1.1 root 74:
1.1.1.9 root 75: case ROMIO_KEYIN:
1.1.1.7 root 76: ROM_Keyin();
77: return 0;
1.1 root 78:
1.1.1.9 root 79: case ROMIO_CLOSE:
1.1.1.7 root 80: ROM_Close();
81: return 0;
1.1 root 82:
1.1.1.9 root 83: case ROMIO_ENTRY:
1.1.1.7 root 84: return ROM_Entry();
1.1.1.6 root 85:
1.1 root 86: default:
87: break;
88: }
89: }
1.1.1.2 root 90:
1.1.1.10 root 91: return inherited::Read32(addr);
1.1 root 92: }
93:
1.1.1.7 root 94: // デバイスの初期化の共通部分。(継承クラスの ROM_Init() から呼ぶ)
95: void
96: LunaPROMEmuDevice::InitDevice()
1.1 root 97: {
98: // 初期パレット
1.1.1.4 root 99: // 偶数番は白(R/G/B=15/15/15)、奇数番は黒(0/0/0)。
100: uint8 val = 0xf0;
1.1.1.7 root 101: gBT454->Write(0, 0); // select palette
1.1.1.4 root 102: for (int i = 0; i < 16; i++) {
1.1.1.7 root 103: gBT454->Write(1, val); // R
104: gBT454->Write(1, val); // G
105: gBT454->Write(1, val); // B
1.1.1.4 root 106: val ^= 0xf0;
107: }
1.1 root 108:
109: // SCSI
110: // BDID の書き込みによってホストデバイスがバスにアタッチされる構造なので。
1.1.1.7 root 111: gSPC->Write(SPC::BDID, 7);
1.1 root 112:
1.1.1.7 root 113: // キーボード(chB)を初期化
114: gSIO->Write(3, 0x01); // CR1
115: gSIO->Write(3, 0x10); // RX Int(all char)
116: gSIO->Write(3, 0x04); // CR4
117: gSIO->Write(3, 0x44); // Stop 1bit
1.1.1.13! root 118: gSIO->Write(3, 0x03); // CR3
! 119: gSIO->Write(3, 0xc1); // RX Enable, 8bit
1.1.1.7 root 120: gSIO->Write(3, 0x05); // CR5
121: gSIO->Write(3, 0x68); // TX Enable, 8bit
1.1.1.12 root 122:
123: // LED を(明示的に)オフ、マウスサンプリングをオフ
124: gSIO->Write(2, 0x00);
125: gSIO->Write(2, 0x01);
126: gSIO->Write(2, 0x20);
1.1.1.7 root 127: }
1.1.1.2 root 128:
1.1.1.7 root 129: // スクリーンを初期化
130: void
1.1.1.13! root 131: LunaPROMEmuDevice::InitScreen()
1.1.1.7 root 132: {
1.1.1.13! root 133: gLunafb->EmuInitScreen();
! 134:
! 135: screen_w = 80;
! 136: screen_h = 32;
! 137: cursor_x = 0;
! 138: cursor_y = 0;
! 139:
! 140: // 表示範囲はざっくりセンタリングする
! 141: origin_px = (1280 - screen_w * 12) / 2;
! 142: origin_py = (1024 - screen_h * 24) / 2;
! 143: // 横は 48ドット境界から始めておく。
! 144: // こうすると1(,2)文字目の24ビットが必ずワード境界から始まるため。
! 145: // 今となっては任意の場所から文字が描画できるようになったので揃える
! 146: // 必要はなくなったが、依然このほうがアクセス効率はいいので。
! 147: origin_px = (origin_px / 48) * 48;
! 148:
1.1.1.2 root 149: inputbuf.clear();
150: inputpos = 0;
151: is_shift = false;
1.1.1.3 root 152: mousecnt = 0;
1.1.1.13! root 153: }
1.1 root 154:
1.1.1.13! root 155: // タイトルを出力
! 156: void
! 157: LunaPROMEmuDevice::PrintTitle(const char *machine_name)
! 158: {
! 159: Print("NONO %d.%d.%d Emulated ROM Monitor for %s\n\n",
1.1.1.7 root 160: NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER, machine_name);
1.1.1.13! root 161: }
! 162:
! 163: // 1文字出力
! 164: void
! 165: LunaPROMEmuDevice::Putc(uint ch)
! 166: {
! 167: uint px = origin_px + cursor_x * 12;
! 168: uint py = origin_py + cursor_y * 24;
! 169:
! 170: // カーソル位置の反転を元に戻す
! 171: gLunafb->EmuPutc(px, py, ' ', ROP::INV2);
! 172:
! 173: if (ch == LF) {
! 174: put_LF:
! 175: cursor_x = 0;
! 176: if (cursor_y < screen_h - 1) {
! 177: cursor_y++;
! 178: } else {
! 179: // 上スクロールして..
! 180: gLunafb->EmuScrollY(origin_py, origin_py + 24, (screen_h - 1) * 24);
! 181: // 最終行を消す
! 182: gLunafb->EmuFill(0, py, 1280, 24, 0);
! 183: }
! 184: } else if (ch == LEFT) {
! 185: if (cursor_x > 0) {
! 186: cursor_x--;
! 187: } else {
! 188: cursor_y--;
! 189: cursor_x = screen_w - 1;
! 190: }
! 191: } else if (ch == RIGHT) {
! 192: if (cursor_x < screen_w - 1) {
! 193: cursor_x++;
! 194: } else {
! 195: cursor_y++;
! 196: cursor_x = 0;
! 197: }
! 198: } else {
! 199: if (cursor_x < screen_w - 1) {
! 200: gLunafb->EmuPutc(px, py, ch);
! 201: cursor_x++;
! 202: } else {
! 203: gLunafb->EmuPutc(px, py, ch);
! 204: goto put_LF;
! 205: }
! 206: }
! 207:
! 208: // カーソル位置を再計算して描画 (文字を反転)
! 209: px = origin_px + cursor_x * 12;
! 210: py = origin_py + cursor_y * 24;
! 211: gLunafb->EmuPutc(px, py, ' ', ROP::INV2);
! 212: }
! 213:
! 214: // 文字列 s を出力
! 215: void
! 216: LunaPROMEmuDevice::Print(const std::string& s)
! 217: {
! 218: for (auto *p = s.c_str(); *p; p++) {
! 219: Putc(*p);
! 220: }
! 221: }
! 222:
! 223: // 文字列 fmt... を出力
! 224: void
! 225: LunaPROMEmuDevice::Print(const char *fmt, ...)
! 226: {
! 227: char buf[1024];
! 228: va_list ap;
! 229:
! 230: va_start(ap, fmt);
! 231: vsnprintf(buf, sizeof(buf), fmt, ap);
! 232: va_end(ap);
! 233:
! 234: for (char *p = buf; *p; p++) {
! 235: Putc(*p);
! 236: }
1.1 root 237: }
238:
1.1.1.7 root 239: // ROM 処理のクローズ
240: void
1.1.1.2 root 241: LunaPROMEmuDevice::ROM_Close()
1.1 root 242: {
1.1.1.11 root 243: // SIO をリセットしておく。
244: // SIO のバッファにデータが残っていると、NetBSD のブートローダが
245: // 起動してこないようだ。リセットして RXEN を落としておけば大丈夫ぽい。
1.1.1.13! root 246: gSIO->ResetHard(false);
1.1 root 247: }
248:
1.1.1.2 root 249: // ROM プロンプトのキー入力。
1.1.1.7 root 250: // ゲストコードの割り込みハンドラから呼ばれて、キー入力で行を構成し、
251: // あるいは行が完成すればコマンドを実行する。
252: // 実行結果により画面を更新したりメンバ変数を更新する。
253: void
1.1.1.2 root 254: LunaPROMEmuDevice::ROM_Keyin()
1.1 root 255: {
1.1.1.7 root 256: uint32 lunakey = gSIO->Read(2);
1.1 root 257:
1.1.1.3 root 258: // マウス入力の 2, 3バイト目は無視
259: if (mousecnt > 0) {
260: mousecnt--;
1.1.1.7 root 261: return;
1.1.1.3 root 262: }
263:
1.1.1.2 root 264: // SHIFT キーだけ状態を持つので先に処理
265: if (lunakey == 0x0c || lunakey == 0x0d) { // 左右SHIFT押下
266: is_shift = true;
1.1.1.7 root 267: return;
1.1.1.2 root 268: }
269: if (lunakey == 0x8c || lunakey == 0x8d) { // 左右SHIFT開放
1.1 root 270: is_shift = false;
1.1.1.7 root 271: return;
1.1.1.2 root 272: }
1.1.1.3 root 273: // マウスデータの1バイト目なら、後続2バイトをスキップ
274: if ((lunakey & 0xf8) == 0x80) {
275: mousecnt = 2;
1.1.1.7 root 276: return;
1.1.1.3 root 277: }
278: // キー開放は無視してよい
1.1.1.2 root 279: if ((lunakey & 0x80)) {
1.1.1.7 root 280: return;
1.1.1.2 root 281: }
1.1 root 282:
1.1.1.9 root 283: // キーコードから文字を取得
284: char asciicode;
285: if (is_shift) {
286: asciicode = lunakey2shifttable[lunakey];
287: } else {
288: asciicode = lunakey2asciitable[lunakey];
289: }
290: if (asciicode != 0) {
291: GetChar(asciicode);
292: }
293: }
294:
295: // キー入力ハンドラ (キー入力割り込みによって呼ばれる)。
296: // 引数 asciicode は概ね ASCII コード (ただし 0x1c..0x1f が上下左右)。
297: void
298: LunaPROMEmuDevice::GetChar(char asciicode)
299: {
300: // LUNA1 の場合、
301: // 実機 PROM にはカーソル左右移動の機能はなく、カーソルは常に行末にいる。
302: // そのためかどうか Delete も BackSpace と同じく1文字前を消す動作をして
303: // いる。カーソル位置に文字はないので、Delete をそうするのはまあ分からん
304: // でもない。
305: // 一方、エミュレーション ROM では、カーソルの左右移動をサポートしてみた
306: // ため Delete を「カーソル位置の文字を消す」にすることは出来るが、それは
307: // それで実機と乖離が広がるので、どうしたもんか。
308:
309: switch (asciicode) {
310: case BS:
311: if (inputbuf.empty() == false && inputpos != 0) {
312: inputbuf.erase(--inputpos, 1);
1.1.1.13! root 313: Putc(LEFT);
! 314: for (int i = inputpos; i < inputbuf.size(); i++) {
! 315: Putc(inputbuf[i]);
! 316: }
! 317: Putc(' ');
! 318: for (int i = 0; i < 1 + inputbuf.size() - inputpos; i++) {
! 319: Putc(LEFT);
! 320: }
1.1 root 321: }
1.1.1.2 root 322: break;
1.1.1.9 root 323:
324: case LF:
1.1.1.2 root 325: // 改行
1.1.1.13! root 326: Putc(asciicode);
! 327: prompt_y = cursor_y;
1.1.1.9 root 328: // コマンド実行
1.1.1.7 root 329: Command();
1.1.1.9 root 330: // プロンプトを更新
331: ClearPrompt();
1.1.1.2 root 332: break;
1.1.1.9 root 333:
334: case LEFT:
1.1.1.2 root 335: if (inputpos > 0) {
336: inputpos--;
1.1.1.13! root 337: Putc(LEFT);
1.1 root 338: }
1.1.1.2 root 339: break;
1.1.1.9 root 340: case RIGHT:
1.1.1.2 root 341: if (inputpos < inputbuf.length()) {
1.1 root 342: inputpos++;
1.1.1.13! root 343: Putc(RIGHT);
1.1 root 344: }
345: break;
1.1.1.9 root 346:
347: case UP:
348: case DOWN:
349: case 0: // キー割り当てなし、これは来ないはずだが一応
350: return;
351:
352: default: // 通常文字のはず
1.1.1.2 root 353: inputbuf.insert(inputbuf.begin() + inputpos, asciicode);
1.1.1.13! root 354: for (int i = inputpos; i < inputbuf.size(); i++) {
! 355: Putc(inputbuf[i]);
! 356: }
! 357: for (int i = 0; i < inputbuf.size() - inputpos - 1; i++) {
! 358: Putc(LEFT);
! 359: }
1.1.1.2 root 360: inputpos++;
361: break;
1.1 root 362: }
1.1.1.2 root 363: }
1.1 root 364:
1.1.1.9 root 365: // 入力バッファを初期化する。
366: void
367: LunaPROMEmuDevice::ClearPrompt()
368: {
1.1.1.13! root 369: prompt_y = cursor_y;
1.1.1.9 root 370: // 入力行をクリア
371: inputbuf.clear();
372: inputpos = 0;
373:
1.1.1.13! root 374: Print(prompt);
1.1.1.9 root 375: }
376:
377: // NVRAM のチェックサムを返す
378: uint8
379: LunaPROMEmuDevice::CalcNVRAMCsum() const
380: {
381: uint8 eor = 0;
382: for (uint32 addr = 0x20; addr < 0x560; addr++) {
383: eor ^= nvram->Peek(addr);
384: }
385: return eor;
386: }
387:
388: // NVRAM のチェックサムを計算して書き込む
389: void
390: LunaPROMEmuDevice::WriteNVRAMCsum()
391: {
392: uint8 eor = CalcNVRAMCsum();
393:
394: nvram->Write(0x001c, eor);
395: nvram->Write(0x001d, eor);
396: nvram->Write(0x001e, eor);
397: }
398:
399: // NVRAM のマジックとチェックサムを照合する
400: bool
401: LunaPROMEmuDevice::VerifyNVRAM() const
402: {
403: if (nvram->PeekString(0x0004) != "<nv>") {
404: return false;
405: }
406:
407: uint8 eor = CalcNVRAMCsum();
408: for (uint32 addr = 0x1c; addr < 0x1f; addr++) {
409: if (nvram->Peek(addr) != eor) {
410: return false;
411: }
412: }
413: return true;
1.1 root 414: }
415:
1.1.1.10 root 416: // -X オプションで指定されたホストファイルをロードする。
1.1 root 417: // ロードできればエントリポイントを返す。
1.1.1.10 root 418: // exec_file が指定されている時に呼ぶこと。
1.1 root 419: uint32
420: LunaPROMEmuDevice::LoadHostFile()
421: {
1.1.1.10 root 422: assert(gMainApp.exec_file);
1.1 root 423:
1.1.1.10 root 424: uint32 entry = gRAM->LoadFile(gMainApp.exec_file);
1.1 root 425: if (entry) {
1.1.1.13! root 426: Print("Host program loaded. Entry point = $%08x\n", entry);
1.1 root 427: } else {
1.1.1.13! root 428: Print("** Couldn't load specified host program.\n");
1.1 root 429: }
430: return entry;
431: }
432:
1.1.1.7 root 433: // bootinfo に従ってゲストから起動先をロードする。
1.1 root 434: // 成功すればエントリポイントを返す。
435: // 失敗ならをエラーメッセージを errmsg にセットして 0 を返す。
436: uint32
1.1.1.7 root 437: LunaPROMEmuDevice::LoadGuestFile(const bootinfo_t& bootinfo)
1.1 root 438: {
1.1.1.7 root 439: const int& dkpart = bootinfo.dkpart;
440: const int& dkunit = bootinfo.dkunit;
441: const std::string& dkfile = bootinfo.dkfile;
1.1 root 442:
443: // ユニット (今は SCSI ID=6 決め打ち)
444: int id = 6;
445: SCSITarget *target = gSPC->GetTarget(id);
446: if (target == NULL) {
447: errmsg = "No bootable disks";
448: putmsg(0, "%s", errmsg.c_str());
449: return 0;
450: }
1.1.1.3 root 451: if (target->GetDevType() != SCSI::DevType::HD) {
1.1 root 452: errmsg = "No bootable disks";
453: putmsg(0, "%s", errmsg.c_str());
454: return 0;
455: }
456:
1.1.1.10 root 457: SCSIDisk *hd = dynamic_cast<SCSIDisk*>(target);
1.1 root 458: putmsg(2, "%s SCSIHD[%d] found", __func__, id);
459:
460: // +0 セクタ目(512byte)に Sun disklabel
461: scd_dk_label dk;
1.1.1.10 root 462: if (hd->Peek(&dk, 0, sizeof(dk)) == false) {
1.1 root 463: putmsg(0, "SCSIHD[%d] ReadSector failed", id);
464: errmsg = string_format("dk%d Read disklabel failed.", dkunit);
465: return 0;
466: }
467:
468: // +$1fc の MAGIC をチェック
469: uint32 magic = be16toh(dk.magic);
470: if (magic != scd_dk_label::DKL_MAGIC) {
471: putmsg(0, "SCSIHD[%d] Bad magic 0x%04x (!= 0x%x)",
472: id, magic, scd_dk_label::DKL_MAGIC);
473: errmsg = string_format("dk%d Bad disklabel magic.", dkunit);
474: return 0;
475: }
476:
477: // チェックサム照合は手抜きで省略
478:
479: // 指定パーティションの開始位置とサイズ
480: if (loglevel >= 2) {
481: // デバッグ用に全部表示
482: for (int i = 0; i < 8; i++) {
1.1.1.7 root 483: putmsgn("%s partition %c: { start=%8u size=%8u }", __func__,
1.1 root 484: 'a' + i,
485: be32toh(dk.map[i].blkno),
486: be32toh(dk.map[i].nblk));
487: }
488: }
489: uint32 part_start = be32toh(dk.map[dkpart].blkno);
490: uint32 part_size = be32toh(dk.map[dkpart].nblk);
491: putmsg(1, "%s partition=%c, start=%u size=%u", __func__,
492: dkpart + 'a', part_start, part_size);
493:
494: // ファイルシステムをオープン (マウントっぽいイメージ)
495: Filesys fsys(this);
1.1.1.7 root 496: if (fsys.Mount(hd, part_start, part_size) == false) {
497: errmsg = string_format("dk%d,%c Open ffs failed: %s",
498: dkunit, dkpart + 'a', fsys.errstr.c_str());
499: putmsg(0, "%s: %s", __func__, errmsg.c_str());
1.1 root 500: return 0;
501: }
502:
1.1.1.7 root 503: // ファイルを探してオープンする
504: inodefile f(this);
505: if (fsys.OpenFile(f, dkfile) == false) {
506: errmsg = string_format("dk%d,%c,%s open failed: %s",
507: dkunit, dkpart + 'a', dkfile.c_str(), fsys.errstr.c_str());
508: putmsg(0, "%s: %s", __func__, errmsg.c_str());
1.1 root 509: return 0;
510: }
1.1.1.7 root 511:
1.1 root 512: // ファイル本体を読み込み
1.1.1.7 root 513: fsys.ReadData(f);
1.1 root 514:
515: // ロード
516: uint32 entry;
517: std::string name = string_format("SCSIHD %d,%c:%s",
1.1.1.7 root 518: id, dkpart + 'a', dkfile.c_str());
519: entry = gRAM->LoadFile(name.c_str(), f.data.data(), f.data.size());
520: if (entry == 0) {
521: errmsg = string_format("dk%d,%c,%s not executable",
522: dkunit, dkpart + 'a', dkfile.c_str());
1.1 root 523: }
524:
1.1.1.7 root 525: return entry;
1.1 root 526: }
527:
1.1.1.7 root 528: // キーコードから文字への変換
529: /*static*/ const uint8
530: LunaPROMEmuDevice::lunakey2asciitable[] = {
1.1.1.9 root 531: 0, 0, 0, 0, 0, 0, 0, 0,
532: 0, 0, 0, 0, 0, 0, 0, 0,
533: 0, BS, LF, 0, ' ', 0, 0, 0,
534: 0, 0, 0, 0, UP,LEFT,RIGHT,DOWN,
1.1.1.7 root 535: 0, 0, '1', '2', '3', '4', '5', '6',
536: '7', '8', '9', '0', '-', '^', '\\', 0,
537: 0, 0, 'q', 'w', 'e', 'r', 't', 'y',
538: 'u', 'i', 'o', 'p', '@', '[', 0, 0,
539: 0, 0, 'a', 's', 'd', 'f', 'g', 'h',
540: 'j', 'k', 'l', ';', ':', ']', 0, 0,
541: 0, 0, 'z', 'x', 'c', 'v', 'b', 'n',
542: 'm', ',', '.', '/', 0, 0, 0, 0,
1.1.1.13! root 543: 0, '+', '-', '7', '8', '9', '4', '5',
! 544: '6', '1', '2', '3', '0', '.', LF, 0,
! 545: 0, 0, 0, 0, 0, 0, 0, 0,
! 546: 0, 0, 0, 0, '*', '/', '=', ',',
1.1.1.7 root 547: };
548: /*static*/ const uint8
549: LunaPROMEmuDevice::lunakey2shifttable[] = {
1.1.1.9 root 550: 0, 0, 0, 0, 0, 0, 0, 0,
551: 0, 0, 0, 0, 0, 0, 0, 0,
552: 0, BS, LF, 0, ' ', 0, 0, 0,
553: 0, 0, 0, 0, UP,LEFT,RIGHT,DOWN,
554: 0, 0, '!', '\"','#', '$', '%', '&',
555: '\'','(', ')', ' ', '=', '~', '|', 0, // XXX SHIFT+'0'は?
1.1.1.7 root 556: 0, 0, 'Q', 'W', 'E', 'R', 'T', 'Y',
557: 'U', 'I', 'O', 'P', '`', '{', 0, 0,
558: 0, 0, 'A', 'S', 'D', 'F', 'G', 'H',
559: 'J', 'K', 'L', '+', '*', '}', 0, 0,
560: 0, 0, 'Z', 'X', 'C', 'V', 'B', 'N',
561: 'M', '<', '>', '?', '_', 0, 0, 0,
1.1.1.13! root 562: 0, '+', '-', '7', '8', '9', '4', '5',
! 563: '6', '1', '2', '3', '0', '.', LF, 0,
! 564: 0, 0, 0, 0, 0, 0, 0, 0,
! 565: 0, 0, 0, 0, '*', '/', '=', ',',
1.1.1.7 root 566: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.