|
|
1.1 root 1: //
2: // nono
1.1.1.4 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
1.1.1.10 root 7: //
8: // X68k キーボード
9: //
10:
1.1.1.5 root 11: // 送信タイミングについて
12: // キーボードは MFP の USART に繋がっている。
13: // MFP USART の RR 線 (Receiver Ready) は受信可能なら 'H'、受信中は 'L' に
14: // なっている。キーボードは RR が 'H' の時だけデータを送信する。また、
15: // システムポート#4 の KEY CTRL = 0 (キーデータ送信不可) はこの RR を
16: // 'L' に落とす AND 回路になっている。キーボードからの送信は 2400bps、
17: // ストップビット1、パリティなしなので、1バイトの送信に 3.75 msec かかる。
18: // 実装では、キーボードは MFP の RR が 'H' なら MFP にキーデータを送信。
19: // MFP 側で RR を 'L' にして 3.75 msec をイベントで待った後、キーを処理
20: // して RR を 'H' に戻すことにする。
21:
22: // SHIFT キーについては keyboard.cpp のコメントを参照。
1.1.1.4 root 23:
1.1 root 24: #include "x68kkbd.h"
25: #include "mfp.h"
1.1.1.7 root 26: #include "scc.h"
1.1.1.10 root 27: #include "scheduler.h"
1.1.1.5 root 28: #include "sysport.h"
1.1 root 29:
30: // コンストラクタ
31: X68030Keyboard::X68030Keyboard()
32: {
1.1.1.4 root 33: led.resize(7);
1.1.1.6 root 34:
1.1.1.10 root 35: rept_event.func = ToEventCallback(&X68030Keyboard::ReptCallback);
1.1.1.6 root 36: rept_event.time = 0;
1.1.1.10 root 37: rept_event.Regist("Keyboard Repeat");
1.1.1.7 root 38:
1.1.1.10 root 39: mouse_event.func = ToEventCallback(&X68030Keyboard::MouseCallback);
40: mouse_event.Regist("Mouse");
1.1.1.11! root 41:
! 42: monitor.func = ToMonitorCallback(&X68030Keyboard::MonitorUpdate);
! 43: monitor.SetSize(47, 5);
! 44: monitor.Regist(ID_MONITOR_KEYBOARD);
1.1.1.3 root 45: }
46:
47: // デストラクタ
48: X68030Keyboard::~X68030Keyboard()
49: {
50: }
51:
1.1.1.10 root 52: // キーボード接続時の MD 固有処理
1.1.1.4 root 53: void
1.1.1.10 root 54: X68030Keyboard::MDConnect()
1.1.1.3 root 55: {
1.1.1.11! root 56: rept_delay_data = 3;
1.1.1.6 root 57: rept_delay = 500_msec;
1.1.1.11! root 58: rept_time_data = 4;
1.1.1.6 root 59: rept_time = 110_msec;
60: rept_key = KC_none;
1.1.1.10 root 61: gScheduler->StopEvent(rept_event);
1.1.1.4 root 62:
63: led_darkness = 0;
1.1.1.7 root 64:
1.1.1.10 root 65: // XXX マウスの接続取り外しは未定
66: gScheduler->StopEvent(mouse_event);
67: mouse_event.code = 0;
68: }
69:
70: // キーボード取り外し時の MD 固有処理
71: void
72: X68030Keyboard::MDDisconnect()
73: {
74: gScheduler->StopEvent(rept_event);
75: gScheduler->StopEvent(mouse_event);
76: mouse_event.code = 0;
1.1 root 77: }
78:
1.1.1.11! root 79: // モニター
! 80: void
! 81: X68030Keyboard::MonitorUpdate(Monitor *, TextScreen& screen)
! 82: {
! 83: int y;
! 84:
! 85: screen.Clear();
! 86: y = 0;
! 87:
! 88: // 0 1 2 3 4
! 89: // 01234567890123456789012345678901234567890123456
! 90: // Key Enable: % MSCTRL: %
! 91: // Ctrl Enable: OPT.2 Enable: %
! 92: // Key Repeat: Delay $x(????msec)
! 93: // Repeat $x(????msec)
! 94: // LED: Zen Hira INS CAPS Code Roma Kana ($xx)
! 95: // LED Darkness: x
! 96:
! 97: // KEY_EN, CTRL_EN, OPT2_EN, MSCTRL は未実装
! 98:
! 99: // キーリピート
! 100: screen.Puts(0, y, "Key Repeat:");
! 101: screen.Print(12, y++, "Delay $%x(%4dmsec)",
! 102: rept_delay_data, (int)(rept_delay / 1_msec));
! 103: screen.Print(12, y++, "Repeat $%x(%4dmsec)",
! 104: rept_time_data, (int)(rept_time / 1_msec));
! 105:
! 106: // マウス
! 107: int mx = mouse_x;
! 108: int my = mouse_y;
! 109: if (mx < -999)
! 110: mx = -999;
! 111: if (mx > 999)
! 112: mx = 999;
! 113: if (my < -999)
! 114: my = -999;
! 115: if (my > 999)
! 116: my = 999;
! 117:
! 118: screen.Puts(0, y, "Mouse:");
! 119: screen.Print(12, y, "x=%-4d y=%-4d", mx, my);
! 120: screen.Print(27, y, TA::OnOff(mouse_l), "L");
! 121: screen.Print(29, y, TA::OnOff(mouse_r), "R");
! 122: y++;
! 123:
! 124: // LED
! 125: screen.Puts(0, y, "LED:");
! 126: static const char * const ledname[] = {
! 127: "Zen", "Hira", "INS", "CAPS", "Code", "Roma", "Kana"
! 128: };
! 129: uint val = 0x80;
! 130: for (int i = 0; i < 7; i++) {
! 131: int n = 6 - i;
! 132: screen.Puts(7 + i * 5, y, TA::OnOff(led[n]), ledname[i]);
! 133: if (led[n] == 0) {
! 134: val |= 1 << n;
! 135: }
! 136: }
! 137: screen.Print(42, y, "($%02x)", val);
! 138: y++;
! 139:
! 140: // LED の明るさ (というより暗さ)
! 141: screen.Print(0, y++, "LED Darkness: %d", led_darkness);
! 142: }
! 143:
1.1.1.9 root 144: // 共通 keystat を X680x0 キーコードに変換して返す。
1.1.1.5 root 145: // 対応する X680x0 のキーがなければ 0 を返す。
146: uint
1.1.1.9 root 147: X68030Keyboard::KeyToMDKey(uint keystat) const
1.1 root 148: {
1.1.1.9 root 149: uint keycode = KC_CODE(keystat);
1.1.1.5 root 150: if (keycode >= countof(keycode2x68kkey_table)) {
151: return 0;
152: }
1.1 root 153:
1.1.1.9 root 154: uint mdkey = keycode2x68kkey_table[keycode];
155: if (mdkey && KC_IS_BREAK(keystat)) {
156: mdkey |= 0x80;
157: }
158: return mdkey;
1.1.1.5 root 159: }
160:
161: // シフト状態なら true を返す
162: bool
163: X68030Keyboard::IsShift() const
164: {
165: return pressed[KC_SHIFT];
166: }
1.1 root 167:
1.1.1.6 root 168: // キー入力を1つ処理する
169: void
170: X68030Keyboard::KeyInput(uint keystat)
1.1.1.5 root 171: {
1.1.1.6 root 172: uint keycode = KC_CODE(keystat);
173: bool ismake = KC_IS_MAKE(keystat);
174:
175: assert(keycode < KC_max);
176:
1.1.1.5 root 177: // X68k のキーボードは左右の [SHIFT] の区別がない。
178: // KC_SHIFT_L はホストの物理キー左 [SHIFT] に対応、
179: // KC_SHIFT_R はホストの物理キー右 [SHIFT] に対応、
180: // KC_SHIFT はソフトウェアキーボードからの左右のない [SHIFT] に対応。
181: // どのキーコードで問い合わせをされても KC_SHIFT_L と KC_SHIFT_R の OR が
182: // [SHIFT] キーの状態である。
183: if (keycode == KC_SHIFT_L || keycode == KC_SHIFT_R) {
184: // KC_SHIFT_L, R に対する pressed は内部の OR 処理用
1.1.1.6 root 185: pressed[keycode] = ismake;
1.1.1.5 root 186: if (keycode == KC_SHIFT_L && pressed[KC_SHIFT_R]) {
1.1.1.6 root 187: return;
1.1.1.5 root 188: }
189: if (keycode == KC_SHIFT_R && pressed[KC_SHIFT_L]) {
1.1.1.6 root 190: return;
1.1.1.5 root 191: }
1.1.1.6 root 192: keycode = KC_SHIFT;
1.1.1.8 root 193:
1.1.1.6 root 194: // keycode を変更したので keystat も更新
1.1.1.8 root 195: keystat = (keystat & 0xff00) | keycode;
1.1 root 196: }
1.1.1.5 root 197:
1.1.1.6 root 198: // 共通処理へ
199: if (KeyInputCommon(keystat) == false) {
200: return;
201: }
1.1 root 202:
1.1.1.4 root 203: // LED は MPU 側から点灯/消灯指示が来るのでここでは何もしなくてよい
1.1 root 204:
1.1.1.6 root 205: // ここでキーリピートのイベント開始処理
206: if (ismake) {
207: // いま起きているリピートは中止
208: rept_key = keycode;
209: rept_event.time = rept_delay;
1.1.1.10 root 210: gScheduler->RestartEvent(rept_event);
1.1.1.6 root 211: } else {
212: if (keycode == rept_key) {
213: // 同じキーのブレークでのみリピートを終了する
1.1.1.10 root 214: gScheduler->StopEvent(rept_event);
1.1.1.6 root 215: rept_key = KC_none;
216: }
217: }
218: }
1.1.1.5 root 219:
1.1.1.6 root 220: void
221: X68030Keyboard::ReptCallback(Event& ev)
222: {
223: // リピート
1.1.1.9 root 224: sendqueue.Enqueue(KeyToMDKey(rept_key));
1.1.1.6 root 225: SendStart();
226:
227: // 二回目以降のリピートは rept_time 間隔で発生
228: rept_event.time = rept_time;
1.1.1.10 root 229: gScheduler->StartEvent(rept_event);
1.1 root 230: }
1.1.1.5 root 231:
232: // MFP、システムポートと、キーボードの関係。
233: //
234: // キーボードの Ready 信号は MFP の RR とシステムポートの KeyCtrl の
235: // AND で作られているが、この AND 部分はキーボードが担当することにする。
236: // そのほうが都合がいいため。
237: //
238: // <- 本体 || キーボード ->
239: // MFP ||
240: // --------+ : || +-----
1.1.1.10 root 241: // | : |& || |
242: // RR|o-------|& || |
243: // --------+ : |&---------|T0 (Ready)
244: // +-----|& || |
245: // Sysport | : |& || |
1.1.1.5 root 246: // --------+ | : || |
247: // KeyCtrl|--+ : || +-----
248: // | : ||
249: // :エミュレータでは点線から右をキーボードが担当
1.1 root 250:
1.1.1.5 root 251: // キーデータを送信
1.1 root 252: void
1.1.1.5 root 253: X68030Keyboard::SendStart()
1.1 root 254: {
1.1.1.5 root 255: if (gMFP->RxIsReady()) {
256: // 本体が Ready ならここで送信
257: Ready();
258: } else {
259: // そうでない場合、ここでは処理は不要。
260: // MFP 側が受信可能になると Ready() がコールされ、そっちで処理される
1.1 root 261: }
262: }
263:
1.1.1.5 root 264: // MFP からの Ready 通知
265: // 実際にはキーボード側が MFP (を含む本体側) が Ready になるのを待っている
266: // のだが、待っているを作るのは大変なので。
1.1.1.2 root 267: void
1.1.1.5 root 268: X68030Keyboard::Ready()
1.1.1.2 root 269: {
1.1.1.5 root 270: if (gSysport->GetKeyCtrl()) {
271: // KEYCTRL が有効なら処理する
272:
1.1.1.9 root 273: // ここで取り出せるのは MDKey。
274: uint x68kkey;
275: if (sendqueue.Dequeue(&x68kkey)) {
1.1.1.5 root 276: // あれば送信
277:
278: // 呼ばれたからには送信できるはず
279: assert(gMFP->RxIsReady() == true);
280:
281: assert(x68kkey != 0);
282:
283: // 送信
284: gMFP->Rx(x68kkey);
285: } else {
286: // なければ何もせず終了
287: }
288: } else {
289: // KEYCTRL が無効なら何もしないだけでなく、キューをクリア。
290: // KEYCTRL が無効から有効になった時に、無効だった期間のキーデータを
291: // 吐き出すのはおかしいので。
292: sendqueue.Clear();
293: }
1.1.1.2 root 294: }
295:
1.1.1.5 root 296: // マウス入力
1.1.1.4 root 297: void
1.1.1.5 root 298: X68030Keyboard::MouseInput(int x, int y, bool rb, bool lb, bool mb)
1.1.1.4 root 299: {
1.1.1.7 root 300: // ホストからマウスイベントのたびに呼ばれるので、ここで積算。
301: // 20msec ごとのサンプリングのタイミングで積算結果を処理する。
302: // X68k のマウスの Y 方向は下に動く方向がプラス。
303: mouse_x += x;
304: mouse_y += y;
305: mouse_r = rb;
306: mouse_l = lb;
307: }
308:
309: // マウスデータを VM に送信
310: void
311: X68030Keyboard::MouseSendStart()
312: {
313: uint8 status = 0;
314:
1.1.1.10 root 315: // 最後のバイトを送るまでは、次は受け付けられない。
316: // 送信中かどうかは mouse_event.code で判断している。
317: if (mouse_event.code != 0) {
1.1.1.7 root 318: return;
319: }
320:
321: if (mouse_x > 127) {
322: status |= XOVFL;
323: mouse_x = 127;
324: }
325: if (mouse_x < -128) {
326: status |= XUNFL;
327: mouse_x = -128;
328: }
329: if (mouse_y > 127) {
330: status |= YOVFL;
331: mouse_y = 127;
332: }
333: if (mouse_y < -128) {
334: status |= YUNFL;
335: mouse_y = -128;
336: }
337: status |= mouse_l ? SW_L : 0;
338: status |= mouse_r ? SW_R : 0;
339:
1.1.1.10 root 340: // この時点でラッチしてみる(?)
341: mouse_data[0] = status;
342: mouse_data[1] = mouse_x;
343: mouse_data[2] = mouse_y;
1.1.1.7 root 344:
345: mouse_x = 0;
346: mouse_y = 0;
347:
1.1.1.10 root 348: // 1バイト目は MSCTRL が立ち下がってから 700us 以上後から送信。
349: mouse_event.time = 700_usec;
350: mouse_event.code = 1;
351: gScheduler->RestartEvent(mouse_event);
1.1.1.7 root 352: }
353:
1.1.1.10 root 354: // マウスデータのイベントコールバック
1.1.1.7 root 355: void
356: X68030Keyboard::MouseCallback(Event& ev)
357: {
1.1.1.10 root 358: // マウスは 4800bps で、ストップビット 2 なので11ビット。
359: // 1/4800 * 11 = 2.29166 msec なので、1バイトあたり 2.3 msec にしておく。
360:
361: // code は 1 なら1バイト目 ([0])
362: // :
363: // code が 4 なら送らずに終了
364:
365: if (mouse_event.code < 4) {
366: // 1, 2, 3 バイト目送信
367: gSCC->Rx(1, mouse_data[mouse_event.code - 1]);
368:
369: // 次のイベント
370: mouse_event.time = 2300_usec;
371: mouse_event.code++;
372: gScheduler->StartEvent(mouse_event);
373: } else {
374: // 3 バイト目の送信が終わって、1文字送信分の時間が経過したので
375: // ここで code を 0 に戻す。
376: // XXX 実際のタイミングがどうかは知らない。
377: mouse_event.code = 0;
378: }
1.1.1.4 root 379: }
380:
381: // ホストからの制御
382: void
383: X68030Keyboard::Command(uint32 data)
384: {
385: if (data < 0x40) {
386: // $00-$3f TV 制御
387: // (実装不要)
1.1.1.11! root 388: putlog(2, "Command TV control $%02x", (data & 0x1f));
1.1.1.4 root 389:
390: } else if (data < 0x48) {
391: // $40-$47 MSCTRL 信号制御
392: // こいつは頻度が高いのでログレベルを上げておく
1.1.1.11! root 393: putlog(3, "Command MSCTRL control %d", (data & 1));
1.1.1.4 root 394:
395: } else if (data < 0x50) {
396: // $48-$4f キーデータ送出許可
1.1.1.11! root 397: putlog(1, "Command Key send enable %d (NOT IMPLEMENTED)", (data & 1));
1.1.1.4 root 398:
399: } else if (data < 0x54) {
400: // $50-$53 ディスプレイ制御モード選択
401: // (実装不要)
1.1.1.11! root 402: putlog(2, "Command Display control mode %d", (data & 1));
1.1.1.4 root 403:
404: } else if (data < 0x58) {
405: // $54-$57 LED 明るさ選択
406: // 0 が明るく 3 が暗いので、明るさというより暗さ。
407: led_darkness = (data & 3);
1.1.1.11! root 408: putlog(1, "Command LED brightness %d", led_darkness);
1.1.1.4 root 409:
410: } else if (data < 0x5c) {
411: // $58-$5b 本体からのディスプレイ制御許可
412: // (実装不要)
1.1.1.11! root 413: putlog(2, "Command Display control enable %d", (data & 1));
1.1.1.4 root 414:
415: } else if (data < 0x60) {
416: // $5c-$5f OPT.2 によるディスプレイ制御許可
417: // (実装不要)
1.1.1.11! root 418: putlog(2, "Command OPT2 control enable %d", (data & 1));
1.1.1.4 root 419:
420: } else if (data < 0x70) {
421: // $60-$6f リピートまでの時間
1.1.1.11! root 422: rept_delay_data = data & 15;
! 423: rept_delay = 200_msec + rept_delay_data * 100_msec;
! 424: putlog(1, "Command Repeat Delay %d (%d msec)", rept_delay_data,
1.1.1.6 root 425: (int)(rept_delay / 1_msec));
1.1.1.4 root 426:
427: } else if (data < 0x80) {
428: // $70-$7f リピート間隔
1.1.1.11! root 429: rept_time_data = data & 15;
! 430: rept_time = 30_msec + rept_time_data * rept_time_data * 5_msec;
! 431: putlog(1, "Command Repeat time %d (%d msec)", rept_time_data,
1.1.1.6 root 432: (int)(rept_time / 1_msec));
1.1.1.4 root 433:
434: } else {
435: // $80-$ff LED 制御
436: data &= 0x7f;
1.1.1.11! root 437: putlog(1, "Command LED control $%02x", data);
1.1.1.4 root 438: uint32 b = 0x01;
439: for (int i = 0; i < led.size(); i++) {
440: // data は %0 で点灯、led[] は true で点灯
441: led[i] = ((data & b) == 0);
442: b <<= 1;
443: }
444: }
445: }
446:
1.1 root 447: // キーコード変換表
448: // 共通キーコードから X680x0 キーコードに変換する。
449: /*static*/ const uint
1.1.1.5 root 450: X68030Keyboard::keycode2x68kkey_table[KC_max] = {
1.1 root 451: NoKey, // [00]
452: X68kKey_BREAK, // [01]
453: X68kKey_COPY, // [02]
454: X68kKey_romaji, // [03]
1.1.1.4 root 455: X68kKey_code, // [04]
1.1 root 456: X68kKey_kigou, // [05]
457: X68kKey_touroku, // [06]
458: X68kKey_HELP, // [07]
1.1.1.9 root 459: X68kKey_SHIFT, // [08]
1.1 root 460: X68kKey_TAB, // [09]
461: X68kKey_CTRL, // [0a]
462: X68kKey_kana, // [0b]
463: X68kKey_SHIFT, // [0c]
1.1.1.5 root 464: X68kKey_SHIFT, // [0d]
1.1 root 465: X68kKey_CAPS, // [0e]
466: NoKey, // [0f]
467:
468: X68kKey_ESC, // [10]
469: X68kKey_BS, // [11]
470: X68kKey_enter, // [12]
471: X68kKey_INS, // [13]
472: X68kKey_space, // [14]
473: X68kKey_DEL, // [15]
474: NoKey, // [16]
475: NoKey, // [17]
476: NoKey, // [18]
477: NoKey, // [19]
478: NoKey, // [1a]
479: NoKey, // [1b]
480: X68kKey_up, // [1c]
481: X68kKey_left, // [1d]
482: X68kKey_right, // [1e]
483: X68kKey_down, // [1f]
484:
485: X68kKey_ROLLUP, // [20]
486: X68kKey_ROLLDOWN, // [21]
487: X68kKey_1, // [22]
488: X68kKey_2, // [23]
489: X68kKey_3, // [24]
490: X68kKey_4, // [25]
491: X68kKey_5, // [26]
492: X68kKey_6, // [27]
493: X68kKey_7, // [28]
494: X68kKey_8, // [29]
495: X68kKey_9, // [2a]
496: X68kKey_0, // [2b]
497: X68kKey_minus, // [2c]
498: X68kKey_circum, // [2d]
499: X68kKey_backslash, // [2e]
1.1.1.9 root 500: NoKey, // [2f]
1.1 root 501:
502: X68kKey_OPT1, // [30]
503: X68kKey_OPT2, // [31]
504: X68kKey_Q, // [32]
505: X68kKey_W, // [33]
506: X68kKey_E, // [34]
507: X68kKey_R, // [35]
508: X68kKey_T, // [36]
509: X68kKey_Y, // [37]
510: X68kKey_U, // [38]
511: X68kKey_I, // [39]
512: X68kKey_O, // [3a]
513: X68kKey_P, // [3b]
514: X68kKey_at, // [3c]
515: X68kKey_bracketleft, // [3d]
516: X68kKey_hira, // [3e]
517: X68kKey_zenkaku, // [3f]
518:
1.1.1.9 root 519: X68kKey_HOME, // [40]
520: X68kKey_UNDO, // [41]
1.1 root 521: X68kKey_A, // [42]
522: X68kKey_S, // [43]
523: X68kKey_D, // [44]
524: X68kKey_F, // [45]
525: X68kKey_G, // [46]
526: X68kKey_H, // [47]
527: X68kKey_J, // [48]
528: X68kKey_K, // [49]
529: X68kKey_L, // [4a]
530: X68kKey_semicolon, // [4b]
531: X68kKey_colon, // [4c]
532: X68kKey_bracketright, // [4d]
533: NoKey, // [4e]
534: NoKey, // [4f]
535:
1.1.1.9 root 536: X68kKey_XF1, // [50]
537: X68kKey_XF2, // [51]
1.1 root 538: X68kKey_Z, // [52]
539: X68kKey_X, // [53]
540: X68kKey_C, // [54]
541: X68kKey_V, // [55]
542: X68kKey_B, // [56]
1.1.1.5 root 543: X68kKey_N, // [57]
544: X68kKey_M, // [58]
1.1 root 545: X68kKey_comma, // [59]
546: X68kKey_period, // [5a]
547: X68kKey_slash, // [5b]
548: X68kKey_underscore, // [5c]
1.1.1.9 root 549: X68kKey_XF3, // [5d]
550: X68kKey_XF4, // [5e]
551: X68kKey_XF5, // [5f]
1.1 root 552:
553: NoKey, // [60]
554: X68kKey_PAD_plus, // [61]
555: X68kKey_PAD_minus, // [62]
556: X68kKey_PAD_7, // [63]
557: X68kKey_PAD_8, // [64]
558: X68kKey_PAD_9, // [65]
559: X68kKey_PAD_4, // [66]
560: X68kKey_PAD_5, // [67]
561: X68kKey_PAD_6, // [68]
562: X68kKey_PAD_1, // [69]
563: X68kKey_PAD_2, // [6a]
564: X68kKey_PAD_3, // [6b]
565: X68kKey_PAD_0, // [6c]
566: X68kKey_PAD_decimal, // [6d]
567: X68kKey_PAD_enter, // [6e]
568: X68kKey_PAD_CLR, // [6f]
569:
570: NoKey, // [70]
571: NoKey, // [71]
572: X68kKey_F1, // [72]
573: X68kKey_F2, // [73]
574: X68kKey_F3, // [74]
575: X68kKey_F4, // [75]
576: X68kKey_F5, // [76]
577: X68kKey_F6, // [77]
578: X68kKey_F7, // [78]
579: X68kKey_F8, // [79]
580: X68kKey_F9, // [7a]
581: X68kKey_F10, // [7b]
582: X68kKey_PAD_multiply, // [7c]
583: X68kKey_PAD_divide, // [7d]
584: X68kKey_PAD_equal, // [7e]
585: X68kKey_PAD_comma, // [7f]
586: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.