|
|
1.1 root 1: /*
1.1.1.6 root 2: Hatari - main.c
3:
4: This file is distributed under the GNU Public License, version 2 or at
5: your option any later version. Read the file gpl.txt for details.
6:
7: Main initialization and event handling routines.
1.1 root 8: */
1.1.1.15! root 9: const char Main_rcsid[] = "Hatari $Id: main.c,v 1.153 2008/11/23 10:27:13 thothy Exp $";
1.1 root 10:
1.1.1.13 root 11: #include <time.h>
1.1 root 12: #include <SDL.h>
13:
1.1.1.13 root 14: #include "config.h"
1.1 root 15: #include "main.h"
16: #include "configuration.h"
1.1.1.15! root 17: #include "control.h"
1.1.1.12 root 18: #include "options.h"
1.1 root 19: #include "dialog.h"
20: #include "audio.h"
21: #include "joy.h"
22: #include "floppy.h"
23: #include "gemdos.h"
1.1.1.4 root 24: #include "hdc.h"
1.1 root 25: #include "ikbd.h"
1.1.1.10 root 26: #include "ioMem.h"
1.1.1.5 root 27: #include "keymap.h"
1.1.1.10 root 28: #include "log.h"
1.1 root 29: #include "m68000.h"
30: #include "memorySnapShot.h"
1.1.1.8 root 31: #include "midi.h"
1.1.1.13 root 32: #include "nvram.h"
33: #include "paths.h"
1.1 root 34: #include "printer.h"
1.1.1.8 root 35: #include "reset.h"
1.1 root 36: #include "rs232.h"
37: #include "screen.h"
1.1.1.5 root 38: #include "sdlgui.h"
1.1 root 39: #include "shortcut.h"
40: #include "sound.h"
1.1.1.15! root 41: #include "statusbar.h"
1.1.1.8 root 42: #include "stMemory.h"
1.1.1.15! root 43: #include "str.h"
1.1 root 44: #include "tos.h"
45: #include "video.h"
46:
1.1.1.13 root 47: #include "hatari-glue.h"
48:
49: #include "falcon/hostscreen.h"
50: #if ENABLE_DSP_EMU
51: #include "falcon/dsp.h"
52: #endif
1.1 root 53:
54:
1.1.1.15! root 55: bool bQuitProgram = FALSE; /* Flag to quit program cleanly */
! 56: bool bEnableDebug = FALSE; /* Enable debug UI? */
1.1.1.13 root 57:
1.1.1.15! root 58: static bool bEmulationActive = TRUE; /* Run emulation when started */
! 59: static bool bAccurateDelays; /* Host system has an accurate SDL_Delay()? */
! 60: static bool bIgnoreNextMouseMotion = FALSE; /* Next mouse motion will be ignored (needed after SDL_WarpMouse) */
1.1 root 61:
62:
63: /*-----------------------------------------------------------------------*/
1.1.1.13 root 64: /**
65: * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
66: */
1.1.1.15! root 67: void Main_MemorySnapShot_Capture(bool bSave)
1.1 root 68: {
1.1.1.13 root 69: int nBytes;
1.1 root 70:
1.1.1.13 root 71: /* Save/Restore details */
72: /* Only save/restore area of memory machine ie set to, eg 1Mb */
73: if (bSave)
74: {
75: nBytes = STRamEnd;
76: MemorySnapShot_Store(&nBytes, sizeof(nBytes));
77: MemorySnapShot_Store(STRam, nBytes);
78: }
79: else
80: {
81: MemorySnapShot_Store(&nBytes, sizeof(nBytes));
82: MemorySnapShot_Store(STRam, nBytes);
83: }
84: /* And Cart/TOS/Hardware area */
1.1.1.15! root 85: MemorySnapShot_Store(&RomMem[0xE00000], 0x200000);
1.1 root 86: }
87:
88:
89: /*-----------------------------------------------------------------------*/
1.1.1.13 root 90: /**
1.1.1.15! root 91: * Pause emulation, stop sound. 'visualize' should be set TRUE,
! 92: * unless unpause will be called immediately afterwards.
! 93: *
! 94: * Return TRUE if paused now, FALSE if was already paused
1.1.1.13 root 95: */
1.1.1.15! root 96: bool Main_PauseEmulation(bool visualize)
1.1 root 97: {
1.1.1.15! root 98: if ( !bEmulationActive )
! 99: return FALSE;
! 100:
! 101: Audio_EnableAudio(FALSE);
! 102: bEmulationActive = FALSE;
! 103: if (visualize)
1.1.1.13 root 104: {
1.1.1.15! root 105: Statusbar_AddMessage("Emulation paused", 100);
! 106: /* make sure msg gets shown */
! 107: Statusbar_Update(sdlscrn);
1.1.1.13 root 108: }
1.1.1.15! root 109: return TRUE;
1.1 root 110: }
111:
1.1.1.2 root 112: /*-----------------------------------------------------------------------*/
1.1.1.13 root 113: /**
1.1.1.15! root 114: * Start/continue emulation
! 115: *
! 116: * Return TRUE if continued, FALSE if was already running
1.1.1.13 root 117: */
1.1.1.15! root 118: bool Main_UnPauseEmulation(void)
1.1 root 119: {
1.1.1.15! root 120: if ( bEmulationActive )
! 121: return FALSE;
1.1 root 122:
1.1.1.15! root 123: Sound_ResetBufferIndex();
! 124: Audio_EnableAudio(ConfigureParams.Sound.bEnableSound);
! 125: bEmulationActive = TRUE;
! 126:
! 127: /* Cause full screen update (to clear all) */
! 128: Screen_SetFullUpdate();
! 129: return TRUE;
1.1 root 130: }
131:
1.1.1.13 root 132: /*-----------------------------------------------------------------------*/
133: /**
134: * Optionally ask user whether to quit and set bQuitProgram accordingly
135: */
136: void Main_RequestQuit(void)
137: {
138: if (ConfigureParams.Memory.bAutoSave)
139: {
140: bQuitProgram = TRUE;
141: MemorySnapShot_Capture(ConfigureParams.Memory.szAutoSaveFileName, FALSE);
142: }
143: else if (ConfigureParams.Log.bConfirmQuit)
144: {
145: bQuitProgram = FALSE; /* if set TRUE, dialog exits */
146: bQuitProgram = DlgAlert_Query("All unsaved data will be lost.\nDo you really want to quit?");
147: }
148: else
149: {
150: bQuitProgram = TRUE;
151: }
152:
153: if (bQuitProgram)
154: {
155: /* Assure that CPU core shuts down */
156: M68000_SetSpecial(SPCFLAG_BRK);
157: }
158: }
1.1.1.7 root 159:
1.1.1.12 root 160: /*-----------------------------------------------------------------------*/
1.1.1.13 root 161: /**
162: * This function waits on each emulated VBL to synchronize the real time
163: * with the emulated ST.
164: * Unfortunately SDL_Delay and other sleep functions like usleep or nanosleep
165: * are very inaccurate on some systems like Linux 2.4 or Mac OS X (they can only
166: * wait for a multiple of 10ms due to the scheduler on these systems), so we have
167: * to "busy wait" there to get an accurate timing.
168: */
1.1.1.12 root 169: void Main_WaitOnVbl(void)
170: {
1.1.1.13 root 171: int nCurrentMilliTicks;
172: static int nDestMilliTicks = 0;
173: int nFrameDuration;
174: signed int nDelay;
175:
176: nCurrentMilliTicks = SDL_GetTicks();
177:
178: nFrameDuration = 1000/nScreenRefreshRate;
179: nDelay = nDestMilliTicks - nCurrentMilliTicks;
180:
1.1.1.15! root 181: /* Do not wait if we are in fast forward mode or if we are totally out of sync */
! 182: if (ConfigureParams.System.bFastForward == TRUE
1.1.1.13 root 183: || nDelay < -4*nFrameDuration)
184: {
1.1.1.15! root 185: if (nFrameSkips < ConfigureParams.Screen.nFrameSkips)
! 186: {
! 187: nFrameSkips += 1;
! 188: Log_Printf(LOG_DEBUG, "Increased frameskip to %d\n", nFrameSkips);
! 189: }
1.1.1.13 root 190: /* Only update nDestMilliTicks for next VBL */
191: nDestMilliTicks = nCurrentMilliTicks + nFrameDuration;
192: return;
193: }
1.1.1.15! root 194: /* If automatic frameskip is enabled and delay's more than twice
! 195: * the effect of single frameskip, decrease frameskip
! 196: */
! 197: if (nFrameSkips > 0
! 198: && ConfigureParams.Screen.nFrameSkips >= AUTO_FRAMESKIP_LIMIT
! 199: && 2*nDelay > nFrameDuration/nFrameSkips)
! 200: {
! 201: nFrameSkips -= 1;
! 202: Log_Printf(LOG_DEBUG, "Decreased frameskip to %d\n", nFrameSkips);
! 203: }
1.1.1.12 root 204:
1.1.1.13 root 205: if (bAccurateDelays)
206: {
207: /* Accurate sleeping is possible -> use SDL_Delay to free the CPU */
208: if (nDelay > 1)
209: SDL_Delay(nDelay - 1);
210: }
211: else
212: {
213: /* No accurate SDL_Delay -> only wait if more than 5ms to go... */
214: if (nDelay > 5)
215: SDL_Delay(nDelay<10 ? nDelay-1 : 9);
216: }
217:
218: /* Now busy-wait for the right tick: */
219: while (nDelay > 0)
220: {
221: nCurrentMilliTicks = SDL_GetTicks();
222: nDelay = nDestMilliTicks - nCurrentMilliTicks;
223: }
224:
225: /* Update nDestMilliTicks for next VBL */
226: nDestMilliTicks += nFrameDuration;
1.1.1.12 root 227: }
228:
229:
230: /*-----------------------------------------------------------------------*/
1.1.1.13 root 231: /**
232: * Since SDL_Delay and friends are very inaccurate on some systems, we have
233: * to check if we can rely on this delay function.
234: */
1.1.1.12 root 235: static void Main_CheckForAccurateDelays(void)
236: {
1.1.1.13 root 237: int nStartTicks, nEndTicks;
1.1.1.12 root 238:
1.1.1.13 root 239: /* Force a task switch now, so we have a longer timeslice afterwards */
240: SDL_Delay(10);
1.1.1.12 root 241:
1.1.1.13 root 242: nStartTicks = SDL_GetTicks();
243: SDL_Delay(1);
244: nEndTicks = SDL_GetTicks();
245:
246: /* If the delay took longer than 10ms, we are on an inaccurate system! */
247: bAccurateDelays = ((nEndTicks - nStartTicks) < 9);
248:
249: if (bAccurateDelays)
250: Log_Printf(LOG_DEBUG, "Host system has accurate delays. (%d)\n", nEndTicks - nStartTicks);
251: else
1.1.1.15! root 252: Log_Printf(LOG_WARN, "Host system does not have accurate delays. (%d)\n", nEndTicks - nStartTicks);
1.1.1.12 root 253: }
254:
255:
1.1 root 256: /* ----------------------------------------------------------------------- */
1.1.1.13 root 257: /**
258: * Set mouse pointer to new coordinates and set flag to ignore the mouse event
259: * that is generated by SDL_WarpMouse().
260: */
1.1.1.9 root 261: void Main_WarpMouse(int x, int y)
262: {
1.1.1.13 root 263: SDL_WarpMouse(x, y); /* Set mouse pointer to new position */
264: bIgnoreNextMouseMotion = TRUE; /* Ignore mouse motion event from SDL_WarpMouse */
1.1.1.9 root 265: }
266:
267:
268: /* ----------------------------------------------------------------------- */
1.1.1.13 root 269: /**
270: * Handle mouse motion event.
271: */
1.1.1.12 root 272: static void Main_HandleMouseMotion(SDL_Event *pEvent)
273: {
274: int dx, dy;
275: static int ax = 0, ay = 0;
276:
1.1.1.13 root 277:
1.1.1.12 root 278: if (bIgnoreNextMouseMotion)
279: {
280: bIgnoreNextMouseMotion = FALSE;
281: return;
282: }
283:
284: dx = pEvent->motion.xrel;
285: dy = pEvent->motion.yrel;
286:
1.1.1.13 root 287: /* In zoomed low res mode, we divide dx and dy by the zoom factor so that
288: * the ST mouse cursor stays in sync with the host mouse. However, we have
289: * to take care of lowest bit of dx and dy which will get lost when
290: * dividing. So we store these bits in ax and ay and add them to dx and dy
291: * the next time. */
292: if (nScreenZoomX != 1)
293: {
1.1.1.12 root 294: dx += ax;
1.1.1.13 root 295: ax = dx % nScreenZoomX;
296: dx /= nScreenZoomX;
1.1.1.12 root 297: }
1.1.1.13 root 298: if (nScreenZoomY != 1)
1.1.1.12 root 299: {
300: dy += ay;
1.1.1.13 root 301: ay = dy % nScreenZoomY;
302: dy /= nScreenZoomY;
1.1.1.12 root 303: }
304:
305: KeyboardProcessor.Mouse.dx += dx;
306: KeyboardProcessor.Mouse.dy += dy;
307: }
308:
309:
310: /* ----------------------------------------------------------------------- */
1.1.1.13 root 311: /**
312: * SDL message handler.
313: * Here we process the SDL events (keyboard, mouse, ...) and map it to
314: * Atari IKBD events.
315: */
1.1.1.8 root 316: void Main_EventHandler(void)
1.1 root 317: {
1.1.1.13 root 318: SDL_Event event;
1.1.1.15! root 319: int ok;
! 320:
! 321: do
1.1.1.13 root 322: {
1.1.1.15! root 323: /* check remote process control */
! 324: int remotepause = Control_CheckUpdates();
! 325:
! 326: if ( bEmulationActive || remotepause )
! 327: {
! 328: ok = SDL_PollEvent(&event);
! 329: }
! 330: else
! 331: {
! 332: ShortCut_ActKey();
! 333: /* last (shortcut) event activated emulation? */
! 334: if ( bEmulationActive )
! 335: break;
! 336: ok = SDL_WaitEvent(&event);
! 337: }
! 338: if (!ok)
! 339: {
! 340: continue;
! 341: }
1.1.1.13 root 342: switch (event.type)
343: {
1.1 root 344:
1.1.1.13 root 345: case SDL_QUIT:
346: Main_RequestQuit();
347: break;
348:
349: case SDL_MOUSEMOTION: /* Read/Update internal mouse position */
350: Main_HandleMouseMotion(&event);
351: break;
352:
353: case SDL_MOUSEBUTTONDOWN:
354: if (event.button.button == SDL_BUTTON_LEFT)
355: {
356: if (Keyboard.LButtonDblClk == 0)
357: Keyboard.bLButtonDown |= BUTTON_MOUSE; /* Set button down flag */
358: }
359: else if (event.button.button == SDL_BUTTON_RIGHT)
360: {
361: Keyboard.bRButtonDown |= BUTTON_MOUSE;
362: }
363: else if (event.button.button == SDL_BUTTON_MIDDLE)
364: {
365: /* Start double-click sequence in emulation time */
366: Keyboard.LButtonDblClk = 1;
367: }
368: else if (event.button.button == SDL_BUTTON_WHEELDOWN)
369: {
370: /* Simulate pressing the "cursor down" key */
371: IKBD_PressSTKey(0x50, TRUE);
372: }
373: else if (event.button.button == SDL_BUTTON_WHEELUP)
374: {
375: /* Simulate pressing the "cursor up" key */
376: IKBD_PressSTKey(0x48, TRUE);
377: }
378: break;
379:
380: case SDL_MOUSEBUTTONUP:
381: if (event.button.button == SDL_BUTTON_LEFT)
382: {
383: Keyboard.bLButtonDown &= ~BUTTON_MOUSE;
384: }
385: else if (event.button.button == SDL_BUTTON_RIGHT)
386: {
387: Keyboard.bRButtonDown &= ~BUTTON_MOUSE;
388: }
389: else if (event.button.button == SDL_BUTTON_WHEELDOWN)
390: {
391: /* Simulate releasing the "cursor down" key */
392: IKBD_PressSTKey(0x50, FALSE);
393: }
394: else if (event.button.button == SDL_BUTTON_WHEELUP)
395: {
396: /* Simulate releasing the "cursor up" key */
397: IKBD_PressSTKey(0x48, FALSE);
398: }
399: break;
400:
401: case SDL_KEYDOWN:
402: Keymap_KeyDown(&event.key.keysym);
403: break;
404:
405: case SDL_KEYUP:
406: Keymap_KeyUp(&event.key.keysym);
407: break;
408: }
1.1.1.15! root 409: } while (!(bEmulationActive || bQuitProgram));
1.1.1.13 root 410: }
411:
412:
413: /*-----------------------------------------------------------------------*/
414: /**
415: * Initialise emulation
416: */
1.1.1.8 root 417: static void Main_Init(void)
1.1 root 418: {
1.1.1.13 root 419: /* Open debug log file */
1.1.1.15! root 420: if (!Log_Init())
! 421: {
! 422: fprintf(stderr, "Logging/tracing initialization failed\n");
! 423: exit(-1);
! 424: }
1.1.1.13 root 425: Log_Printf(LOG_INFO, PROG_NAME ", compiled on: " __DATE__ ", " __TIME__ "\n");
426:
427: /* Init SDL's video subsystem. Note: Audio and joystick subsystems
428: will be initialized later (failures there are not fatal). */
429: if (SDL_Init(SDL_INIT_VIDEO) < 0)
430: {
431: fprintf(stderr, "Could not initialize the SDL library:\n %s\n", SDL_GetError() );
432: exit(-1);
433: }
434:
435: SDLGui_Init();
436: Printer_Init();
437: RS232_Init();
438: Midi_Init();
439: Screen_Init();
440: HostScreen_Init();
441: #if ENABLE_DSP_EMU
442: if (ConfigureParams.System.nDSPType == DSP_TYPE_EMU)
443: {
444: DSP_Init();
445: }
446: #endif
447: Floppy_Init();
448: Init680x0(); /* Init CPU emulation */
449: Audio_Init();
450: Keymap_Init();
451:
452: /* Init HD emulation */
453: if (ConfigureParams.HardDisk.bUseHardDiskImage)
454: {
455: char *szHardDiskImage = ConfigureParams.HardDisk.szHardDiskImage;
456: if (HDC_Init(szHardDiskImage))
457: printf("Hard drive image %s mounted.\n", szHardDiskImage);
458: else
459: printf("Couldn't open HD file: %s, or no partitions\n", szHardDiskImage);
460: }
461: GemDOS_Init();
462: if (ConfigureParams.HardDisk.bUseHardDiskDirectories)
463: {
464: GemDOS_InitDrives();
465: }
466:
467: if (Reset_Cold()) /* Reset all systems, load TOS image */
468: {
469: /* If loading of the TOS failed, we bring up the GUI to let the
470: * user choose another TOS ROM file. */
471: Dialog_DoProperty();
472: }
473: if (!bTosImageLoaded || bQuitProgram)
474: {
475: fprintf(stderr, "Failed to load TOS image!\n");
476: SDL_Quit();
477: exit(-2);
478: }
479:
480: IoMem_Init();
481: NvRam_Init();
482: Joy_Init();
483: Sound_Init();
1.1 root 484: }
485:
1.1.1.6 root 486:
1.1.1.2 root 487: /*-----------------------------------------------------------------------*/
1.1.1.13 root 488: /**
489: * Un-Initialise emulation
490: */
1.1.1.8 root 491: static void Main_UnInit(void)
1.1 root 492: {
1.1.1.13 root 493: Screen_ReturnFromFullScreen();
494: Floppy_UnInit();
495: HDC_UnInit();
496: Midi_UnInit();
497: RS232_UnInit();
498: Printer_UnInit();
499: IoMem_UnInit();
500: NvRam_UnInit();
501: GemDOS_UnInitDrives();
502: Joy_UnInit();
503: if (Sound_AreWeRecording())
504: Sound_EndRecording();
505: Audio_UnInit();
506: SDLGui_UnInit();
507: #if ENABLE_DSP_EMU
508: if (ConfigureParams.System.nDSPType == DSP_TYPE_EMU)
509: {
510: DSP_UnInit();
511: }
512: #endif
1.1.1.15! root 513: HostScreen_UnInit();
1.1.1.13 root 514: Screen_UnInit();
515: Exit680x0();
1.1 root 516:
1.1.1.13 root 517: /* SDL uninit: */
518: SDL_Quit();
1.1.1.10 root 519:
1.1.1.13 root 520: /* Close debug log file */
521: Log_UnInit();
1.1 root 522: }
523:
1.1.1.6 root 524:
1.1.1.2 root 525: /*-----------------------------------------------------------------------*/
1.1.1.13 root 526: /**
1.1.1.15! root 527: * Load initial configuration file(s)
! 528: */
! 529: static void Main_LoadInitialConfig(void)
! 530: {
! 531: char *psGlobalConfig;
! 532:
! 533: psGlobalConfig = malloc(FILENAME_MAX);
! 534: if (psGlobalConfig)
! 535: {
! 536: #if defined(__AMIGAOS4__)
! 537: strncpy(psGlobalConfig, CONFDIR"hatari.cfg", FILENAME_MAX);
! 538: #else
! 539: snprintf(psGlobalConfig, FILENAME_MAX, CONFDIR"%chatari.cfg", PATHSEP);
! 540: #endif
! 541: /* Try to load the global configuration file */
! 542: Configuration_Load(psGlobalConfig);
! 543:
! 544: free(psGlobalConfig);
! 545: }
! 546:
! 547: /* Now try the users configuration file */
! 548: Configuration_Load(NULL);
! 549: }
! 550:
! 551: /*-----------------------------------------------------------------------*/
! 552: /**
! 553: * Set TOS etc information and initial help message
! 554: */
! 555: static void Main_StatusbarSetup(void)
! 556: {
! 557: const char *name = NULL;
! 558: SDLKey key;
! 559:
! 560: key = ConfigureParams.Shortcut.withoutModifier[SHORTCUT_OPTIONS];
! 561: if (!key)
! 562: key = ConfigureParams.Shortcut.withModifier[SHORTCUT_OPTIONS];
! 563: if (key)
! 564: name = SDL_GetKeyName(key);
! 565: if (name)
! 566: {
! 567: char message[24], *keyname;
! 568: keyname = Str_ToUpper(strdup(name));
! 569: snprintf(message, sizeof(message), "Press %s for Options", keyname);
! 570: free(keyname);
! 571:
! 572: Statusbar_AddMessage(message, 6000);
! 573: }
! 574: /* update information loaded by Main_Init() */
! 575: Statusbar_UpdateInfo();
! 576: }
! 577:
! 578: /*-----------------------------------------------------------------------*/
! 579: /**
1.1.1.13 root 580: * Main
1.1.1.15! root 581: *
! 582: * Note: 'argv' cannot be declared const, MinGW would then fail to link.
1.1.1.13 root 583: */
1.1 root 584: int main(int argc, char *argv[])
585: {
1.1.1.13 root 586: /* Generate random seed */
587: srand(time(NULL));
588:
589: /* Initialize directory strings */
590: Paths_Init(argv[0]);
591:
592: /* Set default configuration values: */
593: Configuration_SetDefault();
1.1.1.8 root 594:
1.1.1.13 root 595: /* Now load the values from the configuration file */
1.1.1.15! root 596: Main_LoadInitialConfig();
1.1 root 597:
1.1.1.15! root 598: /* Check for any passed parameters */
! 599: if (!Opt_ParseParameters(argc, (const char**)argv))
! 600: {
! 601: return 1;
! 602: }
1.1.1.13 root 603: /* monitor type option might require "reset" -> TRUE */
604: Configuration_Apply(TRUE);
1.1.1.2 root 605:
1.1.1.13 root 606: #ifdef WIN32
607: Win_OpenCon();
608: #endif
1.1.1.7 root 609:
1.1.1.13 root 610: /* Needed on maemo but useful also with normal X11 window managers
611: * for window grouping when you have multiple Hatari SDL windows open
612: */
613: #if HAVE_SETENV
614: setenv("SDL_VIDEO_X11_WMCLASS", "hatari", 1);
615: #endif
1.1 root 616:
1.1.1.13 root 617: /* Init emulator system */
618: Main_Init();
1.1 root 619:
1.1.1.15! root 620: /* Set initial Statusbar information */
! 621: Main_StatusbarSetup();
! 622:
1.1.1.13 root 623: /* Check if SDL_Delay is accurate */
624: Main_CheckForAccurateDelays();
1.1.1.12 root 625:
1.1.1.13 root 626: /* Run emulation */
627: Main_UnPauseEmulation();
628: M68000_Start(); /* Start emulation */
1.1 root 629:
1.1.1.13 root 630: /* Un-init emulation system */
631: Main_UnInit();
1.1 root 632:
1.1.1.13 root 633: return 0;
1.1 root 634: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.