Annotation of uae/src/disk.c, revision 1.1.1.2

1.1.1.2 ! root        1:  /*
1.1       root        2:   * UAE - The Un*x Amiga Emulator
1.1.1.2 ! root        3:   *
1.1       root        4:   * Floppy disk emulation
                      5:   *
                      6:   * (c) 1995 Bernd Schmidt, Hannu Rummukainen
                      7:   */
                      8: 
1.1.1.2 ! root        9: #include <stdio.h>
        !            10: 
1.1       root       11: #include "sysconfig.h"
                     12: #include "sysdeps.h"
                     13: 
                     14: #include "config.h"
                     15: #include "options.h"
                     16: #include "memory.h"
                     17: #include "events.h"
                     18: #include "custom.h"
                     19: #include "ersatz.h"
                     20: #include "disk.h"
                     21: #include "gui.h"
                     22: #include "zfile.h"
1.1.1.2 ! root       23: #include "autoconf.h"
        !            24: #include "readcpu.h"
        !            25: #include "newcpu.h"
        !            26: #include "xwin.h"
        !            27: #include "osemu.h"
        !            28: #include "execlib.h"
        !            29: 
        !            30: #define FLOPPY_GAP_LEN 380
1.1       root       31: 
                     32: #define FLOPPY_SPEED 5
                     33: 
                     34: UWORD* mfmwrite;
                     35: static UWORD mfmwrbuffer[16384]; /* space for maximum disk DMA transfer */
                     36: 
                     37: static int side, direction, step;
                     38: static UBYTE selected = 15;
                     39: static int dskready;
                     40: static int need_read = 0;
                     41: 
                     42: typedef struct {
                     43:     UWORD sync;
                     44:     UWORD len;
                     45:     ULONG offs;
                     46: } trackid;
                     47: 
1.1.1.2 ! root       48: #define MAX_TRACKS 328
        !            49: 
1.1       root       50: typedef enum { ADF_NORMAL, ADF_EXT1 } drive_filetype;
                     51: typedef struct {
                     52:     FILE *diskfile;
                     53:     drive_filetype filetype;
1.1.1.2 ! root       54:     trackid trackdata[MAX_TRACKS];
1.1       root       55:     unsigned int track;
                     56:     int motoroff;
                     57:     int wrprot;
1.1.1.2 ! root       58:     UWORD bigmfmbuf[0x4000];
1.1       root       59:     int tracklen;
                     60:     unsigned long last_cycles;
                     61:     int mfmpos;
1.1.1.2 ! root       62:     unsigned int num_tracks, num_secs;
        !            63:     int nodskchange;
1.1       root       64: } drive;
                     65: 
                     66: drive floppy[4];
                     67: 
                     68: static void drive_fill_bigbuf(drive *drv);
                     69: 
