|
|
1.1 ! root 1: /* ! 2: * dsp.c - Atari DSP56001 emulation code ! 3: * ! 4: * Copyright (c) 2001-2004 Petr Stehlik of ARAnyM dev team ! 5: * Adaption to Hatari (C) 2006 by Thomas Huth ! 6: * ! 7: * This program is free software; you can redistribute it and/or modify ! 8: * it under the terms of the GNU General Public License as published by ! 9: * the Free Software Foundation; either version 2 of the License, or ! 10: * (at your option) any later version. ! 11: * ! 12: * This program is distributed in the hope that it will be useful, ! 13: * but WITHOUT ANY WARRANTY; without even the implied warranty of ! 14: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! 15: * GNU General Public License for more details. ! 16: * ! 17: * You should have received a copy of the GNU General Public License ! 18: * along with ARAnyM; if not, write to the Free Software ! 19: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ! 20: */ ! 21: ! 22: #include "main.h" ! 23: #include "sysdeps.h" ! 24: #include "ioMem.h" ! 25: #include "dsp.h" ! 26: #include "dsp_cpu.h" ! 27: ! 28: #define DSP_EMULATION 1 ! 29: #define DEBUG 1 ! 30: ! 31: #if DEBUG ! 32: #define D(x) x ! 33: #else ! 34: #define D(x) ! 35: #endif ! 36: ! 37: #include <math.h> ! 38: ! 39: #include <SDL.h> ! 40: #include <SDL_thread.h> ! 41: ! 42: #ifndef M_PI ! 43: #define M_PI 3.141592653589793238462643383279502 ! 44: #endif ! 45: ! 46: ! 47: /* DSP state */ ! 48: uint8 dsp_state; ! 49: ! 50: /* Registers */ ! 51: uint16 dsp_pc; ! 52: uint32 dsp_registers[64]; ! 53: ! 54: /* stack[0=ssh], stack[1=ssl] */ ! 55: uint16 dsp_stack[2][15]; ! 56: ! 57: /* ram[0] is x:, ram[1] is y:, ram[2] is p: */ ! 58: uint32 dsp_ram[3][DSP_RAMSIZE]; ! 59: ! 60: /* rom[0] is x:, rom[1] is y: */ ! 61: uint32 dsp_rom[2][512]; ! 62: ! 63: /* peripheral space, [x|y]:0xffc0-0xffff */ ! 64: uint32 dsp_periph[2][64]; ! 65: ! 66: /* host port, CPU side */ ! 67: uint8 dsp_hostport[8]; ! 68: ! 69: /* Misc */ ! 70: uint32 dsp_loop_rep; /* executing rep ? */ ! 71: uint32 dsp_last_loop_inst; /* executing the last instruction in DO ? */ ! 72: uint32 dsp_first_host_write; /* first byte written to host port */ ! 73: ! 74: SDL_sem *dsp56k_sem; ! 75: ! 76: ! 77: /* For bootstrap routine */ ! 78: static uint16 bootstrap_pos; ! 79: static uint32 bootstrap_accum; ! 80: ! 81: static SDL_Thread *dsp56k_thread; ! 82: ! 83: ! 84: ! 85: #if DSP_EMULATION ! 86: ! 87: /* More disasm infos, if wanted */ ! 88: #define DSP_DISASM_HOSTREAD 0 /* Dsp->Host transfer */ ! 89: #define DSP_DISASM_HOSTWRITE 0 /* Host->Dsp transfer */ ! 90: #define DSP_DISASM_STATE 0 /* State changes */ ! 91: ! 92: /* Execute DSP instructions till the DSP waits for a read/write */ ! 93: #define DSP_HOST_FORCEEXEC 0 ! 94: ! 95: ! 96: static inline Uint32 getHWoffset(void) ! 97: { ! 98: return 0xFFA200; ! 99: } ! 100: ! 101: ! 102: /* Constructor and destructor for DSP class */ ! 103: void DSP_Init(void) ! 104: { ! 105: int i; ! 106: ! 107: memset(dsp_ram, 0,sizeof(dsp_ram)); ! 108: ! 109: /* Initialize Y:rom[0x0100-0x01ff] with a sin table */ ! 110: { ! 111: float src; ! 112: int32 dest; ! 113: ! 114: for (i=0;i<256;i++) { ! 115: src = (((float) i)*M_PI)/128.0; ! 116: dest = (int32) (sin(src) * 8388608.0); /* 1<<23 */ ! 117: if (dest>8388607) { ! 118: dest = 8388607; ! 119: } else if (dest<-8388608) { ! 120: dest = -8388608; ! 121: } ! 122: dsp_rom[DSP_SPACE_Y][0x100+i]=dest & 0x00ffffff; ! 123: } ! 124: } ! 125: ! 126: /* Initialize X:rom[0x0100-0x017f] with a mu-law table */ ! 127: { ! 128: const uint16 mulaw_base[8]={ ! 129: 0x7d7c, 0x3e7c, 0x1efc, 0x0f3c, 0x075c, 0x036c, 0x0174, 0x0078 ! 130: }; ! 131: ! 132: uint32 value, offset, position; ! 133: int j; ! 134: ! 135: position = 0x0100; ! 136: offset = 0x040000; ! 137: for(i=0;i<8;i++) { ! 138: value = mulaw_base[i]<<8; ! 139: ! 140: for (j=0;j<16;j++) { ! 141: dsp_rom[DSP_SPACE_X][position++]=value; ! 142: value -= offset; ! 143: } ! 144: ! 145: offset >>= 1; ! 146: } ! 147: } ! 148: ! 149: /* Initialize X:rom[0x0180-0x01ff] with a a-law table */ ! 150: { ! 151: const int32 multiply_base[8]={ ! 152: 0x1580, 0x0ac0, 0x5600, 0x2b00, ! 153: 0x1580, 0x0058, 0x0560, 0x02b0 ! 154: }; ! 155: const int32 multiply_col[4]={0x10, 0x01, 0x04, 0x02}; ! 156: const int32 multiply_line[4]={0x40, 0x04, 0x10, 0x08}; ! 157: const int32 base_values[4]={0, -1, 2, 1}; ! 158: uint32 pos=0x0180; ! 159: ! 160: for (i=0;i<8;i++) { ! 161: int32 alawbase, j; ! 162: ! 163: alawbase = multiply_base[i]<<8; ! 164: for (j=0;j<4;j++) { ! 165: int32 alawbase1, k; ! 166: ! 167: alawbase1 = alawbase + ((base_values[j]*multiply_line[i & 3])<<12); ! 168: ! 169: for (k=0;k<4;k++) { ! 170: int32 alawbase2; ! 171: ! 172: alawbase2 = alawbase1 + ((base_values[k]*multiply_col[i & 3])<<12); ! 173: ! 174: dsp_rom[DSP_SPACE_X][pos++]=alawbase2; ! 175: } ! 176: } ! 177: } ! 178: } ! 179: ! 180: D(bug("Dsp: power-on done")); ! 181: ! 182: dsp56k_thread = NULL; ! 183: dsp56k_sem = NULL; ! 184: ! 185: dsp_state = DSP_HALT; ! 186: #if DSP_DISASM_STATE ! 187: D(bug("Dsp: state = HALT")); ! 188: #endif ! 189: } ! 190: ! 191: void DSP_UnInit(void) ! 192: { ! 193: DSP_shutdown(); ! 194: } ! 195: ! 196: /* Other functions to init/shutdown dsp emulation */ ! 197: void DSP_Reset(void) ! 198: { ! 199: int i; ! 200: ! 201: /* Kill existing thread and semaphore */ ! 202: DSP_shutdown(); ! 203: ! 204: /* Pause thread */ ! 205: dsp_state = DSP_BOOTING; ! 206: #if DSP_DISASM_STATE ! 207: D(bug("Dsp: state = BOOTING")); ! 208: #endif ! 209: ! 210: /* Memory */ ! 211: memset(dsp_periph, 0,sizeof(dsp_periph)); ! 212: memset(dsp_stack, 0,sizeof(dsp_stack)); ! 213: memset(dsp_registers, 0,sizeof(dsp_registers)); ! 214: ! 215: bootstrap_pos = bootstrap_accum = 0; ! 216: ! 217: /* Registers */ ! 218: dsp_pc = 0x0000; ! 219: dsp_registers[DSP_REG_OMR]=0x02; ! 220: for (i=0;i<8;i++) { ! 221: dsp_registers[DSP_REG_M0+i]=0x00ffff; ! 222: } ! 223: ! 224: /* host port init, dsp side */ ! 225: dsp_periph[DSP_SPACE_X][DSP_HOST_HSR]=(1<<DSP_HOST_HSR_HTDE); ! 226: ! 227: /* host port init, cpu side */ ! 228: dsp_hostport[CPU_HOST_CVR]=0x12; ! 229: dsp_hostport[CPU_HOST_ISR]=(1<<CPU_HOST_ISR_TRDY)|(1<<CPU_HOST_ISR_TXDE); ! 230: dsp_hostport[CPU_HOST_IVR]=0x0f; ! 231: ! 232: /* Other hardware registers */ ! 233: dsp_periph[DSP_SPACE_X][DSP_IPR]=0; ! 234: dsp_periph[DSP_SPACE_X][DSP_BCR]=0xffff; ! 235: ! 236: /* Misc */ ! 237: dsp_loop_rep = 0; ! 238: dsp_last_loop_inst = 0; ! 239: dsp_first_host_write = 1; ! 240: ! 241: D(bug("Dsp: reset done")); ! 242: ! 243: /* Create thread and semaphore if needed */ ! 244: if (dsp56k_sem == NULL) { ! 245: dsp56k_sem = SDL_CreateSemaphore(0); ! 246: } ! 247: ! 248: if (dsp56k_thread == NULL) { ! 249: dsp56k_thread = SDL_CreateThread(dsp56k_do_execute, NULL); ! 250: } ! 251: } ! 252: ! 253: void DSP_shutdown(void) ! 254: { ! 255: if (dsp56k_thread != NULL) { ! 256: ! 257: /* Stop thread */ ! 258: dsp_state = DSP_STOPTHREAD; ! 259: #if DSP_DISASM_STATE ! 260: D(bug("Dsp: state = STOPTHREAD")); ! 261: #endif ! 262: ! 263: /* Release semaphore, if thread waiting for it */ ! 264: if (SDL_SemValue(dsp56k_sem)==0) { ! 265: SDL_SemPost(dsp56k_sem); ! 266: } ! 267: ! 268: /* Wait for the thread to finish */ ! 269: while (dsp_state != DSP_STOPPEDTHREAD) { ! 270: SDL_Delay(1); ! 271: } ! 272: ! 273: dsp56k_thread = NULL; ! 274: } ! 275: ! 276: /* Destroy the semaphore */ ! 277: if (dsp56k_sem != NULL) { ! 278: SDL_DestroySemaphore(dsp56k_sem); ! 279: dsp56k_sem = NULL; ! 280: } ! 281: } ! 282: ! 283: /********************************** ! 284: * Force execution of DSP, till something ! 285: * to read from/write to host port ! 286: **********************************/ ! 287: ! 288: static inline void DSP_force_exec(void) ! 289: { ! 290: #if DSP_HOST_FORCEEXEC ! 291: while (state == DSP_RUNNING) { ! 292: } ! 293: #endif ! 294: } ! 295: ! 296: #endif /* DSP_EMULATION */ ! 297: ! 298: /********************************** ! 299: * Hardware address read/write by CPU ! 300: **********************************/ ! 301: ! 302: static uint8 DSP_handleRead(Uint32 addr) ! 303: { ! 304: #if DSP_EMULATION ! 305: uint8 value=0; ! 306: ! 307: addr -= getHWoffset(); ! 308: ! 309: /* D(bug("HWget_b(0x%08x)=0x%02x at 0x%08x", addr+HW_DSP, value, showPC()));*/ ! 310: ! 311: /* Whenever the host want to read something on host port, we test if a ! 312: transfer is needed */ ! 313: DSP_dsp2host(); ! 314: ! 315: switch(addr) { ! 316: case CPU_HOST_ICR: ! 317: value = dsp_hostport[CPU_HOST_ICR]; ! 318: break; ! 319: case CPU_HOST_CVR: ! 320: value = dsp_hostport[CPU_HOST_CVR]; ! 321: break; ! 322: case CPU_HOST_ISR: ! 323: value = dsp_hostport[CPU_HOST_ISR]; ! 324: break; ! 325: case CPU_HOST_IVR: ! 326: value = dsp_hostport[CPU_HOST_IVR]; ! 327: break; ! 328: case CPU_HOST_RX0: ! 329: DSP_force_exec(); ! 330: value = 0; ! 331: break; ! 332: case CPU_HOST_RXH: ! 333: DSP_force_exec(); ! 334: value = dsp_hostport[CPU_HOST_RXH]; ! 335: break; ! 336: case CPU_HOST_RXM: ! 337: DSP_force_exec(); ! 338: value = dsp_hostport[CPU_HOST_RXM]; ! 339: break; ! 340: case CPU_HOST_RXL: ! 341: DSP_force_exec(); ! 342: value = dsp_hostport[CPU_HOST_RXL]; ! 343: ! 344: if (dsp_state!=DSP_BOOTING) { ! 345: /* Clear RXDF bit to say that CPU has read */ ! 346: dsp_hostport[CPU_HOST_ISR] &= 0xff-(1<<CPU_HOST_ISR_RXDF); ! 347: #if DSP_DISASM_HOSTWRITE ! 348: D(bug("Dsp: (D->H): Host RXDF cleared")); ! 349: #endif ! 350: } ! 351: ! 352: /* Wake up DSP if it was waiting our read */ ! 353: if (dsp_state==DSP_WAITHOSTREAD) { ! 354: #if DSP_DISASM_STATE ! 355: D(bug("Dsp: state = DSP_RUNNING")); ! 356: #endif ! 357: dsp_state = DSP_RUNNING; ! 358: ! 359: if (SDL_SemValue(dsp56k_sem)==0) { ! 360: SDL_SemPost(dsp56k_sem); ! 361: } ! 362: } ! 363: ! 364: break; ! 365: } ! 366: ! 367: return value; ! 368: #else ! 369: return 0xff; // this value prevents TOS from hanging in the DSP init code */ ! 370: #endif /* DSP_EMULATION */ ! 371: } ! 372: ! 373: void DSP_HandleReadAccess(void) ! 374: { ! 375: Uint32 a; ! 376: Uint8 v; ! 377: for (a = IoAccessBaseAddress; a < IoAccessBaseAddress+nIoMemAccessSize; a++) ! 378: { ! 379: v = DSP_handleRead(a); ! 380: IoMem_WriteByte(a, v); ! 381: } ! 382: } ! 383: ! 384: static void DSP_handleWrite(Uint32 addr, uint8 value) ! 385: { ! 386: #if DSP_EMULATION ! 387: addr -= getHWoffset(); ! 388: ! 389: /* D(bug("HWput_b(0x%08x,0x%02x) at 0x%08x", addr+HW_DSP, value, showPC()));*/ ! 390: ! 391: switch(addr) { ! 392: case CPU_HOST_ICR: ! 393: dsp_hostport[CPU_HOST_ICR]=value & 0xfb; ! 394: /* Set HF1 and HF0 accordingly on the host side */ ! 395: dsp_periph[DSP_SPACE_X][DSP_HOST_HSR] &= ! 396: 0xff-((1<<DSP_HOST_HSR_HF1)|(1<<DSP_HOST_HSR_HF0)); ! 397: dsp_periph[DSP_SPACE_X][DSP_HOST_HSR] |= ! 398: dsp_hostport[CPU_HOST_ICR] & ((1<<DSP_HOST_HSR_HF1)|(1<<DSP_HOST_HSR_HF0)); ! 399: break; ! 400: case CPU_HOST_CVR: ! 401: dsp_hostport[CPU_HOST_CVR]=value & 0x9f; ! 402: /* if bit 7=1, host command */ ! 403: if (value & (1<<7)) { ! 404: dsp_periph[DSP_SPACE_X][DSP_HOST_HSR] |= 1<<DSP_HOST_HSR_HCP; ! 405: } ! 406: break; ! 407: case CPU_HOST_ISR: ! 408: /* Read only */ ! 409: break; ! 410: case CPU_HOST_IVR: ! 411: dsp_hostport[CPU_HOST_IVR]=value; ! 412: break; ! 413: case CPU_HOST_TX0: ! 414: DSP_force_exec(); ! 415: ! 416: if (dsp_first_host_write) { ! 417: dsp_first_host_write = 0; ! 418: bootstrap_accum = 0; ! 419: } ! 420: break; ! 421: case CPU_HOST_TXH: ! 422: DSP_force_exec(); ! 423: ! 424: if (dsp_first_host_write) { ! 425: dsp_first_host_write = 0; ! 426: bootstrap_accum = 0; ! 427: } ! 428: dsp_hostport[CPU_HOST_TXH]=value; ! 429: bootstrap_accum |= value<<16; ! 430: break; ! 431: case CPU_HOST_TXM: ! 432: DSP_force_exec(); ! 433: ! 434: if (dsp_first_host_write) { ! 435: dsp_first_host_write = 0; ! 436: dsp_hostport[CPU_HOST_TXH]=value; /* FIXME: is it correct ? */ ! 437: bootstrap_accum = 0; ! 438: } ! 439: dsp_hostport[CPU_HOST_TXM]=value; ! 440: bootstrap_accum |= value<<8; ! 441: break; ! 442: case CPU_HOST_TXL: ! 443: DSP_force_exec(); ! 444: ! 445: if (dsp_first_host_write) { ! 446: dsp_first_host_write = 0; ! 447: dsp_hostport[CPU_HOST_TXH]=value; /* FIXME: is it correct ? */ ! 448: dsp_hostport[CPU_HOST_TXM]=value; /* FIXME: is it correct ? */ ! 449: bootstrap_accum = 0; ! 450: } ! 451: dsp_hostport[CPU_HOST_TXL]=value; ! 452: bootstrap_accum |= value; ! 453: ! 454: dsp_first_host_write = 1; ! 455: ! 456: if (dsp_state != DSP_BOOTING) { ! 457: /* Clear TXDE to say that host has written */ ! 458: dsp_hostport[CPU_HOST_ISR] &= 0xff-(1<<CPU_HOST_ISR_TXDE); ! 459: #if DSP_DISASM_HOSTREAD ! 460: D(bug("Dsp: (H->D): Host TXDE cleared")); ! 461: #endif ! 462: ! 463: DSP_host2dsp(); ! 464: } ! 465: ! 466: switch(dsp_state) { ! 467: case DSP_BOOTING: ! 468: dsp_ram[DSP_SPACE_P][bootstrap_pos] = bootstrap_accum; ! 469: /* D(bug("Dsp: bootstrap: p:0x%04x: 0x%06x written", bootstrap_pos, bootstrap_accum));*/ ! 470: bootstrap_pos++; ! 471: if (bootstrap_pos == 0x200) { ! 472: #if DSP_DISASM_STATE ! 473: D(bug("Dsp: bootstrap done")); ! 474: #endif ! 475: dsp_state = DSP_RUNNING; ! 476: ! 477: SDL_SemPost(dsp56k_sem); ! 478: } ! 479: bootstrap_accum = 0; ! 480: break; ! 481: case DSP_WAITHOSTWRITE: ! 482: /* Wake up DSP if it was waiting our write */ ! 483: #if DSP_DISASM_STATE ! 484: D(bug("Dsp: state = DSP_RUNNING")); ! 485: #endif ! 486: dsp_state = DSP_RUNNING; ! 487: ! 488: if (SDL_SemValue(dsp56k_sem)==0) { ! 489: SDL_SemPost(dsp56k_sem); ! 490: } ! 491: break; ! 492: } ! 493: ! 494: break; ! 495: } ! 496: #endif /* DSP_EMULATION */ ! 497: } ! 498: ! 499: void DSP_HandleWriteAccess(void) ! 500: { ! 501: Uint32 a; ! 502: Uint8 v; ! 503: for (a = IoAccessBaseAddress; a < IoAccessBaseAddress+nIoMemAccessSize; a++) ! 504: { ! 505: v = IoMem_ReadByte(a); ! 506: DSP_handleWrite(a,v); ! 507: } ! 508: } ! 509: ! 510: ! 511: #if DSP_EMULATION ! 512: ! 513: /********************************** ! 514: * Host transfer ! 515: **********************************/ ! 516: ! 517: void DSP_host2dsp(void) ! 518: { ! 519: int trdy; ! 520: ! 521: /* Host port transfer ? (host->dsp) */ ! 522: if ( ! 523: ((dsp_hostport[CPU_HOST_ISR] & (1<<CPU_HOST_ISR_TXDE))==0) && ! 524: ((dsp_periph[DSP_SPACE_X][DSP_HOST_HSR] & (1<<DSP_HOST_HSR_HRDF))==0) ! 525: ) { ! 526: ! 527: dsp_periph[DSP_SPACE_X][DSP_HOST_HRX] = dsp_hostport[CPU_HOST_TXL]; ! 528: dsp_periph[DSP_SPACE_X][DSP_HOST_HRX] |= dsp_hostport[CPU_HOST_TXM]<<8; ! 529: dsp_periph[DSP_SPACE_X][DSP_HOST_HRX] |= dsp_hostport[CPU_HOST_TXH]<<16; ! 530: ! 531: #if DSP_DISASM_HOSTREAD ! 532: D(bug("Dsp: (H->D): Transfer 0x%06x",dsp_periph[DSP_SPACE_X][DSP_HOST_HRX])); ! 533: #endif ! 534: ! 535: /* Set HRDF bit to say that DSP can read */ ! 536: dsp_periph[DSP_SPACE_X][DSP_HOST_HSR] |= 1<<DSP_HOST_HSR_HRDF; ! 537: #if DSP_DISASM_HOSTREAD ! 538: D(bug("Dsp: (H->D): Dsp HRDF set")); ! 539: #endif ! 540: ! 541: /* Set TXDE bit to say that host can write */ ! 542: dsp_hostport[CPU_HOST_ISR] |= 1<<CPU_HOST_ISR_TXDE; ! 543: #if DSP_DISASM_HOSTREAD ! 544: D(bug("Dsp: (H->D): Host TXDE set")); ! 545: #endif ! 546: ! 547: /* Clear/set TRDY bit */ ! 548: dsp_hostport[CPU_HOST_ISR] &= 0xff-(1<<CPU_HOST_ISR_TRDY); ! 549: trdy = (dsp_hostport[CPU_HOST_ISR]>>CPU_HOST_ISR_TXDE) & 1; ! 550: trdy &= !((dsp_periph[DSP_SPACE_X][DSP_HOST_HSR]>>DSP_HOST_HSR_HRDF) & 1); ! 551: dsp_hostport[CPU_HOST_ISR] |= (trdy & 1)<< CPU_HOST_ISR_TRDY; ! 552: } ! 553: } ! 554: ! 555: void DSP_dsp2host(void) ! 556: { ! 557: /* Host port transfer ? (dsp->host) */ ! 558: if ( ! 559: ((dsp_hostport[CPU_HOST_ISR] & (1<<CPU_HOST_ISR_RXDF))==0) && ! 560: ((dsp_periph[DSP_SPACE_X][DSP_HOST_HSR] & (1<<DSP_HOST_HSR_HTDE))==0) ! 561: ) { ! 562: ! 563: dsp_hostport[CPU_HOST_RXL] = dsp_periph[DSP_SPACE_X][DSP_HOST_HTX]; ! 564: dsp_hostport[CPU_HOST_RXM] = dsp_periph[DSP_SPACE_X][DSP_HOST_HTX]>>8; ! 565: dsp_hostport[CPU_HOST_RXH] = dsp_periph[DSP_SPACE_X][DSP_HOST_HTX]>>16; ! 566: ! 567: #if DSP_DISASM_HOSTWRITE ! 568: D(bug("Dsp: (D->H): Transfer 0x%06x",dsp_periph[DSP_SPACE_X][DSP_HOST_HTX])); ! 569: #endif ! 570: ! 571: /* Set HTDE bit to say that DSP can write */ ! 572: dsp_periph[DSP_SPACE_X][DSP_HOST_HSR] |= 1<<DSP_HOST_HSR_HTDE; ! 573: #if DSP_DISASM_HOSTWRITE ! 574: D(bug("Dsp: (D->H): Dsp HTDE set")); ! 575: #endif ! 576: ! 577: /* Set RXDF bit to say that host can read */ ! 578: dsp_hostport[CPU_HOST_ISR] |= 1<<CPU_HOST_ISR_RXDF; ! 579: #if DSP_DISASM_HOSTWRITE ! 580: D(bug("Dsp: (D->H): Host RXDF set")); ! 581: #endif ! 582: } ! 583: } ! 584: ! 585: #endif /* DSP_EMULATION */ ! 586:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.