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

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

unix.superglobalmegacorp.com

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