1.1.1.2 ! root       70: static void drive_insert(drive *drv, int dnum, const char *fname)
1.1       root       71: {
                     72:     unsigned char buffer[10];
                     73:     
                     74:     drv->diskfile = zfile_open(fname,"r+b");
                     75:     if (drv->diskfile) {
                     76:        drv->wrprot = 0;
                     77:     } else {
                     78:        drv->wrprot = 1;
                     79:        drv->diskfile = zfile_open(fname, "rb");
                     80:        if (!drv->diskfile) {
                     81:            gui_filename(dnum, "");
                     82:            return;
                     83:        }
                     84:     }
                     85:     switch (dnum) {
                     86:      case 0: strcpy((char *)df0, fname); break;
                     87:      case 1: strcpy((char *)df1, fname); break;
                     88:      case 2: strcpy((char *)df2, fname); break;
                     89:      case 3: strcpy((char *)df3, fname); break;
                     90:     }
                     91:     gui_filename(dnum, fname);
1.1.1.2 ! root       92:     
1.1       root       93:     fread(buffer,sizeof(char),8,drv->diskfile);
                     94:     if (strncmp((char *)buffer,"UAE--ADF",8) == 0) {   
                     95:        int offs = 160*4+8;
                     96:        int i;
                     97:        
                     98:        drv->filetype = ADF_EXT1;
1.1.1.2 ! root       99:        drv->num_tracks = 160;
        !           100:        drv->num_secs = 11;
        !           101:            
1.1       root      102:        drv->wrprot = 1; /* write to adf_ext1 not implemented */
                    103:        for(i=0; i<160; i++) {
                    104:            fread(buffer, 4, 1, drv->diskfile);
                    105:            drv->trackdata[i].sync = buffer[0]*256 + buffer[1];
                    106:            drv->trackdata[i].len = buffer[2]*256 + buffer[3];
                    107:            drv->trackdata[i].offs = offs;
                    108:            offs += drv->trackdata[i].len;
                    109:        }
                    110:     } else {
1.1.1.2 ! root      111:        unsigned int i;
        !           112:        
1.1       root      113:        drv->filetype = ADF_NORMAL;
1.1.1.2 ! root      114: 
        !           115:        fseek(drv->diskfile,0,SEEK_END);
        !           116:        i = ftell(drv->diskfile);
        !           117:        fseek(drv->diskfile,0,SEEK_SET);
        !           118:        
        !           119:        /* High-density disk? */
        !           120:        if (i >= 160*22*512)
        !           121:            drv->num_tracks = i / (512*(drv->num_secs = 22));
        !           122:        else
        !           123:            drv->num_tracks = i / (512*(drv->num_secs = 11));
        !           124:        
        !           125:        if (drv->num_tracks > MAX_TRACKS)
        !           126:            fprintf(stderr, "Your diskfile is too big!\n");
        !           127:        for(i = 0; i < drv->num_tracks; i++) {
        !           128:            drv->trackdata[i].len = 512 * drv->num_secs;
1.1       root      129:            drv->trackdata[i].sync = 0;
1.1.1.2 ! root      130:            drv->trackdata[i].offs = i * 512 * drv->num_secs;
1.1       root      131:        }
                    132:     }
                    133:     drive_fill_bigbuf(drv);
                    134: }
                    135: 
1.1.1.2 ! root      136: static int drive_empty(drive *drv)
        !           137: {
        !           138:     return drv->diskfile == 0; 
        !           139: }
        !           140: 
