|
|
1.1.1.3 ! root 1: /* ! 2: Copyright (C) 1997-2001 Id Software, Inc. ! 3: ! 4: This program is free software; you can redistribute it and/or ! 5: modify it under the terms of the GNU General Public License ! 6: as published by the Free Software Foundation; either version 2 ! 7: of the License, or (at your option) any later version. ! 8: ! 9: This program is distributed in the hope that it will be useful, ! 10: but WITHOUT ANY WARRANTY; without even the implied warranty of ! 11: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ! 12: ! 13: See the GNU General Public License for more details. ! 14: ! 15: You should have received a copy of the GNU General Public License ! 16: along with this program; if not, write to the Free Software ! 17: Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ! 18: ! 19: */ 1.1 root 20: // Main windowed and fullscreen graphics interface module. This module 21: // is used for both the software and OpenGL rendering versions of the 22: // Quake refresh engine. 23: #include <assert.h> 24: #include <float.h> 25: 26: #include "..\client\client.h" 27: #include "winquake.h" 28: //#include "zmouse.h" 29: 30: // Structure containing functions exported from refresh DLL 31: refexport_t re; 32: 33: cvar_t *win_noalttab; 34: 35: #ifndef WM_MOUSEWHEEL 36: #define WM_MOUSEWHEEL (WM_MOUSELAST+1) // message that will be supported by the OS 37: #endif 38: 39: static UINT MSH_MOUSEWHEEL; 40: 41: // Console variables that we need to access from this module 42: cvar_t *vid_gamma; 43: cvar_t *vid_ref; // Name of Refresh DLL loaded 44: cvar_t *vid_xpos; // X coordinate of window position 45: cvar_t *vid_ypos; // Y coordinate of window position 46: cvar_t *vid_fullscreen; 47: 48: // Global variables used internally by this module 49: viddef_t viddef; // global video state; used by other modules 50: HINSTANCE reflib_library; // Handle to refresh DLL 51: qboolean reflib_active = 0; 52: 53: HWND cl_hwnd; // Main window handle for life of program 54: 55: #define VID_NUM_MODES ( sizeof( vid_modes ) / sizeof( vid_modes[0] ) ) 56: 57: LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); 58: 59: static qboolean s_alttab_disabled; 60: 61: extern unsigned sys_msg_time; 62: 63: /* 64: ** WIN32 helper functions 65: */ 66: extern qboolean s_win95; 67: 68: static void WIN_DisableAltTab( void ) 69: { 70: if ( s_alttab_disabled ) 71: return; 72: 73: if ( s_win95 ) 74: { 75: BOOL old; 76: 77: SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, &old, 0 ); 78: } 79: else 80: { 81: RegisterHotKey( 0, 0, MOD_ALT, VK_TAB ); 82: RegisterHotKey( 0, 1, MOD_ALT, VK_RETURN ); 83: } 84: s_alttab_disabled = true; 85: } 86: 87: static void WIN_EnableAltTab( void ) 88: { 89: if ( s_alttab_disabled ) 90: { 91: if ( s_win95 ) 92: { 93: BOOL old; 94: 95: SystemParametersInfo( SPI_SCREENSAVERRUNNING, 0, &old, 0 ); 96: } 97: else 98: { 99: UnregisterHotKey( 0, 0 ); 100: UnregisterHotKey( 0, 1 ); 101: } 102: 103: s_alttab_disabled = false; 104: } 105: } 106: 107: /* 108: ========================================================================== 109: 110: DLL GLUE 111: 112: ========================================================================== 113: */ 114: 115: #define MAXPRINTMSG 4096 116: void VID_Printf (int print_level, char *fmt, ...) 117: { 118: va_list argptr; 119: char msg[MAXPRINTMSG]; 120: static qboolean inupdate; 121: 122: va_start (argptr,fmt); 123: vsprintf (msg,fmt,argptr); 124: va_end (argptr); 125: 126: if (print_level == PRINT_ALL) 127: { 128: Com_Printf ("%s", msg); 129: } 130: else if ( print_level == PRINT_DEVELOPER ) 131: { 132: Com_DPrintf ("%s", msg); 133: } 134: else if ( print_level == PRINT_ALERT ) 135: { 136: MessageBox( 0, msg, "PRINT_ALERT", MB_ICONWARNING ); 137: OutputDebugString( msg ); 138: } 139: } 140: 141: void VID_Error (int err_level, char *fmt, ...) 142: { 143: va_list argptr; 144: char msg[MAXPRINTMSG]; 145: static qboolean inupdate; 146: 147: va_start (argptr,fmt); 148: vsprintf (msg,fmt,argptr); 149: va_end (argptr); 150: 151: Com_Error (err_level,"%s", msg); 152: } 153: 154: //========================================================================== 155: 156: byte scantokey[128] = 157: { 158: // 0 1 2 3 4 5 6 7 159: // 8 9 A B C D E F 160: 0 , 27, '1', '2', '3', '4', '5', '6', 161: '7', '8', '9', '0', '-', '=', K_BACKSPACE, 9, // 0 162: 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 163: 'o', 'p', '[', ']', 13 , K_CTRL,'a', 's', // 1 164: 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', 165: '\'' , '`', K_SHIFT,'\\', 'z', 'x', 'c', 'v', // 2 166: 'b', 'n', 'm', ',', '.', '/', K_SHIFT,'*', 167: K_ALT,' ', 0 , K_F1, K_F2, K_F3, K_F4, K_F5, // 3 168: K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, 0 , K_HOME, 169: K_UPARROW,K_PGUP,K_KP_MINUS,K_LEFTARROW,K_KP_5,K_RIGHTARROW, K_KP_PLUS,K_END, //4 170: K_DOWNARROW,K_PGDN,K_INS,K_DEL,0,0, 0, K_F11, 171: K_F12,0 , 0 , 0 , 0 , 0 , 0 , 0, // 5 172: 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0, 173: 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0, // 6 174: 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0, 175: 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 // 7 176: }; 177: 178: /* 179: ======= 180: MapKey 181: 182: Map from windows to quake keynums 183: ======= 184: */ 185: int MapKey (int key) 186: { 187: int result; 188: int modified = ( key >> 16 ) & 255; 189: qboolean is_extended = false; 190: 191: if ( modified > 127) 192: return 0; 193: 194: if ( key & ( 1 << 24 ) ) 195: is_extended = true; 196: 197: result = scantokey[modified]; 198: 199: if ( !is_extended ) 200: { 201: switch ( result ) 202: { 203: case K_HOME: 204: return K_KP_HOME; 205: case K_UPARROW: 206: return K_KP_UPARROW; 207: case K_PGUP: 208: return K_KP_PGUP; 209: case K_LEFTARROW: 210: return K_KP_LEFTARROW; 211: case K_RIGHTARROW: 212: return K_KP_RIGHTARROW; 213: case K_END: 214: return K_KP_END; 215: case K_DOWNARROW: 216: return K_KP_DOWNARROW; 217: case K_PGDN: 218: return K_KP_PGDN; 219: case K_INS: 220: return K_KP_INS; 221: case K_DEL: 222: return K_KP_DEL; 223: default: 224: return result; 225: } 226: } 227: else 228: { 229: switch ( result ) 230: { 231: case 0x0D: 232: return K_KP_ENTER; 233: case 0x2F: 234: return K_KP_SLASH; 235: case 0xAF: 236: return K_KP_PLUS; 237: } 238: return result; 239: } 240: } 241: 242: void AppActivate(BOOL fActive, BOOL minimize) 243: { 244: Minimized = minimize; 245: 246: Key_ClearStates(); 247: 248: // we don't want to act like we're active if we're minimized 249: if (fActive && !Minimized) 250: ActiveApp = true; 251: else 252: ActiveApp = false; 253: 254: // minimize/restore mouse-capture on demand 255: if (!ActiveApp) 256: { 257: IN_Activate (false); 258: CDAudio_Activate (false); 259: S_Activate (false); 260: 261: if ( win_noalttab->value ) 262: { 263: WIN_EnableAltTab(); 264: } 265: } 266: else 267: { 268: IN_Activate (true); 269: CDAudio_Activate (true); 270: S_Activate (true); 271: if ( win_noalttab->value ) 272: { 273: WIN_DisableAltTab(); 274: } 275: } 276: } 277: 278: /* 279: ==================== 280: MainWndProc 281: 282: main window procedure 283: ==================== 284: */ 285: LONG WINAPI MainWndProc ( 286: HWND hWnd, 287: UINT uMsg, 288: WPARAM wParam, 289: LPARAM lParam) 290: { 291: LONG lRet = 0; 292: 293: if ( uMsg == MSH_MOUSEWHEEL ) 294: { 295: if ( ( ( int ) wParam ) > 0 ) 296: { 297: Key_Event( K_MWHEELUP, true, sys_msg_time ); 298: Key_Event( K_MWHEELUP, false, sys_msg_time ); 299: } 300: else 301: { 302: Key_Event( K_MWHEELDOWN, true, sys_msg_time ); 303: Key_Event( K_MWHEELDOWN, false, sys_msg_time ); 304: } 305: return DefWindowProc (hWnd, uMsg, wParam, lParam); 306: } 307: 308: switch (uMsg) 309: { 310: case WM_MOUSEWHEEL: 311: /* 312: ** this chunk of code theoretically only works under NT4 and Win98 313: ** since this message doesn't exist under Win95 314: */ 315: if ( ( short ) HIWORD( wParam ) > 0 ) 316: { 317: Key_Event( K_MWHEELUP, true, sys_msg_time ); 318: Key_Event( K_MWHEELUP, false, sys_msg_time ); 319: } 320: else 321: { 322: Key_Event( K_MWHEELDOWN, true, sys_msg_time ); 323: Key_Event( K_MWHEELDOWN, false, sys_msg_time ); 324: } 325: break; 326: 327: case WM_HOTKEY: 328: return 0; 329: 330: case WM_CREATE: 331: cl_hwnd = hWnd; 332: 333: MSH_MOUSEWHEEL = RegisterWindowMessage("MSWHEEL_ROLLMSG"); 334: return DefWindowProc (hWnd, uMsg, wParam, lParam); 335: 336: case WM_PAINT: 337: SCR_DirtyScreen (); // force entire screen to update next frame 338: return DefWindowProc (hWnd, uMsg, wParam, lParam); 339: 340: case WM_DESTROY: 341: // let sound and input know about this? 342: cl_hwnd = NULL; 343: return DefWindowProc (hWnd, uMsg, wParam, lParam); 344: 345: case WM_ACTIVATE: 346: { 347: int fActive, fMinimized; 348: 349: // KJB: Watch this for problems in fullscreen modes with Alt-tabbing. 350: fActive = LOWORD(wParam); 351: fMinimized = (BOOL) HIWORD(wParam); 352: 353: AppActivate( fActive != WA_INACTIVE, fMinimized); 354: 355: if ( reflib_active ) 356: re.AppActivate( !( fActive == WA_INACTIVE ) ); 357: } 358: return DefWindowProc (hWnd, uMsg, wParam, lParam); 359: 360: case WM_MOVE: 361: { 362: int xPos, yPos; 363: RECT r; 364: int style; 365: 366: if (!vid_fullscreen->value) 367: { 368: xPos = (short) LOWORD(lParam); // horizontal position 369: yPos = (short) HIWORD(lParam); // vertical position 370: 371: r.left = 0; 372: r.top = 0; 373: r.right = 1; 374: r.bottom = 1; 375: 376: style = GetWindowLong( hWnd, GWL_STYLE ); 377: AdjustWindowRect( &r, style, FALSE ); 378: 379: Cvar_SetValue( "vid_xpos", xPos + r.left); 380: Cvar_SetValue( "vid_ypos", yPos + r.top); 381: vid_xpos->modified = false; 382: vid_ypos->modified = false; 383: if (ActiveApp) 384: IN_Activate (true); 385: } 386: } 387: return DefWindowProc (hWnd, uMsg, wParam, lParam); 388: 389: // this is complicated because Win32 seems to pack multiple mouse events into 390: // one update sometimes, so we always check all states and look for events 391: case WM_LBUTTONDOWN: 392: case WM_LBUTTONUP: 393: case WM_RBUTTONDOWN: 394: case WM_RBUTTONUP: 395: case WM_MBUTTONDOWN: 396: case WM_MBUTTONUP: 397: case WM_MOUSEMOVE: 398: { 399: int temp; 400: 401: temp = 0; 402: 403: if (wParam & MK_LBUTTON) 404: temp |= 1; 405: 406: if (wParam & MK_RBUTTON) 407: temp |= 2; 408: 409: if (wParam & MK_MBUTTON) 410: temp |= 4; 411: 412: IN_MouseEvent (temp); 413: } 414: break; 415: 416: case WM_SYSCOMMAND: 417: if ( wParam == SC_SCREENSAVE ) 418: return 0; 419: return DefWindowProc (hWnd, uMsg, wParam, lParam); 420: case WM_SYSKEYDOWN: 421: if ( wParam == 13 ) 422: { 423: if ( vid_fullscreen ) 424: { 425: Cvar_SetValue( "vid_fullscreen", !vid_fullscreen->value ); 426: } 427: return 0; 428: } 429: // fall through 430: case WM_KEYDOWN: 431: Key_Event( MapKey( lParam ), true, sys_msg_time); 432: break; 433: 434: case WM_SYSKEYUP: 435: case WM_KEYUP: 436: Key_Event( MapKey( lParam ), false, sys_msg_time); 437: break; 438: 439: case MM_MCINOTIFY: 440: { 441: LONG CDAudio_MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 442: lRet = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam); 443: } 444: break; 445: 446: default: // pass all unhandled messages to DefWindowProc 447: return DefWindowProc (hWnd, uMsg, wParam, lParam); 448: } 449: 450: /* return 0 if handled message, 1 if not */ 451: return DefWindowProc( hWnd, uMsg, wParam, lParam ); 452: } 453: 454: /* 455: ============ 456: VID_Restart_f 457: 458: Console command to re-start the video mode and refresh DLL. We do this 459: simply by setting the modified flag for the vid_ref variable, which will 460: cause the entire video mode and refresh DLL to be reset on the next frame. 461: ============ 462: */ 463: void VID_Restart_f (void) 464: { 465: vid_ref->modified = true; 466: } 467: 468: void VID_Front_f( void ) 469: { 470: SetWindowLong( cl_hwnd, GWL_EXSTYLE, WS_EX_TOPMOST ); 471: SetForegroundWindow( cl_hwnd ); 472: } 473: 474: /* 475: ** VID_GetModeInfo 476: */ 477: typedef struct vidmode_s 478: { 479: const char *description; 480: int width, height; 481: int mode; 482: } vidmode_t; 483: 484: vidmode_t vid_modes[] = 485: { 486: { "Mode 0: 320x240", 320, 240, 0 }, 487: { "Mode 1: 400x300", 400, 300, 1 }, 488: { "Mode 2: 512x384", 512, 384, 2 }, 489: { "Mode 3: 640x480", 640, 480, 3 }, 490: { "Mode 4: 800x600", 800, 600, 4 }, 491: { "Mode 5: 960x720", 960, 720, 5 }, 492: { "Mode 6: 1024x768", 1024, 768, 6 }, 493: { "Mode 7: 1152x864", 1152, 864, 7 }, 494: { "Mode 8: 1280x960", 1280, 960, 8 }, 1.1.1.3 ! root 495: { "Mode 9: 1600x1200", 1600, 1200, 9 }, ! 496: { "Mode 10: 2048x1536", 2048, 1536, 10 } 1.1 root 497: }; 498: 499: qboolean VID_GetModeInfo( int *width, int *height, int mode ) 500: { 501: if ( mode < 0 || mode >= VID_NUM_MODES ) 502: return false; 503: 504: *width = vid_modes[mode].width; 505: *height = vid_modes[mode].height; 506: 507: return true; 508: } 509: 510: /* 511: ** VID_UpdateWindowPosAndSize 512: */ 513: void VID_UpdateWindowPosAndSize( int x, int y ) 514: { 515: RECT r; 516: int style; 517: int w, h; 518: 519: r.left = 0; 520: r.top = 0; 521: r.right = viddef.width; 522: r.bottom = viddef.height; 523: 524: style = GetWindowLong( cl_hwnd, GWL_STYLE ); 525: AdjustWindowRect( &r, style, FALSE ); 526: 527: w = r.right - r.left; 528: h = r.bottom - r.top; 529: 530: MoveWindow( cl_hwnd, vid_xpos->value, vid_ypos->value, w, h, TRUE ); 531: } 532: 533: /* 534: ** VID_NewWindow 535: */ 536: void VID_NewWindow ( int width, int height) 537: { 538: viddef.width = width; 539: viddef.height = height; 540: 541: cl.force_refdef = true; // can't use a paused refdef 542: } 543: 544: void VID_FreeReflib (void) 545: { 546: if ( !FreeLibrary( reflib_library ) ) 547: Com_Error( ERR_FATAL, "Reflib FreeLibrary failed" ); 548: memset (&re, 0, sizeof(re)); 549: reflib_library = NULL; 550: reflib_active = false; 551: } 552: 553: /* 554: ============== 555: VID_LoadRefresh 556: ============== 557: */ 558: qboolean VID_LoadRefresh( char *name ) 559: { 560: refimport_t ri; 561: GetRefAPI_t GetRefAPI; 562: 563: if ( reflib_active ) 564: { 565: re.Shutdown(); 566: VID_FreeReflib (); 567: } 568: 569: Com_Printf( "------- Loading %s -------\n", name ); 570: 571: if ( ( reflib_library = LoadLibrary( name ) ) == 0 ) 572: { 573: Com_Printf( "LoadLibrary(\"%s\") failed\n", name ); 574: 575: return false; 576: } 577: 578: ri.Cmd_AddCommand = Cmd_AddCommand; 579: ri.Cmd_RemoveCommand = Cmd_RemoveCommand; 580: ri.Cmd_Argc = Cmd_Argc; 581: ri.Cmd_Argv = Cmd_Argv; 582: ri.Cmd_ExecuteText = Cbuf_ExecuteText; 583: ri.Con_Printf = VID_Printf; 584: ri.Sys_Error = VID_Error; 585: ri.FS_LoadFile = FS_LoadFile; 586: ri.FS_FreeFile = FS_FreeFile; 587: ri.FS_Gamedir = FS_Gamedir; 588: ri.Cvar_Get = Cvar_Get; 589: ri.Cvar_Set = Cvar_Set; 590: ri.Cvar_SetValue = Cvar_SetValue; 591: ri.Vid_GetModeInfo = VID_GetModeInfo; 592: ri.Vid_MenuInit = VID_MenuInit; 593: ri.Vid_NewWindow = VID_NewWindow; 594: 595: if ( ( GetRefAPI = (void *) GetProcAddress( reflib_library, "GetRefAPI" ) ) == 0 ) 596: Com_Error( ERR_FATAL, "GetProcAddress failed on %s", name ); 597: 598: re = GetRefAPI( ri ); 599: 600: if (re.api_version != API_VERSION) 601: { 602: VID_FreeReflib (); 603: Com_Error (ERR_FATAL, "%s has incompatible api_version", name); 604: } 605: 606: if ( re.Init( global_hInstance, MainWndProc ) == -1 ) 607: { 608: re.Shutdown(); 609: VID_FreeReflib (); 610: return false; 611: } 612: 613: Com_Printf( "------------------------------------\n"); 614: reflib_active = true; 1.1.1.2 root 615: 616: //====== 617: //PGM 618: vidref_val = VIDREF_OTHER; 619: if(vid_ref) 620: { 621: if(!strcmp (vid_ref->string, "gl")) 622: vidref_val = VIDREF_GL; 623: else if(!strcmp(vid_ref->string, "soft")) 624: vidref_val = VIDREF_SOFT; 625: } 626: //PGM 627: //====== 628: 1.1 root 629: return true; 630: } 631: 632: /* 633: ============ 634: VID_CheckChanges 635: 636: This function gets called once just before drawing each frame, and it's sole purpose in life 637: is to check to see if any of the video mode parameters have changed, and if they have to 638: update the rendering DLL and/or video mode to match. 639: ============ 640: */ 641: void VID_CheckChanges (void) 642: { 643: char name[100]; 644: 645: if ( win_noalttab->modified ) 646: { 647: if ( win_noalttab->value ) 648: { 649: WIN_DisableAltTab(); 650: } 651: else 652: { 653: WIN_EnableAltTab(); 654: } 655: win_noalttab->modified = false; 656: } 657: 658: if ( vid_ref->modified ) 659: { 660: cl.force_refdef = true; // can't use a paused refdef 661: S_StopAllSounds(); 662: } 663: while (vid_ref->modified) 664: { 665: /* 666: ** refresh has changed 667: */ 668: vid_ref->modified = false; 669: vid_fullscreen->modified = true; 670: cl.refresh_prepped = false; 671: cls.disable_screen = true; 672: 673: Com_sprintf( name, sizeof(name), "ref_%s.dll", vid_ref->string ); 674: if ( !VID_LoadRefresh( name ) ) 675: { 676: if ( strcmp (vid_ref->string, "soft") == 0 ) 677: Com_Error (ERR_FATAL, "Couldn't fall back to software refresh!"); 678: Cvar_Set( "vid_ref", "soft" ); 679: 680: /* 681: ** drop the console if we fail to load a refresh 682: */ 683: if ( cls.key_dest != key_console ) 684: { 685: Con_ToggleConsole_f(); 686: } 687: } 688: cls.disable_screen = false; 689: } 690: 691: /* 692: ** update our window position 693: */ 694: if ( vid_xpos->modified || vid_ypos->modified ) 695: { 696: if (!vid_fullscreen->value) 697: VID_UpdateWindowPosAndSize( vid_xpos->value, vid_ypos->value ); 698: 699: vid_xpos->modified = false; 700: vid_ypos->modified = false; 701: } 702: } 703: 704: /* 705: ============ 706: VID_Init 707: ============ 708: */ 709: void VID_Init (void) 710: { 711: /* Create the video variables so we know how to start the graphics drivers */ 712: vid_ref = Cvar_Get ("vid_ref", "soft", CVAR_ARCHIVE); 713: vid_xpos = Cvar_Get ("vid_xpos", "3", CVAR_ARCHIVE); 714: vid_ypos = Cvar_Get ("vid_ypos", "22", CVAR_ARCHIVE); 715: vid_fullscreen = Cvar_Get ("vid_fullscreen", "0", CVAR_ARCHIVE); 716: vid_gamma = Cvar_Get( "vid_gamma", "1", CVAR_ARCHIVE ); 717: win_noalttab = Cvar_Get( "win_noalttab", "0", CVAR_ARCHIVE ); 718: 719: /* Add some console commands that we want to handle */ 720: Cmd_AddCommand ("vid_restart", VID_Restart_f); 721: Cmd_AddCommand ("vid_front", VID_Front_f); 722: 723: /* 724: ** this is a gross hack but necessary to clamp the mode for 3Dfx 725: */ 726: #if 0 727: { 728: cvar_t *gl_driver = Cvar_Get( "gl_driver", "opengl32", 0 ); 729: cvar_t *gl_mode = Cvar_Get( "gl_mode", "3", 0 ); 730: 731: if ( stricmp( gl_driver->string, "3dfxgl" ) == 0 ) 732: { 733: Cvar_SetValue( "gl_mode", 3 ); 734: viddef.width = 640; 735: viddef.height = 480; 736: } 737: } 738: #endif 739: 740: /* Disable the 3Dfx splash screen */ 741: putenv("FX_GLIDE_NO_SPLASH=0"); 742: 743: /* Start the graphics mode and load refresh DLL */ 744: VID_CheckChanges(); 745: } 746: 747: /* 748: ============ 749: VID_Shutdown 750: ============ 751: */ 752: void VID_Shutdown (void) 753: { 754: if ( reflib_active ) 755: { 756: re.Shutdown (); 757: VID_FreeReflib (); 758: } 759: } 760: 761:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.