Annotation of nono/vm/romemu_luna.cpp, revision 1.1.1.10

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.