1.1       root      141: static void drive_step(drive *drv)
                    142: {
1.1.1.2 ! root      143:     if (!drive_empty (drv))
        !           144:        drv->nodskchange = 1;
1.1       root      145:     if (direction) {
                    146:        if (drv->track) drv->track--;
                    147:     } else {
1.1.1.2 ! root      148:        if (drv->track < drv->num_tracks/2) drv->track++;
1.1       root      149:     }
                    150:     drive_fill_bigbuf(drv);
                    151: }
                    152: 
                    153: 
                    154: static int drive_track0(drive *drv) 
                    155: {
                    156:     return drv->track == 0; 
                    157: }
                    158: 
                    159: static int drive_writeprotected(drive *drv) 
                    160: {
                    161:     return drv->wrprot || drv->diskfile == NULL; 
                    162: }
                    163: 
                    164: static int drive_running(drive *drv)
                    165: {
                    166:     return !drv->motoroff; 
                    167: }
                    168: 
                    169: static void drive_motor(drive *drv, int off)
                    170: {
                    171:     if (drv->motoroff && !off) {
                    172:        drv->last_cycles = cycles;
                    173:        drv->mfmpos = 0;
                    174:        eventtab[ev_diskindex].active = 1;
                    175:        if (drv->tracklen)
                    176:            eventtab[ev_diskindex].evtime = FLOPPY_SPEED * drv->tracklen + cycles;
                    177:        else
                    178:            eventtab[ev_diskindex].evtime = cycles + 1000;
                    179:        eventtab[ev_diskindex].oldcycles = cycles;
                    180:     } else if (off)
                    181:        eventtab[ev_diskindex].active = 0;
                    182:     drv->motoroff = off;
                    183:     events_schedule();
                    184:        
                    185: }
                    186: 
                    187: static void drive_fill_bigbuf(drive *drv)
                    188: {
                    189:     int tr = drv->track*2 + side;
                    190:     
1.1.1.2 ! root      191:     if (!drv->diskfile) {
        !           192:        drv->tracklen = 10;
        !           193:        memset (drv->bigmfmbuf,0xaa,drv->tracklen*2);
        !           194:        return;
        !           195:     }
1.1       root      196:     
                    197:     if (drv->trackdata[tr].sync == 0) {
                    198:        /* Normal AmigaDOS format track */
1.1.1.2 ! root      199:        unsigned int sec;
        !           200:        drv->tracklen = drv->num_secs*544 + FLOPPY_GAP_LEN;
        !           201:        memset(drv->bigmfmbuf,0xaa,FLOPPY_GAP_LEN*2);
        !           202: 
        !           203:        for (sec = 0; sec < drv->num_secs; sec++) {
1.1       root      204:            UBYTE secbuf[544];
                    205:            int i;
1.1.1.2 ! root      206:            UWORD *mfmbuf = drv->bigmfmbuf + 544*sec + FLOPPY_GAP_LEN;
1.1       root      207:            ULONG deven,dodd;
                    208:            ULONG hck=0,dck=0;
                    209:            
                    210:            secbuf[0] = secbuf[1] = 0x00;
                    211:            secbuf[2] = secbuf[3] = 0xa1;
                    212:            secbuf[4] = 0xff;
                    213:            secbuf[5] = tr;
                    214:            secbuf[6] = sec;
1.1.1.2 ! root      215:            secbuf[7] = drv->num_secs-sec;
1.1       root      216: 
                    217:            for(i = 8; i < 24; i++)
                    218:                secbuf[i] = 0;
                    219:     
                    220:            fseek(drv->diskfile,
                    221:                  drv->trackdata[tr].offs + sec*512, 
                    222:                  SEEK_SET);
                    223:            fread(&secbuf[32],1,512,drv->diskfile);
                    224:     
                    225:            mfmbuf[0] = mfmbuf[1] = 0xaaaa;
                    226:            mfmbuf[2] = mfmbuf[3] = 0x4489;
                    227:     
                    228:            deven = ((secbuf[4] << 24) | (secbuf[5] << 16) 
                    229:                     | (secbuf[6] << 8) | (secbuf[7]));
                    230:            dodd = deven >> 1;
                    231:            deven &= 0x55555555; dodd &= 0x55555555;
                    232:     
                    233:            mfmbuf[4] = dodd >> 16;
                    234:            mfmbuf[5] = dodd;
                    235:            mfmbuf[6] = deven>> 16; 
                    236:            mfmbuf[7] = deven;
                    237:     
                    238:            for (i = 8; i < 48; i++)
                    239:                mfmbuf[i] = 0;
                    240:            for (i = 0; i < 512; i += 4){
                    241:                deven = ((secbuf[i+32] << 24) | (secbuf[i+33] << 16)
                    242:                         | (secbuf[i+34] << 8) | (secbuf[i+35]));
                    243:                dodd = deven >> 1;
                    244:                deven &= 0x55555555; dodd &= 0x55555555;
                    245:                mfmbuf[(i>>1)+32] = dodd >> 16;
                    246:                mfmbuf[(i>>1)+33] = dodd;
                    247:                mfmbuf[(i>>1)+256+32] = deven >> 16;
                    248:                mfmbuf[(i>>1)+256+33] = deven;
                    249:            }
                    250:     
                    251:            for(i = 4; i < 24; i += 2)
                    252:                hck ^= (mfmbuf[i] << 16) | mfmbuf[i+1];
                    253:     
                    254:            deven = dodd = hck; dodd >>= 1;
                    255:            mfmbuf[24] = dodd >> 16; mfmbuf[25] = dodd;
                    256:            mfmbuf[26] = deven>> 16; mfmbuf[27] = deven;
                    257:     
                    258:            for(i = 32; i < 544; i += 2)
                    259:                dck ^= (mfmbuf[i] << 16) | mfmbuf[i+1];
                    260:     
                    261:            deven = dodd = dck; dodd >>= 1;
                    262:            mfmbuf[28] = dodd >> 16; mfmbuf[29] = dodd;
                    263:            mfmbuf[30] = deven>> 16; mfmbuf[31] = deven;
                    264:        }
                    265:     } else {
                    266:        int i;
                    267:        drv->tracklen = drv->trackdata[tr].len/2 + 1;
                    268:        drv->bigmfmbuf[0] = drv->trackdata[tr].sync;
                    269:        fseek(drv->diskfile, drv->trackdata[tr].offs, SEEK_SET);
                    270:        fread(drv->bigmfmbuf+1, 1, drv->trackdata[tr].len, drv->diskfile);
                    271:        for (i = 0; i < drv->trackdata[tr].len/2; i++) {
                    272:            UWORD *mfm = drv->bigmfmbuf + i + 1;
                    273:            UBYTE *data = (UBYTE *)mfm;
                    274:            
                    275:            *mfm = 256 * *data + *(data+1);
                    276:        }
                    277:     }
                    278:     need_read = 0;
                    279:     drv->last_cycles = cycles;
                    280:     drv->mfmpos = 0;
                    281: }
                    282: 
                    283: static int drive_get_data(drive *drv, UWORD *mfm, UWORD *byt)
                    284: {
                    285:     int offset,mfmpos;
                    286:     if (need_read) {
                    287:        drive_fill_bigbuf(drv);
                    288:     }
                    289:     offset = (cycles - drv->last_cycles) / FLOPPY_SPEED;
                    290:     mfmpos = (drv->mfmpos + offset) % drv->tracklen;
                    291:     drv->last_cycles += offset*FLOPPY_SPEED;
                    292:     drv->mfmpos = mfmpos;
                    293:     *mfm = drv->bigmfmbuf[mfmpos];
                    294:     return offset > 0;
                    295: }
                    296: 
                    297: #define MFMMASK 0x55555555
                    298: static __inline__ ULONG getmfmlong(UWORD* mbuf) 
                    299: {
                    300:     return ((*mbuf << 16) | *(mbuf + 1)) & MFMMASK;
                    301: }
                    302: 
                    303: static void drive_write_data(drive *drv, UWORD *mbuf, int length)
                    304: {
                    305:     int i, secwritten = 0;
                    306:     ULONG odd, even, chksum, id, dlong;
                    307:     UBYTE* secdata;
                    308:     UBYTE secbuf[544];
                    309:     UWORD *mend = mbuf + length;
                    310:     
                    311:     if (drive_writeprotected(drv)) return;
                    312:     mend -= (4 + 16 + 8 + 512);
                    313:     while (length > 0) {
                    314:        int trackoffs;
                    315:        
                    316:        do {
                    317:            while (*mbuf++ != 0x4489) {
                    318:                if (mbuf >= mend) return;
                    319:            }
                    320:        } while (*mbuf++ != 0x4489);
                    321:        
                    322:        odd = getmfmlong(mbuf);
                    323:        even = getmfmlong(mbuf+2);
                    324:        mbuf += 4;      
                    325:        id = (odd << 1) | even;
                    326:        
                    327:        trackoffs = (id & 0xff00) >> 8;
                    328:        if (trackoffs > 10) {
                    329:            printf("Disk write: weird sector number %d\n", trackoffs);
                    330:            continue;
                    331:        }
                    332:        chksum = odd ^ even;
                    333:        for (i=0; i<4; i++) {
                    334:            odd = getmfmlong(mbuf);
                    335:            even = getmfmlong(mbuf+8);
                    336:            mbuf += 2;
                    337:            
                    338:            dlong = (odd << 1) | even;
                    339:            if (dlong)  secwritten = -200;
                    340:            chksum ^= odd ^ even;
                    341:        }  /* could check here if the label is nonstandard */
                    342:        mbuf += 8;
                    343:        odd = getmfmlong(mbuf); even = getmfmlong(mbuf+2); mbuf += 4;
                    344:        if ((((odd << 1) | even) != chksum) || 
                    345:            (((id & 0x00ff0000) >> 16) != drv->track*2 + side)) {
                    346:            printf("Disk write: checksum error on sector header\n");
                    347:            continue;
                    348:        }
                    349:        odd = getmfmlong(mbuf); even = getmfmlong(mbuf+2); mbuf += 4;
                    350:        chksum = (odd << 1) | even;
                    351:        secdata = secbuf + 32;
                    352:        for (i=0; i<128; i++) {
                    353:            odd = getmfmlong(mbuf); even = getmfmlong(mbuf+256); mbuf += 2;
                    354:            dlong = (odd << 1) | even;
                    355:            *secdata++ = dlong >> 24; *secdata++ = (dlong >> 16) & 0xff;
                    356:            *secdata++ = dlong >> 8; *secdata++ = dlong;
                    357:            chksum ^= odd ^ even;
                    358:        }
                    359:        mbuf += 256;
                    360:        if (chksum) {
                    361:            printf("Disk write: data checksum error\n");
                    362:            continue;
                    363:        }
                    364:        secwritten++;
                    365:        fseek(drv->diskfile, 
                    366:              drv->trackdata[drv->track*2 + side].offs + trackoffs*512,
                    367:              SEEK_SET);
                    368:        fwrite(secbuf+32, sizeof(UBYTE), 512, drv->diskfile);
                    369:     }
                    370:     need_read = 1;
                    371:     
                    372:     if (secwritten == 0) 
                    373:        printf("Disk write in unsupported format\n");
                    374:     if (secwritten < 0)
                    375:        printf("Disk write: sector labels ignored\n");
                    376: }
                    377: 
                    378: 
                    379: static void drive_eject(drive *drv)
                    380: {
                    381:     if (!drive_empty(drv)) zfile_close(drv->diskfile);
                    382:     drv->diskfile = 0;
                    383: }
                    384: 
                    385: /* We use this function if we have no Kickstart ROM. 
                    386:  * No error checking - we trust our luck. */
                    387: void DISK_ersatz_read (int tr, int sec, CPTR dest)
                    388: {
                    389:     int i;
                    390:     UBYTE *dptr = get_real_address(dest);
                    391:     fseek(floppy[0].diskfile, floppy[0].trackdata[tr].offs + sec*512, SEEK_SET);
                    392:     fread(dptr,1,512,floppy[0].diskfile);
                    393: }
                    394: 
                    395: void disk_eject(int num)
                    396: {
                    397:     gui_filename(num, "");
                    398:     drive_eject(floppy + num);
                    399: }
                    400: 
