|
|
1.1 ! root 1: /* ! 2: Hatari - screen.c ! 3: ! 4: This file is distributed under the GNU Public License, version 2 or at your ! 5: option any later version. Read the file gpl.txt for details. ! 6: ! 7: */ ! 8: ! 9: const char Screen_fileid[] = "Hatari screen.c : " __DATE__ " " __TIME__; ! 10: ! 11: #include <SDL.h> ! 12: #include <SDL_endian.h> ! 13: ! 14: #include "main.h" ! 15: #include "configuration.h" ! 16: #include "log.h" ! 17: #include "m68000.h" ! 18: #include "paths.h" ! 19: #include "screen.h" ! 20: #include "control.h" ! 21: #include "convert/routines.h" ! 22: #include "resolution.h" ! 23: #include "statusbar.h" ! 24: #include "video.h" ! 25: ! 26: ! 27: /* extern for several purposes */ ! 28: SDL_Surface *sdlscrn = NULL; /* The SDL screen surface */ ! 29: int nScreenZoomX, nScreenZoomY; /* Zooming factors, used for scaling mouse motions */ ! 30: int nBorderPixelsLeft, nBorderPixelsRight; /* Pixels in left and right border */ ! 31: int nBorderPixelsTop, nBorderPixelsBottom; /* Lines in top and bottom border */ ! 32: ! 33: /* extern for shortcuts and falcon/hostscreen.c */ ! 34: bool bGrabMouse = false; /* Grab the mouse cursor in the window */ ! 35: bool bInFullScreen = false; /* true if in full screen */ ! 36: ! 37: ! 38: /* extern for video.c */ ! 39: Uint8 pNEXTScreen[(1120*832)*2]; ! 40: FRAMEBUFFER *pFrameBuffer; /* Pointer into current 'FrameBuffer' */ ! 41: ! 42: static FRAMEBUFFER FrameBuffers[NUM_FRAMEBUFFERS]; /* Store frame buffer details to tell how to update */ ! 43: static Uint8 *pNEXTScreenCopy; /* Keep track of current and previous ST screen data */ ! 44: static Uint8 *pPCScreenDest; /* Destination PC buffer */ ! 45: static int NEXTScreenEndHorizLine; /* End lines to be converted */ ! 46: static int PCScreenBytesPerLine; ! 47: static int NEXTScreenWidthBytes; ! 48: static SDL_Rect NEXTScreenRect; /* screen size without statusbar */ ! 49: ! 50: static int NEXTScreenLineOffset[910]; /* Offsets for ST screen lines eg, 0,160,320... */ ! 51: ! 52: static void (*ScreenDrawFunctionsNormal[3])(void); /* Screen draw functions */ ! 53: ! 54: static bool bScreenContentsChanged; /* true if buffer changed and requires blitting */ ! 55: static bool bScrDoubleY; /* true if double on Y */ ! 56: static int ScrUpdateFlag; /* Bit mask of how to update screen */ ! 57: ! 58: ! 59: static bool Screen_DrawFrame(bool bForceFlip); ! 60: ! 61: ! 62: /*-----------------------------------------------------------------------*/ ! 63: /** ! 64: * Create ST 0x777 / STe 0xfff color format to 16 or 32 bits per pixel ! 65: * conversion table. Called each time when changed resolution or to/from ! 66: * fullscreen mode. ! 67: */ ! 68: static void Screen_SetupRGBTable(void) ! 69: { ! 70: } ! 71: ! 72: SDL_Color sdlColors[16]; ! 73: Uint32 colors[32]; ! 74: ! 75: /*-----------------------------------------------------------------------*/ ! 76: /** ! 77: * Create new palette for display. ! 78: */ ! 79: static void Screen_CreatePalette(void) ! 80: { ! 81: ! 82: sdlColors[0].r = sdlColors[0].g = sdlColors[0].b = 255; ! 83: sdlColors[1].r = sdlColors[1].g = sdlColors[1].b = 160; ! 84: sdlColors[2].r = sdlColors[2].g = sdlColors[2].b = 80; ! 85: sdlColors[3].r = sdlColors[3].g = sdlColors[3].b = 0; ! 86: } ! 87: ! 88: ! 89: /*-----------------------------------------------------------------------*/ ! 90: /** ! 91: * Create 8-Bit palette for display if needed. ! 92: */ ! 93: static void Screen_Handle8BitPalettes(void) ! 94: { ! 95: } ! 96: ! 97: ! 98: /*-----------------------------------------------------------------------*/ ! 99: /** ! 100: * Set screen draw functions. ! 101: */ ! 102: static void Screen_SetDrawFunctions(int nBitCount, bool bDoubleLowRes) ! 103: { ! 104: ScreenDrawFunctionsNormal[ST_HIGH_RES] = ConvertHighRes_640x8Bit; ! 105: } ! 106: ! 107: ! 108: /*-----------------------------------------------------------------------*/ ! 109: /** ! 110: * Set amount of border pixels ! 111: */ ! 112: static void Screen_SetBorderPixels(int leftX, int leftY) ! 113: { ! 114: } ! 115: ! 116: /*-----------------------------------------------------------------------*/ ! 117: /** ! 118: * store Y offset for each horizontal line in our source ST screen for ! 119: * reference in the convert functions. ! 120: */ ! 121: static void Screen_SetSTScreenOffsets(void) ! 122: { ! 123: } ! 124: ! 125: ! 126: /*-----------------------------------------------------------------------*/ ! 127: /** ! 128: * Initialize SDL screen surface / set resolution. ! 129: */ ! 130: static void Screen_SetResolution(void) ! 131: { ! 132: int Width, Height, nZoom, SBarHeight, BitCount, maxW, maxH; ! 133: Uint32 sdlVideoFlags; ! 134: bool bDoubleLowRes = false; ! 135: ! 136: BitCount = 8; ! 137: ! 138: nBorderPixelsTop = nBorderPixelsBottom = 0; ! 139: nBorderPixelsLeft = nBorderPixelsRight = 0; ! 140: ! 141: nScreenZoomX = 1; ! 142: nScreenZoomY = 1; ! 143: ! 144: Width = 1120; ! 145: Height = 832; ! 146: nZoom = 1; ! 147: ! 148: /* Statusbar height for doubled screen size */ ! 149: SBarHeight = Statusbar_GetHeightForSize(1120, 832); ! 150: Resolution_GetLimits(&maxW, &maxH, &BitCount); ! 151: ! 152: ! 153: Screen_SetSTScreenOffsets(); ! 154: Height += Statusbar_SetHeight(Width, Height); ! 155: ! 156: /* SDL Video attributes: */ ! 157: if (bInFullScreen) ! 158: { ! 159: sdlVideoFlags = SDL_HWSURFACE|SDL_FULLSCREEN|SDL_HWPALETTE/*|SDL_DOUBLEBUF*/; ! 160: /* SDL_DOUBLEBUF helps avoiding tearing and can be faster on suitable HW, ! 161: * but it doesn't work with partial screen updates done by the ST screen ! 162: * update code or the Hatari GUI, so double buffering is disabled. ! 163: */ ! 164: } ! 165: else ! 166: { ! 167: sdlVideoFlags = SDL_SWSURFACE|SDL_HWPALETTE; ! 168: } ! 169: ! 170: /* Check if we really have to change the video mode: */ ! 171: if (!sdlscrn || sdlscrn->w != Width || sdlscrn->h != Height ! 172: || (BitCount && sdlscrn->format->BitsPerPixel != BitCount) ! 173: || (sdlscrn->flags&SDL_FULLSCREEN) != (sdlVideoFlags&SDL_FULLSCREEN)) ! 174: { ! 175: #ifdef _MUDFLAP ! 176: if (sdlscrn) { ! 177: __mf_unregister(sdlscrn->pixels, sdlscrn->pitch*sdlscrn->h, __MF_TYPE_GUESS); ! 178: } ! 179: #endif ! 180: if (bInFullScreen) ! 181: { ! 182: /* unhide the Hatari WM window for fullscreen */ ! 183: Control_ReparentWindow(Width, Height, bInFullScreen); ! 184: } ! 185: ! 186: /* Set new video mode */ ! 187: //fprintf(stderr,"Requesting video mode %i %i %i\n", Width, Height, BitCount); ! 188: sdlscrn = SDL_SetVideoMode(Width, Height, BitCount, sdlVideoFlags); ! 189: //fprintf(stderr,"Got video mode %i %i %i\n", sdlscrn->w, sdlscrn->h, sdlscrn->format->BitsPerPixel); ! 190: ! 191: /* By default ConfigureParams.Screen.nForceBpp and therefore ! 192: * BitCount is zero which means "SDL color depth autodetection". ! 193: * In this case the SDL_SetVideoMode() call might return ! 194: * a 24 bpp resolution ! 195: */ ! 196: if (sdlscrn && sdlscrn->format->BitsPerPixel == 24) ! 197: { ! 198: fprintf(stderr, "Unsupported color depth 24, trying 32 bpp instead...\n"); ! 199: sdlscrn = SDL_SetVideoMode(Width, Height, 32, sdlVideoFlags); ! 200: } ! 201: ! 202: /* Exit if we can not open a screen */ ! 203: if (!sdlscrn) ! 204: { ! 205: fprintf(stderr, "Could not set video mode:\n %s\n", SDL_GetError() ); ! 206: SDL_Quit(); ! 207: exit(-2); ! 208: } ! 209: #ifdef _MUDFLAP ! 210: __mf_register(sdlscrn->pixels, sdlscrn->pitch*sdlscrn->h, __MF_TYPE_GUESS, "SDL pixels"); ! 211: #endif ! 212: ! 213: if (!bInFullScreen) ! 214: { ! 215: /* re-embed the new Hatari SDL window */ ! 216: Control_ReparentWindow(Width, Height, bInFullScreen); ! 217: } ! 218: ! 219: /* Re-init screen palette: */ ! 220: if (sdlscrn->format->BitsPerPixel == 8) ! 221: Screen_Handle8BitPalettes(); /* Initialize new 8 bit palette */ ! 222: else ! 223: Screen_SetupRGBTable(); /* Create color convertion table */ ! 224: ! 225: Statusbar_Init(sdlscrn); ! 226: ! 227: /* screen area without the statusbar */ ! 228: NEXTScreenRect.x = 0; ! 229: NEXTScreenRect.y = 0; ! 230: NEXTScreenRect.w = sdlscrn->w; ! 231: NEXTScreenRect.h = sdlscrn->h - Statusbar_GetHeight(); ! 232: } ! 233: ! 234: /* Set drawing functions */ ! 235: Screen_SetDrawFunctions(sdlscrn->format->BitsPerPixel, bDoubleLowRes); ! 236: ! 237: Screen_SetFullUpdate(); /* Cause full update of screen */ ! 238: } ! 239: ! 240: ! 241: /*-----------------------------------------------------------------------*/ ! 242: /** ! 243: * Init Screen bitmap and buffers/tables needed for ST to PC screen conversion ! 244: */ ! 245: void Screen_Init(void) ! 246: { ! 247: int i; ! 248: SDL_Surface *pIconSurf; ! 249: char sIconFileName[FILENAME_MAX]; ! 250: ! 251: /* Clear frame buffer structures and set current pointer */ ! 252: memset(FrameBuffers, 0, NUM_FRAMEBUFFERS * sizeof(FRAMEBUFFER)); ! 253: ! 254: /* Allocate previous screen check workspace. We are going to double-buffer a double-buffered screen. Oh. */ ! 255: for (i = 0; i < NUM_FRAMEBUFFERS; i++) ! 256: { ! 257: FrameBuffers[i].pNEXTScreen = malloc(((1024)/8)*768); ! 258: FrameBuffers[i].pNEXTScreenCopy = malloc(((1024)/8)*768); ! 259: if (!FrameBuffers[i].pNEXTScreen || !FrameBuffers[i].pNEXTScreenCopy) ! 260: { ! 261: fprintf(stderr, "Failed to allocate frame buffer memory.\n"); ! 262: exit(-1); ! 263: } ! 264: } ! 265: pFrameBuffer = &FrameBuffers[0]; ! 266: ! 267: /* Load and set icon */ ! 268: snprintf(sIconFileName, sizeof(sIconFileName), "%s%cprevious-icon.bmp", ! 269: Paths_GetDataDir(), PATHSEP); ! 270: pIconSurf = SDL_LoadBMP(sIconFileName); ! 271: if (pIconSurf) ! 272: { ! 273: SDL_SetColorKey(pIconSurf, SDL_SRCCOLORKEY, SDL_MapRGB(pIconSurf->format, 255, 255, 255)); ! 274: SDL_WM_SetIcon(pIconSurf, NULL); ! 275: SDL_FreeSurface(pIconSurf); ! 276: } ! 277: ! 278: /* Set initial window resolution */ ! 279: bInFullScreen = ConfigureParams.Screen.bFullScreen; ! 280: Screen_SetResolution(); ! 281: ! 282: if (bGrabMouse) ! 283: SDL_WM_GrabInput(SDL_GRAB_ON); ! 284: ! 285: Video_SetScreenRasters(); /* Set rasters ready for first screen */ ! 286: ! 287: Screen_CreatePalette(); ! 288: /* Configure some SDL stuff: */ ! 289: SDL_WM_SetCaption(PROG_NAME, "Previous"); ! 290: SDL_ShowCursor(SDL_DISABLE); ! 291: } ! 292: ! 293: ! 294: /*-----------------------------------------------------------------------*/ ! 295: /** ! 296: * Free screen bitmap and allocated resources ! 297: */ ! 298: void Screen_UnInit(void) ! 299: { ! 300: int i; ! 301: ! 302: /* Free memory used for copies */ ! 303: for (i = 0; i < NUM_FRAMEBUFFERS; i++) ! 304: { ! 305: free(FrameBuffers[i].pNEXTScreen); ! 306: free(FrameBuffers[i].pNEXTScreenCopy); ! 307: } ! 308: } ! 309: ! 310: ! 311: /*-----------------------------------------------------------------------*/ ! 312: /** ! 313: * Reset screen ! 314: */ ! 315: void Screen_Reset(void) ! 316: { ! 317: /* Cause full update */ ! 318: Screen_ModeChanged(); ! 319: } ! 320: ! 321: ! 322: /*-----------------------------------------------------------------------*/ ! 323: /** ! 324: * Set flags so screen will be TOTALLY re-drawn (clears whole of full-screen) ! 325: * next time around ! 326: */ ! 327: void Screen_SetFullUpdate(void) ! 328: { ! 329: int i; ! 330: ! 331: /* Update frame buffers */ ! 332: for (i = 0; i < NUM_FRAMEBUFFERS; i++) ! 333: FrameBuffers[i].bFullUpdate = true; ! 334: } ! 335: ! 336: ! 337: /*-----------------------------------------------------------------------*/ ! 338: /** ! 339: * Clear Window display memory ! 340: */ ! 341: static void Screen_ClearScreen(void) ! 342: { ! 343: SDL_FillRect(sdlscrn, &NEXTScreenRect, SDL_MapRGB(sdlscrn->format, 0, 0, 0)); ! 344: } ! 345: ! 346: ! 347: /*-----------------------------------------------------------------------*/ ! 348: /** ! 349: * Return true if (falcon/tt) hostscreen functions need to be used ! 350: * instead of the (st/ste) functions here. ! 351: */ ! 352: static bool Screen_UseHostScreen(void) ! 353: { ! 354: return false; ! 355: } ! 356: ! 357: /*-----------------------------------------------------------------------*/ ! 358: /** ! 359: * Force screen redraw. Does the right thing regardless of whether ! 360: * we're in ST/STe, Falcon or TT mode. Needed when switching modes ! 361: * while emulation is paused. ! 362: */ ! 363: static void Screen_Refresh(void) ! 364: { ! 365: Screen_DrawFrame(true); ! 366: } ! 367: ! 368: ! 369: /*-----------------------------------------------------------------------*/ ! 370: /** ! 371: * Enter Full screen mode ! 372: */ ! 373: void Screen_EnterFullScreen(void) ! 374: { ! 375: bool bWasRunning; ! 376: ! 377: if (!bInFullScreen) ! 378: { ! 379: /* Hold things... */ ! 380: bWasRunning = Main_PauseEmulation(false); ! 381: bInFullScreen = true; ! 382: ! 383: if (Screen_UseHostScreen()) ! 384: { ! 385: // HostScreen_toggleFullScreen(); ! 386: } ! 387: else ! 388: { ! 389: Screen_SetResolution(); ! 390: Screen_ClearScreen(); /* Black out screen bitmap as will be invalid when return */ ! 391: } ! 392: ! 393: SDL_Delay(20); /* To give monitor time to change to new resolution */ ! 394: ! 395: if (bWasRunning) ! 396: { ! 397: /* And off we go... */ ! 398: Main_UnPauseEmulation(); ! 399: } ! 400: else ! 401: { ! 402: Screen_Refresh(); ! 403: } ! 404: SDL_WM_GrabInput(SDL_GRAB_ON); /* Grab mouse pointer in fullscreen */ ! 405: } ! 406: } ! 407: ! 408: ! 409: /*-----------------------------------------------------------------------*/ ! 410: /** ! 411: * Return from Full screen mode back to a window ! 412: */ ! 413: void Screen_ReturnFromFullScreen(void) ! 414: { ! 415: bool bWasRunning; ! 416: ! 417: if (bInFullScreen) ! 418: { ! 419: /* Hold things... */ ! 420: bWasRunning = Main_PauseEmulation(false); ! 421: bInFullScreen = false; ! 422: ! 423: if (Screen_UseHostScreen()) ! 424: { ! 425: // HostScreen_toggleFullScreen(); ! 426: } ! 427: else ! 428: { ! 429: Screen_SetResolution(); ! 430: } ! 431: SDL_Delay(20); /* To give monitor time to switch resolution */ ! 432: ! 433: if (bWasRunning) ! 434: { ! 435: /* And off we go... */ ! 436: Main_UnPauseEmulation(); ! 437: } ! 438: else ! 439: { ! 440: Screen_Refresh(); ! 441: } ! 442: ! 443: if (!bGrabMouse) ! 444: { ! 445: /* Un-grab mouse pointer in windowed mode */ ! 446: SDL_WM_GrabInput(SDL_GRAB_OFF); ! 447: } ! 448: } ! 449: } ! 450: ! 451: ! 452: /*-----------------------------------------------------------------------*/ ! 453: /** ! 454: * Have we changed between low/med/high res? ! 455: */ ! 456: static void Screen_DidResolutionChange(int new_res) ! 457: { ! 458: } ! 459: ! 460: ! 461: /*-----------------------------------------------------------------------*/ ! 462: /** ! 463: * Force things associated with changing between low/medium/high res. ! 464: */ ! 465: void Screen_ModeChanged(void) ! 466: { ! 467: if (!sdlscrn) ! 468: { ! 469: /* screen not yet initialized */ ! 470: return; ! 471: } ! 472: /* Set new display mode, if differs from current */ ! 473: Screen_SetResolution(); ! 474: Screen_SetFullUpdate(); ! 475: if (bInFullScreen || bGrabMouse) ! 476: SDL_WM_GrabInput(SDL_GRAB_ON); ! 477: else ! 478: SDL_WM_GrabInput(SDL_GRAB_OFF); ! 479: } ! 480: ! 481: ! 482: /*-----------------------------------------------------------------------*/ ! 483: /** ! 484: * Compare current resolution on line with previous, and set 'UpdateLine' accordingly ! 485: * Return if swap between low/medium resolution ! 486: */ ! 487: static bool Screen_CompareResolution(int y, int *pUpdateLine, int oldres) ! 488: { ! 489: return false; ! 490: } ! 491: ! 492: ! 493: /*-----------------------------------------------------------------------*/ ! 494: /** ! 495: * Check to see if palette changes cause screen update and keep 'HBLPalette[]' up-to-date ! 496: */ ! 497: static void Screen_ComparePalette(int y, int *pUpdateLine) ! 498: { ! 499: } ! 500: ! 501: ! 502: /*-----------------------------------------------------------------------*/ ! 503: /** ! 504: * Check for differences in Palette and Resolution from Mask table and update ! 505: * and store off which lines need updating and create full-screen palette. ! 506: * (It is very important for these routines to check for colour changes with ! 507: * the previous screen so only the very minimum parts are updated). ! 508: * Return new STRes value. ! 509: */ ! 510: static int Screen_ComparePaletteMask(int res) ! 511: { ! 512: return 0; ! 513: } ! 514: ! 515: ! 516: /*-----------------------------------------------------------------------*/ ! 517: /** ! 518: * Update Palette Mask to show 'full-update' required. This is usually done after a resolution change ! 519: * or when going between a Window and full-screen display ! 520: */ ! 521: static void Screen_SetFullUpdateMask(void) ! 522: { ! 523: } ! 524: ! 525: ! 526: /*-----------------------------------------------------------------------*/ ! 527: /** ! 528: * Set details for ST screen conversion. ! 529: */ ! 530: static void Screen_SetConvertDetails(void) ! 531: { ! 532: } ! 533: ! 534: ! 535: /*-----------------------------------------------------------------------*/ ! 536: /** ! 537: * Lock full-screen for drawing ! 538: */ ! 539: static bool Screen_Lock(void) ! 540: { ! 541: if (SDL_MUSTLOCK(sdlscrn)) ! 542: { ! 543: if (SDL_LockSurface(sdlscrn)) ! 544: { ! 545: Screen_ReturnFromFullScreen(); /* All OK? If not need to jump back to a window */ ! 546: return false; ! 547: } ! 548: } ! 549: ! 550: return true; ! 551: } ! 552: ! 553: /*-----------------------------------------------------------------------*/ ! 554: /** ! 555: * UnLock full-screen ! 556: */ ! 557: static void Screen_UnLock(void) ! 558: { ! 559: if ( SDL_MUSTLOCK(sdlscrn) ) ! 560: SDL_UnlockSurface(sdlscrn); ! 561: } ! 562: ! 563: ! 564: /*-----------------------------------------------------------------------*/ ! 565: /** ! 566: * Blit our converted ST screen to window/full-screen ! 567: */ ! 568: static void Screen_Blit(void) ! 569: { ! 570: unsigned char *pTmpScreen; ! 571: ! 572: { ! 573: SDL_UpdateRects(sdlscrn, 1, &NEXTScreenRect); ! 574: } ! 575: ! 576: /* Swap copy/raster buffers in screen. */ ! 577: pTmpScreen = pFrameBuffer->pNEXTScreenCopy; ! 578: pFrameBuffer->pNEXTScreenCopy = pFrameBuffer->pNEXTScreen; ! 579: pFrameBuffer->pNEXTScreen = pTmpScreen; ! 580: } ! 581: ! 582: ! 583: /*-----------------------------------------------------------------------*/ ! 584: /** ! 585: */ ! 586: static bool Screen_DrawFrame(bool bForceFlip) ! 587: { ! 588: int new_res; ! 589: void (*pDrawFunction)(void); ! 590: ! 591: /* Lock screen ready for drawing */ ! 592: if (Screen_Lock()) ! 593: { ! 594: ! 595: pDrawFunction = ScreenDrawFunctionsNormal[ST_HIGH_RES]; ! 596: ! 597: if (pDrawFunction) ! 598: CALL_VAR(pDrawFunction); ! 599: ! 600: /* Unlock screen */ ! 601: Screen_UnLock(); ! 602: ! 603: /* draw statusbar or overlay led(s) after unlock */ ! 604: Statusbar_OverlayBackup(sdlscrn); ! 605: Statusbar_Update(sdlscrn); ! 606: ! 607: Screen_Blit(); ! 608: ! 609: return bScreenContentsChanged; ! 610: } ! 611: ! 612: return false; ! 613: } ! 614: ! 615: ! 616: /*-----------------------------------------------------------------------*/ ! 617: /** ! 618: * Draw ST screen to window/full-screen ! 619: */ ! 620: bool Screen_Draw(void) ! 621: { ! 622: if (!bQuitProgram) ! 623: { ! 624: /* And draw (if screen contents changed) */ ! 625: Screen_DrawFrame(false); ! 626: return true; ! 627: } ! 628: ! 629: return false; ! 630: } ! 631: ! 632: ! 633: /* -------------- screen conversion routines -------------------------------- ! 634: */ ! 635: ! 636: ! 637: /*-----------------------------------------------------------------------*/ ! 638: /** ! 639: */ ! 640: static int AdjustLinePaletteRemap(int y) ! 641: { ! 642: return true; ! 643: } ! 644: ! 645: ! 646: /*-----------------------------------------------------------------------*/ ! 647: /** ! 648: */ ! 649: static void Convert_StartFrame(void) ! 650: { ! 651: } ! 652: ! 653: /* lookup tables and conversion macros */ ! 654: #include "convert/macros.h" ! 655: ! 656: /* Conversion routines */ ! 657: ! 658: #include "convert/high640x8.c" /* HighRes To 640xH x 8-bit color */ ! 659:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.