|
|
1.1 ! root 1: /* ! 2: * UAE - The Un*x Amiga Emulator ! 3: * ! 4: * Save/restore emulator state ! 5: * ! 6: * (c) 1999-2001 Toni Wilen ! 7: * ! 8: * see below for ASF-structure ! 9: */ ! 10: ! 11: /* Features: ! 12: * ! 13: * - full CPU state (68000/68010/68020, no FPU) ! 14: * - full CIA-A and CIA-B state (with all internal registers) ! 15: * - saves all custom registers and audio internal state but not all registers are restored yet. ! 16: * - only Chip-ram and Bogo-ram are saved and restored. ! 17: * - disk drive type, imagefile, track and motor state ! 18: * - Kickstart ROM version, address and size is saved. This data is not used during restore yet. ! 19: */ ! 20: ! 21: /* Notes: ! 22: * ! 23: * - blitter state is not saved, blitter is forced to finish immediately if it ! 24: * was active ! 25: * - disk DMA state is completely saved (I hope so..) ! 26: * - does not ask for statefile name and description. Currently uses DF0's disk ! 27: * image name (".adf" is replaced with ".asf") ! 28: * - only Amiga state is restored, harddisk support, autoconfig, expansion boards etc.. ! 29: * are not saved/restored (and probably never will). ! 30: * - use this for saving games that can't be saved to disk ! 31: */ ! 32: ! 33: /* Usage : ! 34: * ! 35: * save: ! 36: * ! 37: * set savestate_state = STATE_DOSAVE, savestate_filename = "..." ! 38: * ! 39: * restore: ! 40: * ! 41: * set savestate_state = STATE_DORESTORE, savestate_filename = "..." ! 42: * ! 43: */ ! 44: ! 45: #include "sysconfig.h" ! 46: #include "sysdeps.h" ! 47: ! 48: #include "config.h" ! 49: #include "options.h" ! 50: #include "memory.h" ! 51: ! 52: #include "savestate.h" ! 53: ! 54: int savestate_state; ! 55: ! 56: char *savestate_filename; ! 57: FILE *savestate_file; ! 58: ! 59: /* functions for reading/writing bytes, shorts and longs in big-endian ! 60: * format independent of host machine's endianess */ ! 61: ! 62: void save_u32_func (uae_u8 **dstp, uae_u32 v) ! 63: { ! 64: uae_u8 *dst = *dstp; ! 65: *dst++ = (uae_u8)(v >> 24); ! 66: *dst++ = (uae_u8)(v >> 16); ! 67: *dst++ = (uae_u8)(v >> 8); ! 68: *dst++ = (uae_u8)(v >> 0); ! 69: *dstp = dst; ! 70: } ! 71: void save_u16_func (uae_u8 **dstp, uae_u16 v) ! 72: { ! 73: uae_u8 *dst = *dstp; ! 74: *dst++ = (uae_u8)(v >> 8); ! 75: *dst++ = (uae_u8)(v >> 0); ! 76: *dstp = dst; ! 77: } ! 78: void save_u8_func (uae_u8 **dstp, uae_u8 v) ! 79: { ! 80: uae_u8 *dst = *dstp; ! 81: *dst++ = v; ! 82: *dstp = dst; ! 83: } ! 84: void save_string_func (uae_u8 **dstp, char *from) ! 85: { ! 86: uae_u8 *dst = *dstp; ! 87: while(*from) ! 88: *dst++ = *from++; ! 89: *dst++ = 0; ! 90: *dstp = dst; ! 91: } ! 92: ! 93: uae_u32 restore_u32_func (uae_u8 **dstp) ! 94: { ! 95: uae_u32 v; ! 96: uae_u8 *dst = *dstp; ! 97: v = (dst[0] << 24) | (dst[1] << 16) | (dst[2] << 8) | (dst[3]); ! 98: *dstp = dst + 4; ! 99: return v; ! 100: } ! 101: uae_u16 restore_u16_func (uae_u8 **dstp) ! 102: { ! 103: uae_u16 v; ! 104: uae_u8 *dst = *dstp; ! 105: v=(dst[0] << 8) | (dst[1]); ! 106: *dstp = dst + 2; ! 107: return v; ! 108: } ! 109: uae_u8 restore_u8_func (uae_u8 **dstp) ! 110: { ! 111: uae_u8 v; ! 112: uae_u8 *dst = *dstp; ! 113: v = dst[0]; ! 114: *dstp = dst + 1; ! 115: return v; ! 116: } ! 117: char *restore_string_func (uae_u8 **dstp) ! 118: { ! 119: int len; ! 120: uae_u8 v; ! 121: uae_u8 *dst = *dstp; ! 122: char *top, *to; ! 123: len = strlen(dst) + 1; ! 124: top = to = malloc (len); ! 125: do { ! 126: v = *dst++; ! 127: *top++ = v; ! 128: } while(v); ! 129: *dstp = dst; ! 130: return to; ! 131: } ! 132: ! 133: /* read and write IFF-style hunks */ ! 134: ! 135: static void save_chunk (FILE *f, uae_u8 *chunk, long len, char *name) ! 136: { ! 137: uae_u8 tmp[4], *dst; ! 138: uae_u8 zero[4]= { 0, 0, 0, 0 }; ! 139: ! 140: if (!chunk) ! 141: return; ! 142: ! 143: /* chunk name */ ! 144: fwrite (name, 1, 4, f); ! 145: /* chunk size */ ! 146: dst = &tmp[0]; ! 147: save_u32 (len + 4 + 4 + 4); ! 148: fwrite (&tmp[0], 1, 4, f); ! 149: /* chunk flags */ ! 150: dst = &tmp[0]; ! 151: save_u32 (0); ! 152: fwrite (&tmp[0], 1, 4, f); ! 153: /* chunk data */ ! 154: fwrite (chunk, 1, len, f); ! 155: /* alignment */ ! 156: len = 4 - (len & 3); ! 157: if (len) ! 158: fwrite (zero, 1, len, f); ! 159: } ! 160: ! 161: static uae_u8 *restore_chunk (FILE *f, char *name, long *len, long *filepos) ! 162: { ! 163: uae_u8 tmp[4], dummy[4], *mem, *src; ! 164: uae_u32 flags; ! 165: long len2; ! 166: ! 167: /* chunk name */ ! 168: fread (name, 1, 4, f); ! 169: name[4] = 0; ! 170: /* chunk size */ ! 171: fread (tmp, 1, 4, f); ! 172: src = tmp; ! 173: len2 = restore_u32 () - 4 - 4 - 4; ! 174: if (len2 < 0) ! 175: len2 = 0; ! 176: *len = len2; ! 177: if (len2 == 0) ! 178: return 0; ! 179: ! 180: /* chunk flags */ ! 181: fread (tmp, 1, 4, f); ! 182: src = tmp; ! 183: flags = restore_u32 (); ! 184: ! 185: *filepos = ftell (f); ! 186: /* chunk data. RAM contents will be loaded during the reset phase, ! 187: no need to malloc multiple megabytes here. */ ! 188: if (strcmp (name, "CRAM") != 0 ! 189: && strcmp (name, "BRAM") != 0 ! 190: && strcmp (name, "FRAM") != 0 ! 191: && strcmp (name, "ZRAM") != 0) ! 192: { ! 193: mem = malloc (len2); ! 194: fread (mem, 1, len2, f); ! 195: } else { ! 196: mem = 0; ! 197: fseek (f, len2, SEEK_CUR); ! 198: } ! 199: ! 200: /* alignment */ ! 201: len2 = 4 - (len2 & 3); ! 202: if (len2) ! 203: fread (dummy, 1, len2, f); ! 204: return mem; ! 205: } ! 206: ! 207: static void restore_header (uae_u8 *src) ! 208: { ! 209: char *emuname, *emuversion, *description; ! 210: ! 211: restore_u32(); ! 212: emuname = restore_string (); ! 213: emuversion = restore_string (); ! 214: description = restore_string (); ! 215: write_log ("Saved with: '%s %s', description: '%s'\n", ! 216: emuname,emuversion,description); ! 217: free (description); ! 218: free (emuversion); ! 219: free (emuname); ! 220: } ! 221: ! 222: /* restore all subsystems */ ! 223: ! 224: void restore_state (char *filename) ! 225: { ! 226: FILE *f; ! 227: uae_u8 *chunk,*end; ! 228: char name[5]; ! 229: long len; ! 230: long filepos; ! 231: ! 232: chunk = 0; ! 233: f = fopen (filename, "rb"); ! 234: if (!f) ! 235: goto error; ! 236: ! 237: chunk = restore_chunk (f, name, &len, &filepos); ! 238: if (!chunk || memcmp (name, "ASF ", 4)) { ! 239: write_log ("%s is not an AmigaStateFile\n",filename); ! 240: goto error; ! 241: } ! 242: savestate_file = f; ! 243: restore_header (chunk); ! 244: free (chunk); ! 245: savestate_state = STATE_RESTORE; ! 246: for (;;) { ! 247: chunk = restore_chunk (f, name, &len, &filepos); ! 248: write_log ("Chunk '%s' size %d\n", name, len); ! 249: if (!strcmp (name, "END ")) ! 250: break; ! 251: if (!strcmp (name, "CRAM")) { ! 252: restore_cram (len, filepos); ! 253: continue; ! 254: } ! 255: else if (!strcmp (name, "BRAM")) { ! 256: restore_bram (len, filepos); ! 257: continue; ! 258: } else if (!strcmp (name, "FRAM")) { ! 259: restore_fram (len, filepos); ! 260: continue; ! 261: } else if (!strcmp (name, "ZRAM")) { ! 262: restore_zram (len, filepos); ! 263: continue; ! 264: } ! 265: ! 266: if (!strcmp (name, "CPU ")) ! 267: end = restore_cpu (chunk); ! 268: else if (!strcmp (name, "AGAC")) ! 269: end = restore_custom_agacolors (chunk); ! 270: else if (!strcmp (name, "SPR0")) ! 271: end = restore_custom_sprite (chunk, 0); ! 272: else if (!strcmp (name, "SPR1")) ! 273: end = restore_custom_sprite (chunk, 1); ! 274: else if (!strcmp (name, "SPR2")) ! 275: end = restore_custom_sprite (chunk, 2); ! 276: else if (!strcmp (name, "SPR3")) ! 277: end = restore_custom_sprite (chunk, 3); ! 278: else if (!strcmp (name, "SPR4")) ! 279: end = restore_custom_sprite (chunk, 4); ! 280: else if (!strcmp (name, "SPR5")) ! 281: end = restore_custom_sprite (chunk, 5); ! 282: else if (!strcmp (name, "SPR6")) ! 283: end = restore_custom_sprite (chunk, 6); ! 284: else if (!strcmp (name, "SPR7")) ! 285: end = restore_custom_sprite (chunk, 7); ! 286: else if (!strcmp (name, "CIAA")) ! 287: end = restore_cia (0, chunk); ! 288: else if (!strcmp (name, "CIAB")) ! 289: end = restore_cia (1, chunk); ! 290: else if (!strcmp (name, "CHIP")) ! 291: end = restore_custom (chunk); ! 292: else if (!strcmp (name, "AUD0")) ! 293: end = restore_audio (chunk, 0); ! 294: else if (!strcmp (name, "AUD1")) ! 295: end = restore_audio (chunk, 1); ! 296: else if (!strcmp (name, "AUD2")) ! 297: end = restore_audio (chunk, 2); ! 298: else if (!strcmp (name, "AUD3")) ! 299: end = restore_audio (chunk, 3); ! 300: #if 0 ! 301: else if (!strcmp (name, "BLIT")) ! 302: end = restore_custom_blitter (chunk); ! 303: #endif ! 304: else if (!strcmp (name, "DISK")) ! 305: end = restore_floppy (chunk); ! 306: else if (!strcmp (name, "DSK0")) ! 307: end = restore_disk (0, chunk); ! 308: else if (!strcmp (name, "DSK1")) ! 309: end = restore_disk (1, chunk); ! 310: else if (!strcmp (name, "DSK2")) ! 311: end = restore_disk (2, chunk); ! 312: else if (!strcmp (name, "DSK3")) ! 313: end = restore_disk (3, chunk); ! 314: else if (!strcmp (name, "EXPA")) ! 315: end = restore_expansion (chunk); ! 316: else if (!strcmp (name, "ROM ")) ! 317: end = restore_rom (chunk); ! 318: else ! 319: write_log ("unknown chunk '%s' size %d bytes\n", name, len); ! 320: if (len != end - chunk) ! 321: write_log ("Chunk '%s' total size %d bytes but read %d bytes!\n", ! 322: name, len, end - chunk); ! 323: free (chunk); ! 324: } ! 325: return; ! 326: ! 327: error: ! 328: savestate_state = 0; ! 329: savestate_file = 0; ! 330: if (chunk) ! 331: free (chunk); ! 332: if (f) ! 333: fclose (f); ! 334: } ! 335: ! 336: void savestate_restore_finish (void) ! 337: { ! 338: if (savestate_state != STATE_RESTORE) ! 339: return; ! 340: fclose (savestate_file); ! 341: savestate_file = 0; ! 342: savestate_state = 0; ! 343: } ! 344: ! 345: /* Save all subsystems */ ! 346: ! 347: void save_state (char *filename, char *description) ! 348: { ! 349: uae_u8 header[1000]; ! 350: char tmp[100]; ! 351: uae_u8 *dst; ! 352: FILE *f; ! 353: int len,i; ! 354: char name[5]; ! 355: ! 356: f = fopen (filename, "wb"); ! 357: if (!f) ! 358: return; ! 359: ! 360: dst = header; ! 361: save_u32 (0); ! 362: save_string("UAE"); ! 363: sprintf (tmp, "%d.%d.%d", UAEMAJOR, UAEMINOR, UAESUBREV); ! 364: save_string (tmp); ! 365: save_string (description); ! 366: save_chunk (f, header, dst-header, "ASF "); ! 367: ! 368: dst = save_cpu (&len); ! 369: save_chunk (f, dst, len, "CPU "); ! 370: free (dst); ! 371: ! 372: strcpy(name, "DSKx"); ! 373: for (i = 0; i < 4; i++) { ! 374: dst = save_disk (i, &len); ! 375: if (dst) { ! 376: name[3] = i + '0'; ! 377: save_chunk (f, dst, len, name); ! 378: free (dst); ! 379: } ! 380: } ! 381: dst = save_floppy (&len); ! 382: save_chunk (f, dst, len, "DISK"); ! 383: free (dst); ! 384: ! 385: dst = save_custom (&len); ! 386: save_chunk (f, dst, len, "CHIP"); ! 387: free (dst); ! 388: ! 389: #if 0 ! 390: dst = save_custom_blitter (&len); ! 391: save_chunk (f, dst, len, "BLIT"); ! 392: free (dst); ! 393: #endif ! 394: ! 395: dst = save_custom_agacolors (&len); ! 396: save_chunk (f, dst, len, "AGAC"); ! 397: free (dst); ! 398: ! 399: strcpy (name, "SPRx"); ! 400: for (i = 0; i < 8; i++) { ! 401: dst = save_custom_sprite (&len, i); ! 402: name[3] = i + '0'; ! 403: save_chunk (f, dst, len, name); ! 404: free (dst); ! 405: } ! 406: ! 407: strcpy (name, "AUDx"); ! 408: for (i = 0; i < 4; i++) { ! 409: dst = save_audio (&len, i); ! 410: name[3] = i + '0'; ! 411: save_chunk (f, dst, len, name); ! 412: free (dst); ! 413: } ! 414: ! 415: dst = save_cia (0, &len); ! 416: save_chunk (f, dst, len, "CIAA"); ! 417: free (dst); ! 418: ! 419: dst = save_cia (1, &len); ! 420: save_chunk (f, dst, len, "CIAB"); ! 421: free (dst); ! 422: ! 423: dst = save_expansion (&len); ! 424: save_chunk (f, dst, len, "EXPA"); ! 425: dst = save_cram (&len); ! 426: save_chunk (f, dst, len, "CRAM"); ! 427: dst = save_bram (&len); ! 428: save_chunk (f, dst, len, "BRAM"); ! 429: dst = save_fram (&len); ! 430: save_chunk (f, dst, len, "FRAM"); ! 431: dst = save_zram (&len); ! 432: save_chunk (f, dst, len, "ZRAM"); ! 433: ! 434: dst = save_rom (1, &len); ! 435: do { ! 436: if (!dst) ! 437: break; ! 438: save_chunk (f, dst, len, "ROM "); ! 439: free (dst); ! 440: } while ((dst = save_rom (0, &len))); ! 441: ! 442: fwrite ("END ", 1, 4, f); ! 443: fwrite ("\0\0\0\08", 1, 4, f); ! 444: write_log ("Save of '%s' complete\n", filename); ! 445: fclose (f); ! 446: } ! 447: ! 448: /* ! 449: ! 450: My (Toni Wilen <[email protected]>) ! 451: proposal for Amiga-emulators' state-save format ! 452: ! 453: Feel free to comment... ! 454: ! 455: This is very similar to IFF-fileformat ! 456: Every hunk must end to 4 byte boundary, ! 457: fill with zero bytes if needed ! 458: ! 459: version 0.7 ! 460: ! 461: HUNK HEADER (beginning of every hunk) ! 462: ! 463: hunk name (4 ascii-characters) ! 464: hunk size (including header) ! 465: hunk flags ! 466: ! 467: bit 0 = chunk contents are compressed with zlib (maybe RAM chunks only?) ! 468: ! 469: HEADER ! 470: ! 471: "ASF " (AmigaStateFile) ! 472: ! 473: statefile version ! 474: emulator name ("uae", "fellow" etc..) ! 475: emulator version string (example: "0.8.15") ! 476: free user writable comment string ! 477: ! 478: CPU ! 479: ! 480: "CPU " ! 481: ! 482: CPU model 4 (68000,68010 etc..) ! 483: CPU typeflags bit 0=EC-model or not ! 484: D0-D7 8*4=32 ! 485: A0-A6 7*4=32 ! 486: PC 4 ! 487: prefetch address 4 ! 488: prefetch data 4 ! 489: USP 4 ! 490: ISP 4 ! 491: SR/CCR 2 ! 492: flags 4 (bit 0=CPU was HALTed) ! 493: ! 494: CPU specific registers ! 495: ! 496: 68000: SR/CCR is last saved register ! 497: 68010: save also DFC,SFC and VBR ! 498: 68020: all 68010 registers and CAAR,CACR and MSP ! 499: etc.. ! 500: ! 501: DFC 4 (010+) ! 502: SFC 4 (010+) ! 503: VBR 4 (010+) ! 504: ! 505: CAAR 4 (020-030) ! 506: CACR 4 (020+) ! 507: MSP 4 (020+) ! 508: ! 509: MMU (when and if MMU is supported in future..) ! 510: ! 511: MMU model 4 (68851,68030,68040) ! 512: ! 513: // 68040 fields ! 514: ! 515: ITT0 4 ! 516: ITT1 4 ! 517: DTT0 4 ! 518: DTT1 4 ! 519: URP 4 ! 520: SRP 4 ! 521: MMUSR 4 ! 522: TC 2 ! 523: ! 524: ! 525: FPU (only if used) ! 526: ! 527: "FPU " ! 528: ! 529: FPU model 4 (68881 or 68882) ! 530: FPU typeflags 4 (keep zero) ! 531: ! 532: FP0-FP7 4+2 (80 bits) ! 533: FPCR 4 ! 534: FPSR 4 ! 535: FPIAR 4 ! 536: ! 537: CUSTOM CHIPS ! 538: ! 539: "CHIP" ! 540: ! 541: chipset flags 4 OCS=0,ECSAGNUS=1,ECSDENISE=2,AGA=4 ! 542: ECSAGNUS and ECSDENISE can be combined ! 543: ! 544: DFF000-DFF1FF 352 (0x120 - 0x17f and 0x0a0 - 0xdf excluded) ! 545: ! 546: sprite registers (0x120 - 0x17f) saved with SPRx chunks ! 547: audio registers (0x0a0 - 0xdf) saved with AUDx chunks ! 548: ! 549: AGA COLORS ! 550: ! 551: "AGAC" ! 552: ! 553: AGA color 8 banks * 32 registers * ! 554: registers LONG (XRGB) = 1024 ! 555: ! 556: SPRITE ! 557: ! 558: "SPR0" - "SPR7" ! 559: ! 560: ! 561: SPRxPT 4 ! 562: SPRxPOS 2 ! 563: SPRxCTL 2 ! 564: SPRxDATA 2 ! 565: SPRxDATB 2 ! 566: AGA sprite DATA/DATB 3 * 2 * 2 ! 567: sprite "armed" status 1 ! 568: ! 569: sprites maybe armed in non-DMA mode ! 570: use bit 0 only, other bits are reserved ! 571: ! 572: ! 573: AUDIO ! 574: "AUD0" "AUD1" "AUD2" "AUD3" ! 575: ! 576: audio state 1 ! 577: machine mode ! 578: AUDxVOL 1 ! 579: irq? 1 ! 580: data_written? 1 ! 581: internal AUDxLEN 2 ! 582: AUDxLEN 2 ! 583: internal AUDxPER 2 ! 584: AUDxPER 2 ! 585: internal AUDxLC 4 ! 586: AUDxLC 4 ! 587: evtime? 4 ! 588: ! 589: BLITTER ! 590: ! 591: "BLIT" ! 592: ! 593: internal blitter state ! 594: ! 595: blitter running 1 ! 596: anything else? ! 597: ! 598: CIA ! 599: ! 600: "CIAA" and "CIAB" ! 601: ! 602: BFE001-BFEF01 16*1 (CIAA) ! 603: BFD000-BFDF00 16*1 (CIAB) ! 604: ! 605: internal registers ! 606: ! 607: IRQ mask (ICR) 1 BYTE ! 608: timer latches 2 timers * 2 BYTES (LO/HI) ! 609: latched tod 3 BYTES (LO/MED/HI) ! 610: alarm 3 BYTES (LO/MED/HI) ! 611: flags 1 BYTE ! 612: bit 0=tod latched (read) ! 613: bit 1=tod stopped (write) ! 614: div10 counter 1 BYTE ! 615: ! 616: FLOPPY DRIVES ! 617: ! 618: "DSK0" "DSK1" "DSK2" "DSK3" ! 619: ! 620: drive state ! 621: ! 622: drive ID-word 4 ! 623: motor state 1 (off=0,on=1) ! 624: rw-head track 1 ! 625: dskready 1 ! 626: id-mode 1 (ID mode bit number 0-31) ! 627: floppy information ! 628: ! 629: bits from 4 ! 630: beginning of track ! 631: CRC of disk-image 4 (used during restore to check if image ! 632: is correct) ! 633: disk-image null-terminated ! 634: file name ! 635: ! 636: INTERNAL FLOPPY CONTROLLER STATUS ! 637: ! 638: "DISK" ! 639: ! 640: current DMA word 2 ! 641: DMA word bit offset 1 ! 642: WORDSYNC found 1 (no=0,yes=1) ! 643: hpos of next bit 1 ! 644: DSKLENGTH status 0=off,1=written once,2=written twice ! 645: ! 646: RAM SPACE ! 647: ! 648: "xRAM" (CRAM = chip, BRAM = bogo, FRAM = fast, ZFRAM = Z3) ! 649: ! 650: start address 4 ("bank"=chip/slow/fast etc..) ! 651: of RAM "bank" ! 652: RAM "bank" size 4 ! 653: RAM flags 4 ! 654: RAM "bank" contents ! 655: ! 656: ROM SPACE ! 657: ! 658: "ROM " ! 659: ! 660: ROM start 4 ! 661: address ! 662: size of ROM 4 ! 663: ROM type 4 KICK=0 ! 664: ROM flags 4 ! 665: ROM version 2 ! 666: ROM revision 2 ! 667: ROM CRC 4 see below ! 668: ROM-image null terminated, see below ! 669: ID-string ! 670: ROM contents (Not mandatory, use hunk size to check if ! 671: this hunk contains ROM data or not) ! 672: ! 673: Kickstart ROM: ! 674: ID-string is "Kickstart x.x" ! 675: ROM version: version in high word and revision in low word ! 676: Kickstart ROM version and revision can be found from ROM start ! 677: + 12 (version) and +14 (revision) ! 678: ! 679: ROM version and CRC is only meant for emulator to automatically ! 680: find correct image from its ROM-directory during state restore. ! 681: ! 682: Usually saving ROM contents is not good idea. ! 683: ! 684: ! 685: END ! 686: hunk "END " ends, remember hunk size 8! ! 687: ! 688: ! 689: EMULATOR SPECIFIC HUNKS ! 690: ! 691: Read only if "emulator name" in header is same as used emulator. ! 692: Maybe useful for configuration? ! 693: ! 694: misc: ! 695: ! 696: - save only at position 0,0 before triggering VBLANK interrupt ! 697: - all data must be saved in bigendian format ! 698: - should we strip all paths from image file names? ! 699: ! 700: */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.