1.1.1.2 ! root      401: void disk_insert(int num, const char *name)
1.1       root      402: {
                    403:     /* just to be sure */
                    404:     drive_eject(floppy + num);
                    405:     drive_insert(floppy + num, num, name);
                    406: }
                    407: 
                    408: int disk_empty(int num)
                    409: {
                    410:     return drive_empty(floppy + num);
                    411: }
                    412: 
                    413: void DISK_init()
                    414: {
                    415:     drive_insert(floppy, 0, df0);
                    416:     if (disk_empty(0))
                    417:        fprintf(stderr, "No floppy in drive 0.\n");
                    418:     drive_insert(floppy + 1, 1, df1);
                    419:     drive_insert(floppy + 2, 2, df2);
                    420:     drive_insert(floppy + 3, 3, df3);
                    421: }
                    422: 
                    423: static int ledstate[] = { 0,0,0,0 };
                    424: 
                    425: void DISK_select(UBYTE data)
                    426: {
                    427:     int step_pulse;
                    428:     int dr;
                    429:     
                    430:     if (selected != ((data >> 3) & 15)) 
                    431:        dskready = 0;
                    432:     selected = (data >> 3) & 15;
                    433:     if (side != 1 - ((data >> 2) & 1)) {
                    434:        side = 1 - ((data >> 2) & 1);
                    435:        need_read = 1;
                    436:     }
                    437:     direction = (data >> 1) & 1;
                    438:     step_pulse = data & 1;
                    439:     if (step != step_pulse) {
                    440:        step = step_pulse;
                    441:        if (step == 0){
                    442:            for (dr = 0; dr < 4; dr++){
                    443:                if (!(selected & (1 << dr))) {
                    444:                    drive_step(floppy + dr);
                    445:                }
                    446:            }
                    447:        }
                    448:     }
                    449:     for (dr = 0; dr < 4; dr++){
                    450:        if (!(selected & (1<<dr))) {
                    451:            drive_motor(floppy + dr, data >> 7);
                    452:        }
                    453:     }
                    454:     for (dr = 0; dr < 4; dr++) {
                    455:        int state = (!(selected & (1<<dr))) | !floppy[dr].motoroff;
                    456:        if (state != ledstate[dr])
                    457:            gui_led (dr+1, state);
                    458:        ledstate[dr] = state;
                    459:     }
                    460: }
                    461: 
                    462: UBYTE DISK_status()
                    463: {
                    464:     UBYTE st = 0x3c;
                    465:     int dr;
                    466:     
                    467:     for (dr = 0; dr < 4; dr++){
                    468:        if (!(selected & (1 << dr))) {
                    469:            if (drive_running(floppy + dr)){
                    470:                if (dskready) st &= ~0x20;
                    471:                dskready = 1;
                    472:            } else {
                    473:                st &= ~0x20; /* report drive ID */
                    474:            }
                    475:                
                    476:            if (drive_track0(floppy + dr)) { st &= ~0x10; }
                    477:            if (drive_writeprotected(floppy + dr)) { st &= ~8; }
1.1.1.2 ! root      478:            if (!floppy[dr].nodskchange) { st &= ~0x4; }
1.1       root      479:        }
                    480:     }
                    481:     return st;
                    482: }
                    483: 
                    484: int DISK_GetData(UWORD *mfm,UWORD *byt)
                    485: {
                    486:     int dr;
                    487:     for (dr = 0; dr < 4; dr++){
                    488:        if (!(selected & (1<<dr))) {
                    489:            return drive_get_data (floppy + dr, mfm, byt);
                    490:        }
                    491:     }
                    492:     return 0;
                    493: }
                    494: 
                    495: static UWORD mfm_read_buffer[0x4000];
                    496: static int mfm_read_length;
                    497: 
                    498: int DISK_PrepareReadMFM(int length, UWORD sync, int use_sync)
                    499: {
                    500:     int dr;
                    501:     
                    502:     mfm_read_length = 0;
                    503:     
                    504:     for (dr = 0; dr < 4; dr++) {
                    505:        if (!(selected & (1<<dr))) {
                    506:            int time = 0;
                    507:            UWORD *mfmp = mfm_read_buffer;
                    508:            drive *drv = floppy + dr;
                    509:            int offset,mfmpos;
                    510: 
                    511:            if (need_read) {
                    512:                drive_fill_bigbuf(drv);
                    513:            }
1.1.1.2 ! root      514: 
1.1       root      515:            offset = (cycles - drv->last_cycles) / FLOPPY_SPEED;
                    516:            mfmpos = (drv->mfmpos + offset) % drv->tracklen;
                    517:            drv->last_cycles += offset*FLOPPY_SPEED;
                    518:            drv->mfmpos = mfmpos;
                    519:            mfmpos++; mfmpos %= drv->tracklen;
                    520:            while (drv->bigmfmbuf[mfmpos] != sync && mfmpos != drv->mfmpos && use_sync)
                    521:                mfmpos = (mfmpos + 1) % drv->tracklen, time++;
1.1.1.2 ! root      522: 
1.1       root      523:            if (drv->bigmfmbuf[mfmpos] != sync && use_sync) {
                    524:                fprintf(stderr, "warning: sync not found on disk read\n");
                    525:                return 0;
                    526:            } else
                    527:                mfmpos++;
1.1.1.2 ! root      528: 
1.1       root      529:            mfm_read_length = length;
                    530: 
                    531:            while (length--) {
                    532:                *mfmp++ = drv->bigmfmbuf[mfmpos];
                    533:                mfmpos = (mfmpos + 1) % drv->tracklen;
                    534:                time++;
                    535:            }
                    536: 
                    537:            return time*FLOPPY_SPEED;
                    538:        }
                    539:     }
                    540:     return 0;    
                    541: }
                    542: 
                    543: int DISK_ReadMFM(CPTR target)
                    544: {
                    545:     int i;
                    546:     UBYTE *mfmp = get_real_address(target);
                    547:     
                    548:     if (!chipmem_bank.check(target, mfm_read_length * 2)) {
                    549:        fprintf(stderr, "warning: Bad disk DMA read pointer\n");
                    550:        return 0;
                    551:     }
                    552: 
                    553:     for (i = 0; i < mfm_read_length; i++) {
                    554:        *mfmp++ = mfm_read_buffer[i] >> 8;
                    555:        *mfmp++ = mfm_read_buffer[i];
                    556:     }
                    557:     return 1;
                    558: }
                    559: 
                    560: void DISK_InitWrite()
                    561: {
                    562:     mfmwrite = mfmwrbuffer;
                    563: }
                    564: 
                    565: void DISK_WriteData(int length)
                    566: {
                    567:     int dr;
                    568:     for (dr=0;dr<4;dr++){
                    569:        if (!(selected & (1<<dr))) {
                    570:            drive_write_data(floppy + dr, mfmwrbuffer, length);
                    571:        }
                    572:     }
                    573: }
1.1.1.2 ! root      574: 
        !           575: void trackdisk_install(void)
        !           576: {
        !           577: }

unix.superglobalmegacorp.com

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