//========= Copyright Valve Corporation, All rights reserved. ============// // // VXCONSOLE.CPP // // Valve XBox Console. //=====================================================================================// #include "vxconsole.h" HWND g_hDlgMain; HWND g_hwndCommandCombo; HWND g_hwndOutputWindow; HWND g_hwndCommandHint; WNDPROC g_hwndCommandSubclassed; BOOL g_connectedToXBox; BOOL g_connectedToApp; PDMN_SESSION g_pdmnSession; PDM_CONNECTION g_pdmConnection; printQueue_t g_PrintQueue; UINT_PTR g_autoConnectTimer; BOOL g_autoConnect; BOOL g_debugCommands; BOOL g_captureDebugSpew; BOOL g_captureGameSpew = TRUE; CHAR g_xboxName[MAX_XBOXNAMELEN]; DWORD g_xboxAddress; HINSTANCE g_hInstance; HICON g_hIcons[MAX_ICONS]; HBRUSH g_hBackgroundBrush; HFONT g_hFixedFont; BOOL g_reboot; char* g_rebootArgv[MAX_ARGVELEMS]; int g_rebootArgc; COLORREF g_backgroundColor; TEXTMETRIC g_fixedFontMetrics; int g_connectCount; int g_currentIcon = -1; HACCEL g_hAccel; HMODULE g_hRichEdit; DWORD g_connectedTime; RECT g_mainWindowRect; HFONT g_hProportionalFont; HANDLE g_hCommandReadyEvent; int g_currentCommandSelection; int g_connectFailure; int g_configID; bool g_bSuppressBlink = false; BOOL g_bPlayTestMode = TRUE; LRESULT CALLBACK Main_DlgProc( HWND, UINT, WPARAM, LPARAM ); LRESULT CALLBACK CommandWindow_SubclassedProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam ); bool ParseCommandLineArg( const char *pCmdLine, const char *pKey, char *pValueBuff, int valueBuffSize ) { const char* pArg = V_stristr( (char*)pCmdLine, pKey ); if ( !pArg ) { return false; } if ( pValueBuff ) { // caller wants next token pArg += strlen( pKey ); int i; for ( i=0; i<valueBuffSize; i++ ) { pValueBuff[i] = *pArg; if ( *pArg == '\0' || *pArg == ' ' ) { break; } pArg++; } pValueBuff[i] = '\0'; } return true; } void MakeConfigString( const char *pString, int configID, char *pOutBuff, int outBuffSize ) { if ( configID <= 0 ) { // as-is, undecorated V_snprintf( pOutBuff, outBuffSize, "%s", pString ); return; } int len = strlen( pString ); bool bAddTerminalSlash = ( len > 1 && pString[len-1] == '\\' ); V_snprintf( pOutBuff, outBuffSize, "%s_%d", pString, configID ); if ( bAddTerminalSlash ) { V_strncat( pOutBuff, "\\", outBuffSize ); } } //----------------------------------------------------------------------------- // LoadConfig // //----------------------------------------------------------------------------- void LoadConfig() { char buff[256]; int numArgs; ConfigDlg_LoadConfig(); // initial menu state is from persisted config g_captureDebugSpew = g_captureDebugSpew_StartupState; Sys_GetRegistryString( "mainWindowRect", buff, "", sizeof( buff ) ); numArgs = sscanf( buff, "%d %d %d %d", &g_mainWindowRect.left, &g_mainWindowRect.top, &g_mainWindowRect.right, &g_mainWindowRect.bottom ); if ( numArgs != 4 || g_mainWindowRect.left < 0 || g_mainWindowRect.top < 0 || g_mainWindowRect.right < 0 || g_mainWindowRect.bottom < 0 ) memset( &g_mainWindowRect, 0, sizeof( g_mainWindowRect ) ); } //----------------------------------------------------------------------------- // SaveConfig // //----------------------------------------------------------------------------- void SaveConfig() { char buff[256]; // get window placement WINDOWPLACEMENT wp; memset( &wp, 0, sizeof( wp ) ); wp.length = sizeof( WINDOWPLACEMENT ); GetWindowPlacement( g_hDlgMain, &wp ); g_mainWindowRect = wp.rcNormalPosition; sprintf( buff, "%d %d %d %d", g_mainWindowRect.left, g_mainWindowRect.top, g_mainWindowRect.right, g_mainWindowRect.bottom ); Sys_SetRegistryString( "mainWindowRect", buff ); } //----------------------------------------------------------------------------- // SetConnectionIcon // //----------------------------------------------------------------------------- void SetConnectionIcon( int icon ) { if ( g_currentIcon == icon ) return; g_currentIcon = icon; SetClassLongPtr( g_hDlgMain, GCLP_HICON, ( LONG_PTR )g_hIcons[icon] ); } //----------------------------------------------------------------------------- // SetMainWindowTitle // //----------------------------------------------------------------------------- void SetMainWindowTitle() { if ( !g_hDlgMain ) { return; } char titleBuff[128]; if ( !g_xboxTargetName[0] ) { strcpy( titleBuff, VXCONSOLE_TITLE ); } else { sprintf( titleBuff, "%s: %s", VXCONSOLE_TITLE, g_xboxTargetName ); if ( g_configID ) { char configBuff[32]; sprintf( configBuff, " (%d)", g_configID ); V_strncat( titleBuff, configBuff, sizeof( titleBuff ) ); } } SetWindowText( g_hDlgMain, titleBuff ); } //----------------------------------------------------------------------------- // DmAPI_DisplayError // //----------------------------------------------------------------------------- VOID DmAPI_DisplayError( const CHAR* message, HRESULT hr ) { CHAR strError[128]; ConsoleWindowPrintf( RGB( 255,0,0 ), "%s\n", message ); HRESULT hrError = DmTranslateError( hr, strError, sizeof( strError ) ); if ( !FAILED( hrError ) && strError[0] ) ConsoleWindowPrintf( RGB( 255,0,0 ), "Reason: '%s'\n", strError ); else ConsoleWindowPrintf( RGB( 255,0,0 ), "Reason: 0x%08lx\n", hr ); } //----------------------------------------------------------------------------- // DmAPI_SendCommand // // Send the specified string across the debugger channel to the application //----------------------------------------------------------------------------- HRESULT DmAPI_SendCommand( const char* strCommand, bool wait ) { DWORD dwResponseLen; CHAR strResponse[MAX_PATH]; int retval; bool bIgnorePingResponse; char* ptr; retval = WaitForSingleObject( g_hCommandReadyEvent, wait ? INFINITE : 0 ); if ( retval != WAIT_OBJECT_0 ) { // cannot send command // some other previous command has not responded and signaled the release // testing has shown DmSendCommand() is not re-entrant and callers get // their responses out of sync return XBDM_UNDEFINED; } // clear the event mutex until ready ResetEvent( g_hCommandReadyEvent ); bIgnorePingResponse = false; dwResponseLen = sizeof( strResponse ); strResponse[0] = '\0'; if ( strCommand[0] == '*' ) { // skip past internal command identifier strCommand++; } else if ( !stricmp( strCommand, VXCONSOLE_COMMAND_PREFIX "!" ) ) { // empty ping command // must be done as a synchronous command with a response because there // is no way to bind an asynch response to the owner bIgnorePingResponse = true; } HRESULT hr = DmSendCommand( g_pdmConnection, strCommand, strResponse, &dwResponseLen ); if ( !FAILED( hr ) ) { // success switch ( hr ) { case XBDM_NOERR: if ( !bIgnorePingResponse ) { // skip past possible ack prefix ptr = strstr( strResponse, VXCONSOLE_COMMAND_ACK ); if ( ptr ) { ptr += strlen( VXCONSOLE_COMMAND_ACK ); // ignore remote acknowledge response if ( !stricmp( ptr, "OK" ) ) break; } else { ptr = strResponse; } ConsoleWindowPrintf( XBX_CLR_DEFAULT, "%s\n", ptr ); } break; case XBDM_MULTIRESPONSE: // multi-line response - loop, looking for end of response while ( 1 ) { dwResponseLen = sizeof( strResponse ); hr = DmReceiveSocketLine( g_pdmConnection, strResponse, &dwResponseLen ); if ( FAILED( hr ) || strResponse[0] == '.' ) break; ConsoleWindowPrintf( XBX_CLR_DEFAULT, "%s\n", strResponse ); } break; case XBDM_BINRESPONSE: ConsoleWindowPrintf( XBX_CLR_DEFAULT, "Binary response - not implemented\n" ); break; case XBDM_READYFORBIN: ConsoleWindowPrintf( XBX_CLR_DEFAULT, "Ready for binary - not implemented\n" ); break; default: ConsoleWindowPrintf( XBX_CLR_RED, "Unknown Response: ( %s ).\n", strResponse ); break; } } SetEvent( g_hCommandReadyEvent ); return hr; } //----------------------------------------------------------------------------- // PrintToQueue // // Formats the string and adds it to the print queue //----------------------------------------------------------------------------- void PrintToQueue( COLORREF rgb, LPCTSTR strFormat, ... ) { char buffer[MAX_QUEUEDSTRINGLEN]; // enter critical section so we don't try to process the list EnterCriticalSection( &g_PrintQueue.CriticalSection ); assert( g_PrintQueue.numMessages <= MAX_QUEUEDSTRINGS ); // when the queue is full, the main thread is probably blocked and not dequeing if ( !g_captureGameSpew || g_PrintQueue.numMessages == MAX_QUEUEDSTRINGS ) { LeaveCriticalSection( &g_PrintQueue.CriticalSection ); return; } va_list arglist; va_start( arglist, strFormat ); int len = _vsnprintf( buffer, MAX_QUEUEDSTRINGLEN, strFormat, arglist ); if ( len == -1 ) { buffer[sizeof(buffer)-1] = '\0'; } va_end( arglist ); // queue the message into the next slot g_PrintQueue.pMessages[g_PrintQueue.numMessages] = Sys_CopyString( buffer ); g_PrintQueue.aColors[g_PrintQueue.numMessages++] = rgb; // ensure we post a message to process the print queue if ( g_PrintQueue.numMessages == 1 ) PostMessage( g_hDlgMain, WM_USER, 0, 0 ); // the main thread can now safely process the list LeaveCriticalSection( &g_PrintQueue.CriticalSection ); } //----------------------------------------------------------------------------- // ProcessPrintQueue // //----------------------------------------------------------------------------- void ProcessPrintQueue() { // enter critical section so we don't try to add anything while we're processing EnterCriticalSection( &g_PrintQueue.CriticalSection ); // dequeue all entries for ( int i = 0; i < g_PrintQueue.numMessages; i++ ) { ConsoleWindowPrintf( g_PrintQueue.aColors[i], "%s", g_PrintQueue.pMessages[i] ); Sys_Free( g_PrintQueue.pMessages[i] ); } g_PrintQueue.numMessages = 0; // now we can safely add to the list LeaveCriticalSection( &g_PrintQueue.CriticalSection ); } //----------------------------------------------------------------------------- // ConsoleWindowPrintf // // Writes out a string directly to the console window //----------------------------------------------------------------------------- int ConsoleWindowPrintf( COLORREF rgb, LPCTSTR strFormat, ... ) { int dwStrLen; char strTemp[512]; va_list arglist; CHARRANGE cr = { -1, -2 }; if ( rgb != XBX_CLR_DEFAULT ) { // set whatever colors, etc. they want CHARFORMAT cf = {0}; cf.cbSize = sizeof( cf ); cf.dwMask = CFM_COLOR; cf.dwEffects = 0; cf.crTextColor = rgb; SendDlgItemMessage( g_hDlgMain, IDC_OUTPUT, EM_SETCHARFORMAT, SCF_SELECTION, ( LPARAM )&cf ); } // Get our string to print va_start( arglist, strFormat ); dwStrLen = _vsnprintf( strTemp, sizeof( strTemp ), strFormat, arglist ); va_end( arglist ); // Move the selection to the end SendDlgItemMessage( g_hDlgMain, IDC_OUTPUT, EM_EXSETSEL, 0, ( LPARAM )&cr ); // Add the text and scroll it into view SendDlgItemMessage( g_hDlgMain, IDC_OUTPUT, EM_REPLACESEL, 0, ( LONG )( LPSTR )strTemp ); SendDlgItemMessage( g_hDlgMain, IDC_OUTPUT, EM_SCROLLCARET, 0, 0L ); if ( g_bPlayTestMode ) { char szLogPath[MAX_PATH]; char szLogName[MAX_PATH]; V_snprintf( szLogName, sizeof( szLogName ), "vxconsole_%s.log", g_xboxTargetName ); V_ComposeFileName( g_localPath, szLogName, szLogPath, sizeof( szLogPath ) ); FILE *fp = fopen( szLogPath, "at+" ); if ( fp ) { fprintf( fp, strTemp ); fclose( fp ); } } return dwStrLen; } //----------------------------------------------------------------------------- // ProcessCommand // //----------------------------------------------------------------------------- bool ProcessCommand( const char* strCmdIn ) { char strRemoteCmd[MAX_PATH + 10]; TCHAR strCmdBak[MAX_PATH]; char strCmd[MAX_PATH]; char* argv[MAX_ARGVELEMS]; BOOL isXCommand = FALSE; BOOL isLocal = FALSE; BOOL isRemote = FALSE; int iIndex; // local copy for destructive purposes strcpy( strCmd, strCmdIn ); // copy of the original command string lstrcpyA( strCmdBak, strCmdIn ); ConsoleWindowPrintf( XBX_CLR_DEFAULT, "] %s\n", strCmd ); // parse argstring into components int argc = CmdToArgv( strCmd, argv, MAX_ARGVELEMS ); if ( !argc ) { // empty command return true; } if ( ( iIndex = ComboBox_GetCount( g_hwndCommandCombo ) ) >= MAX_COMMANDHISTORY ) { // Limit the history of items, purge oldest ComboBox_DeleteString( g_hwndCommandCombo, 0 ); } // add to end of list iIndex = ComboBox_InsertItemData( g_hwndCommandCombo, -1, strCmdBak ); ComboBox_SetCurSel( g_hwndCommandCombo, -1 ); // find command in local list for ( int i=0; i<g_numLocalCommands; i++ ) { if ( lstrcmpiA( g_localCommands[i].strCommand, argv[0] ) ) { // no match continue; } isLocal = TRUE; if ( !g_localCommands[i].pfnHandler ) { // no handler, remote xcommand isXCommand = TRUE; } if ( ( g_localCommands[i].flags & FN_XBOX ) && !g_connectedToXBox ) { // not allowed yet ConsoleWindowPrintf( RGB( 255,0,0 ), "'%s' is not available until connected to XBox.\n", argv[0] ); return true; } else if ( ( g_localCommands[i].flags & FN_APP ) && !g_connectedToApp ) { // not allowed yet ConsoleWindowPrintf( RGB( 255,0,0 ), "'%s' is not available until connected to Application.\n", argv[0] ); return true; } if ( isXCommand ) break; // do local command g_localCommands[i].pfnHandler( argc, argv ); return true; } // find command in remote list if ( !isLocal && !isXCommand && g_connectedToApp ) { for ( int i=0; i<g_numRemoteCommands; i++ ) { if ( lstrcmpiA( g_remoteCommands[i]->strCommand, argv[0] ) ) { // no match continue; } isRemote = TRUE; if ( !g_connectedToApp ) { // not allowed yet ConsoleWindowPrintf( RGB( 255,0,0 ), "'%s' is not available until connected to Application.\n", argv[0] ); return true; } break; } } if ( !isLocal && !isXCommand && !isRemote ) { if ( !g_connectedToApp || g_numRemoteCommands != 0 ) { // unrecognized ConsoleWindowPrintf( RGB( 255,0,0 ), "'%s' is not a recognized command.\n", argv[0] ); return true; } } if ( isXCommand ) { // send the xcommand directly lstrcpyA( strRemoteCmd, strCmdBak ); } else { // add remote command prefix lstrcpyA( strRemoteCmd, VXCONSOLE_COMMAND_PREFIX "!" ); lstrcatA( strRemoteCmd, strCmdBak ); } // send the command to the Xbox HRESULT hr = DmAPI_SendCommand( strRemoteCmd, true ); if ( FAILED( hr ) ) { DmAPI_DisplayError( "DmSendCommand", hr ); return false; } return TRUE; } //----------------------------------------------------------------------------- // CommandWindow_HandleKey // // Handle a WM_KEYDOWN in our RTF cmd window //----------------------------------------------------------------------------- BOOL CommandWindow_HandleKey( WPARAM wParam ) { BOOL bHandled = FALSE; int curSel; int numItems; if ( wParam >= VK_F1 && wParam <= VK_F12 ) { if ( Bindings_TranslateKey( wParam ) ) { // handled return true; } } switch ( wParam ) { case VK_TAB: case VK_UP: case VK_DOWN: if ( IsWindowVisible( g_hwndCommandHint ) ) { // hint window open char hintCmd[MAX_PATH]; char userCmd[MAX_PATH]; // scroll through the hint selections curSel = SendMessage( g_hwndCommandHint, (UINT)LB_GETCURSEL, NULL, NULL ); SendMessage( g_hwndCommandHint, (UINT)LB_GETTEXT, (WPARAM)curSel, (LPARAM)hintCmd ); numItems = SendMessage( g_hwndCommandHint, (UINT)LB_GETCOUNT, NULL, NULL ); if ( numItems < 0 ) numItems = 0; if ( wParam == VK_TAB ) { // get command typed so far ComboBox_GetText( g_hwndCommandCombo, userCmd, MAX_PATH ); // strip the auto-space off the end int len = Q_strlen(userCmd); if ( userCmd[len-1] == ' ' ) { userCmd[len-1] = '\0'; } if ( !stricmp( userCmd, hintCmd ) ) { // cycle to next or prev command with tab or shift-tab if ( GetKeyState(VK_SHIFT) < 0 ) { wParam = VK_UP; } else { wParam = VK_DOWN; } } } // move the selection if ( wParam == VK_UP ) curSel--; else if ( wParam == VK_DOWN ) curSel++; if ( curSel < 0 ) curSel = numItems - 1; else if ( curSel > numItems-1 ) curSel = 0; if ( curSel < 0 ) curSel = 0; // set the selection and get highlighted command SendMessage( g_hwndCommandHint, (UINT)LB_SETCURSEL, (WPARAM)curSel, NULL ); SendMessage( g_hwndCommandHint, (UINT)LB_GETTEXT, (WPARAM)curSel, (LPARAM)hintCmd ); // add a space to the end for easier parameter setting Q_strncat( hintCmd, " ", sizeof(hintCmd), 1 ); // replace command string ComboBox_SetText( g_hwndCommandCombo, hintCmd ); // set cursor to end of command SendMessage( g_hwndCommandCombo, (UINT)CB_SETEDITSEL, (WPARAM)0, MAKELONG( MAX_PATH,MAX_PATH ) ); bHandled = TRUE; } else { curSel = SendMessage( g_hwndCommandCombo, (UINT)CB_GETCURSEL, NULL, NULL ); if ( curSel < 0 ) { // combo box has no selection // override combo box behavior and set selection numItems = SendMessage( g_hwndCommandCombo, (UINT)CB_GETCOUNT, NULL, NULL ); if ( numItems > 0 ) { if ( wParam == VK_UP ) { // set to bottom of list curSel = numItems-1; } else if ( wParam == VK_DOWN ) { // set to top of list curSel = 0; } SendMessage( g_hwndCommandCombo, (UINT)CB_SETCURSEL, (WPARAM)curSel, NULL ); bHandled = TRUE; } } } break; case VK_RETURN: // user hit return in the combo box if ( ComboBox_GetDroppedState( g_hwndCommandCombo ) ) { ComboBox_ShowDropdown( g_hwndCommandCombo, FALSE ); } else { PostMessage( g_hDlgMain, WM_APP, 0, 0 ); bHandled = TRUE; } break; } return bHandled; } //----------------------------------------------------------------------------- // CommandWindow_SubclassedProc // //----------------------------------------------------------------------------- LRESULT CALLBACK CommandWindow_SubclassedProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam ) { switch ( msg ) { case WM_SYSKEYDOWN: case WM_KEYDOWN: if ( CommandWindow_HandleKey( wParam ) ) return 0; break; case WM_CHAR: if ( wParam == VK_RETURN ) return 0; break; } return CallWindowProc( g_hwndCommandSubclassed, hDlg, msg, wParam, lParam ); } //----------------------------------------------------------------------------- // Main_SizeWindow // // Handles a WM_SIZE message by resizing all our child windows to match the main window //----------------------------------------------------------------------------- void Main_SizeWindow( HWND hDlg, UINT wParam, int cx, int cy ) { if ( cx==0 || cy==0 ) { RECT rcClient; GetClientRect( hDlg, &rcClient ); cx = rcClient.right; cy = rcClient.bottom; } // if we're big enough, position our child windows if ( g_hwndCommandCombo && cx > 64 && cy > 64 ) { RECT rcCmd; RECT rcHint; RECT rcOut; // fit the "command" combo box into our window GetWindowRect( g_hwndCommandCombo, &rcCmd ); ScreenToClient( hDlg, ( LPPOINT )&rcCmd ); ScreenToClient( hDlg, ( LPPOINT )&rcCmd + 1 ); int x = rcCmd.left; int dx = cx - 8 - x; int dy = rcCmd.bottom - rcCmd.top; int y = cy - 8 - dy; SetWindowPos( g_hwndCommandCombo, NULL, x, y, dx, dy, SWP_NOZORDER ); // position the "hint popup" window above the "command" window GetWindowRect( g_hwndCommandHint, &rcHint ); ScreenToClient( g_hDlgMain, ( LPPOINT )&rcHint ); ScreenToClient( g_hDlgMain, ( LPPOINT )&rcHint + 1 ); SetWindowPos( g_hwndCommandHint, NULL, rcCmd.left, ( rcCmd.top - 4 ) - ( rcHint.bottom - rcHint.top + 1 ), 0, 0, SWP_NOSIZE | SWP_NOZORDER ); // position the "Cmd" label RECT rcStaticCmd; HWND hStaticCmd = GetDlgItem( g_hDlgMain, IDC_LABEL ); GetWindowRect( hStaticCmd, &rcStaticCmd ); ScreenToClient( hDlg, ( LPPOINT )&rcStaticCmd ); ScreenToClient( hDlg, ( LPPOINT )&rcStaticCmd + 1 ); SetWindowPos( hStaticCmd, NULL, 8, y + ( dy - ( rcStaticCmd.bottom - rcStaticCmd.top ) ) / 2 - 1, 0, 0, SWP_NOSIZE | SWP_NOZORDER ); // position the "output" window GetWindowRect( g_hwndOutputWindow, &rcOut ); ScreenToClient( hDlg, ( LPPOINT )&rcOut ); int dwWidth = cx - rcOut.left - 8; int dwHeight = y - rcOut.top - 8; SetWindowPos( g_hwndOutputWindow, NULL, 0, 0, dwWidth, dwHeight, SWP_NOMOVE | SWP_NOZORDER ); } } //----------------------------------------------------------------------------- // _SortCommands // //----------------------------------------------------------------------------- int _SortCommands( const void* a, const void* b ) { const char* strA = *( const char** )a; const char* strB = *( const char** )b; return ( stricmp( strA, strB ) ); } //----------------------------------------------------------------------------- // EnableCommandHint // // Open/Close the command hint popup //----------------------------------------------------------------------------- void EnableCommandHint( bool enable ) { int w = 0; int h = 0; int itemHeight; int i; int maxLen; int len; const char* cmds[256]; int numLocalCommands; int numRemoteCommands; int curSel; if ( !enable ) goto cleanUp; // get the current command char strCmd[MAX_PATH]; ComboBox_GetText( g_hwndCommandCombo, strCmd, MAX_PATH ); if ( !strCmd[0] ) { // user has typed nothing enable = false; goto cleanUp; } SendMessage( g_hwndCommandHint, ( UINT )LB_RESETCONTENT, NULL, NULL ); // get a list of possible matches maxLen = 0; numLocalCommands = MatchLocalCommands( strCmd, cmds, 256 ); numRemoteCommands = MatchRemoteCommands( strCmd, cmds+numLocalCommands, 256-numLocalCommands ); for ( i=0; i<numLocalCommands+numRemoteCommands; i++ ) { len = strlen( cmds[i] ); if ( maxLen < len ) maxLen = len; } if ( !maxLen ) { // no matches enable = false; goto cleanUp; } // sort the list ( eschew listbox's autosorting ) qsort( cmds, numLocalCommands+numRemoteCommands, sizeof( const char* ), _SortCommands ); curSel = -1; len = strlen( strCmd ); for ( i=0; i<numLocalCommands+numRemoteCommands; i++ ) { // populate the listbox SendMessage( g_hwndCommandHint, ( UINT )LB_ADDSTRING, 0, ( LPARAM )cmds[i] ); // determine first best match if ( curSel == -1 && !strnicmp( strCmd, cmds[i], len ) ) curSel = i; } if ( curSel != -1 ) { // set the selection to the first best string // ensure the top string is shown ( avoids odd auto-vscroll choices ) SendMessage( g_hwndCommandHint, ( UINT )LB_SETCURSEL, ( WPARAM )curSel, NULL ); if ( !curSel ) SendMessage( g_hwndCommandHint, ( UINT )LB_SETTOPINDEX, 0, NULL ); } RECT rcCmd; GetWindowRect( g_hwndCommandCombo, &rcCmd ); ScreenToClient( g_hDlgMain, ( LPPOINT )&rcCmd ); ScreenToClient( g_hDlgMain, ( LPPOINT )&rcCmd + 1 ); // clamp listbox height to client space itemHeight = SendMessage( g_hwndCommandHint, ( UINT )LB_GETITEMHEIGHT, 0, NULL ); if ( itemHeight <= 0 ) { // oops, shouldn't happen enable = false; goto cleanUp; } h = ( numLocalCommands + numRemoteCommands )*itemHeight + 2; if ( h > rcCmd.top - 8) { h = rcCmd.top - 8; } // clamp listbox width w = ( maxLen + 5 ) * g_fixedFontMetrics.tmMaxCharWidth; // position the "hint popup" window above the "command" window SetWindowPos( g_hwndCommandHint, NULL, rcCmd.left, ( rcCmd.top - 4 ) - h, w, h, SWP_NOZORDER ); cleanUp: BOOL isVisible = IsWindowVisible( g_hwndCommandHint ); if ( !enable && isVisible ) ShowWindow( g_hwndCommandHint, SW_HIDE ); else if ( enable && !isVisible ) ShowWindow( g_hwndCommandHint, SW_SHOWNA ); } //----------------------------------------------------------------------------- // Main_DlgProc // //----------------------------------------------------------------------------- LRESULT CALLBACK Main_DlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) { WORD wID = LOWORD( wParam ); BOOL isConnect; switch ( message ) { case WM_APP: // user has pressed enter // take the string from the command window and process it // extra room for \r\n char strCmd[MAX_PATH + 3]; ComboBox_GetText( g_hwndCommandCombo, strCmd, MAX_PATH ); ProcessCommand( strCmd ); ComboBox_SetText( g_hwndCommandCombo, "" ); EnableCommandHint( false ); break; case WM_USER: ProcessPrintQueue(); break; case WM_TIMER: // Don't do auto-connect stuff while the Assert dialog is up // (it uses a synchronous command, so calling 'Dm' funcs here can cause a lockup) if ( !g_AssertDialogActive ) { if ( wID == TIMERID_AUTOCONNECT ) { AutoConnectTimerProc( hDlg, TIMERID_AUTOCONNECT ); return 0L; } } break; case WM_CTLCOLORLISTBOX: case WM_CTLCOLOREDIT: SetBkColor( ( HDC )wParam,g_backgroundColor ); return ( BOOL )g_hBackgroundBrush; case WM_SIZE: Main_SizeWindow( hDlg, wParam, LOWORD( lParam ), HIWORD( lParam ) ); break; case WM_SYSCOMMAND: if ( wID == SC_CLOSE ) { PostMessage( hDlg, WM_CLOSE, 0, 0 ); return 0L; } break; case WM_CLOSE: // disconnect before closing lc_disconnect( 0, NULL ); SaveConfig(); DestroyWindow( hDlg ); break; case WM_DESTROY: SubclassWindow( g_hwndCommandCombo, g_hwndCommandSubclassed ); PostQuitMessage( 0 ); return 0L; case WM_INITMENU: isConnect = g_connectedToXBox || g_connectedToApp; CheckMenuItem( ( HMENU )wParam, IDM_AUTOCONNECT, MF_BYCOMMAND | ( g_autoConnect ? MF_CHECKED : MF_UNCHECKED ) ); CheckMenuItem( ( HMENU )wParam, IDM_CAPTUREGAMESPEW, MF_BYCOMMAND | ( g_captureGameSpew ? MF_CHECKED : MF_UNCHECKED ) ); CheckMenuItem( ( HMENU )wParam, IDM_CAPTUREDEBUGSPEW, MF_BYCOMMAND | ( g_captureDebugSpew ? MF_CHECKED : MF_UNCHECKED ) ); CheckMenuItem( ( HMENU )wParam, IDM_DEBUGCOMMANDS, MF_BYCOMMAND | ( g_debugCommands ? MF_CHECKED : MF_UNCHECKED ) ); CheckMenuItem( ( HMENU )wParam, IDM_PLAYTESTMODE, MF_BYCOMMAND | ( g_bPlayTestMode ? MF_CHECKED : MF_UNCHECKED ) ); EnableMenuItem( ( HMENU )wParam, IDM_SYNCFILES, MF_BYCOMMAND | ( g_connectedToXBox ? MF_ENABLED : MF_GRAYED ) ); EnableMenuItem( ( HMENU )wParam, IDM_SYNCINSTALL, MF_BYCOMMAND | ( g_connectedToXBox ? MF_ENABLED : MF_GRAYED ) ); return 0L; case WM_COMMAND: switch ( wID ) { case IDC_COMMAND: switch ( HIWORD( wParam ) ) { case CBN_EDITCHANGE: case CBN_SETFOCUS: EnableCommandHint( true ); break; case CBN_KILLFOCUS: EnableCommandHint( false ); break; } break; case IDM_CONFIG: ConfigDlg_Open(); return 0L; case IDM_CAPTUREGAMESPEW: g_captureGameSpew ^= 1; return 0L; case IDM_CAPTUREDEBUGSPEW: g_captureDebugSpew ^= 1; return 0L; case IDM_DEBUGCOMMANDS: g_debugCommands ^= 1; return 0L; case IDM_BUG: BugDlg_Open(); return 0L; case IDM_MEMORYDUMP: ShowMemDump_Open(); return 0L; case IDM_TIMESTAMPLOG: TimeStampLog_Open(); return 0L; case IDM_SYNCFILES: SyncFilesDlg_Open(); return 0L; case IDM_SYNCINSTALL: InstallDlg_Open(); return 0L; case IDM_EXCLUDEPATHS: ExcludePathsDlg_Open(); return 0L; case IDM_CPU_SAMPLES: CpuProfileSamples_Open(); return 0L; case IDM_CPU_HISTORY: CpuProfileHistory_Open(); return 0L; case IDM_D3D_SAMPLES: TexProfileSamples_Open(); return 0L; case IDM_D3D_HISTORY: TexProfileHistory_Open(); return 0L; case IDM_SHOWMEMORYUSAGE: MemProfile_Open(); return 0L; case IDM_PLAYTESTMODE: g_bPlayTestMode ^= 1; return 0L; case IDM_SHOWRESOURCES_TEXTURES: ShowTextures_Open(); return 0L; case IDM_SHOWRESOURCES_MATERIALS: ShowMaterials_Open(); return 0L; case IDM_SHOWRESOURCES_SOUNDS: ShowSounds_Open(); return 0L; case IDM_SHOWRESOURCES_MODELS: NotImplementedYet(); return 0L; case IDM_AUTOCONNECT: if ( g_connectedToXBox || g_connectedToApp || g_autoConnect ) lc_disconnect( 0, NULL ); else lc_autoConnect( 0, NULL ); return 0L; case IDM_BINDINGS_EDIT: Bindings_Open(); return 0L; case IDM_BINDINGS_BIND1: case IDM_BINDINGS_BIND2: case IDM_BINDINGS_BIND3: case IDM_BINDINGS_BIND4: case IDM_BINDINGS_BIND5: case IDM_BINDINGS_BIND6: case IDM_BINDINGS_BIND7: case IDM_BINDINGS_BIND8: case IDM_BINDINGS_BIND9: case IDM_BINDINGS_BIND10: case IDM_BINDINGS_BIND11: case IDM_BINDINGS_BIND12: Bindings_MenuSelection( wID ); return 0L; case IDM_EXIT: PostMessage( hDlg, WM_CLOSE, 0, 0 ); return 0L; } break; } return ( DefDlgProc( hDlg, message, wParam, lParam ) ); } //----------------------------------------------------------------------------- // CmdToArgv // // Parses a string into argv and return # of args. //----------------------------------------------------------------------------- int CmdToArgv( char* str, char* argv[], int maxargs ) { int argc = 0; int argcT = 0; char* strNil = str + lstrlenA( str ); while ( argcT < maxargs ) { // Eat whitespace while ( *str && ( *str==' ' ) ) str++; if ( !*str ) { argv[argcT++] = strNil; } else { // Find the end of this arg char chEnd = ( *str == '"' || *str == '\'' ) ? *str++ : ' '; char* strArgEnd = str; while ( *strArgEnd && ( *strArgEnd != chEnd ) ) strArgEnd++; // Record this argument argv[argcT++] = str; argc = argcT; // Move strArg to the next argument ( or not, if we hit the end ) str = *strArgEnd ? strArgEnd + 1 : strArgEnd; *strArgEnd = 0; } } return argc; } //----------------------------------------------------------------------------- // CreateCommandHint // //----------------------------------------------------------------------------- void CreateCommandHint() { // create the "hint" popup g_hwndCommandHint = CreateWindowEx( WS_EX_NOPARENTNOTIFY, "LISTBOX", "", WS_BORDER|WS_CHILD|LBS_HASSTRINGS|WS_VSCROLL, 0, 0, 100, 0, g_hDlgMain, ( HMENU )IDC_COMMANDHINT, g_hInstance, NULL ); // force the popup to head of z-order // to draw over all other children BringWindowToTop( g_hwndCommandHint ); // set font SendDlgItemMessage( g_hDlgMain, IDC_COMMANDHINT, WM_SETFONT, ( WPARAM )g_hFixedFont, TRUE ); } //----------------------------------------------------------------------------- // CreateResources // //----------------------------------------------------------------------------- bool CreateResources() { LOGFONT lf; HFONT hFontOld; HDC hDC; int i; // pull in common controls INITCOMMONCONTROLSEX initCommon; initCommon.dwSize = sizeof( INITCOMMONCONTROLSEX ); initCommon.dwICC = ICC_LISTVIEW_CLASSES|ICC_USEREX_CLASSES; if ( !InitCommonControlsEx( &initCommon ) ) return false; // pull in rich edit controls g_hRichEdit = LoadLibrary( "Riched32.dll" ); if ( !g_hRichEdit ) return false; g_backgroundColor = XBX_CLR_LTGREY; g_hBackgroundBrush = CreateSolidBrush( g_backgroundColor ); // get icons g_hIcons[ICON_APPLICATION] = LoadIcon( g_hInstance, MAKEINTRESOURCE( IDI_VXCONSOLE ) ); g_hIcons[ICON_DISCONNECTED] = LoadIcon( g_hInstance, MAKEINTRESOURCE( IDI_DISCONNECTED ) ); g_hIcons[ICON_CONNECTED_XBOX] = LoadIcon( g_hInstance, MAKEINTRESOURCE( IDI_CONNECT1_ON ) ); g_hIcons[ICON_CONNECTED_APP0] = LoadIcon( g_hInstance, MAKEINTRESOURCE( IDI_CONNECT2_OFF ) ); g_hIcons[ICON_CONNECTED_APP1] = LoadIcon( g_hInstance, MAKEINTRESOURCE( IDI_CONNECT2_ON ) ); for ( i=0; i<MAX_ICONS; i++ ) { if ( !g_hIcons[i] ) return false; } // get the font feight hDC = GetWindowDC( NULL ); int nHeight = -MulDiv( VXCONSOLE_FONTSIZE, GetDeviceCaps( hDC, LOGPIXELSY ), 72 ); ReleaseDC( NULL, hDC ); // create fixed font memset( &lf, 0, sizeof( LOGFONT ) ); lf.lfHeight = nHeight; lf.lfWeight = 400; strcpy( lf.lfFaceName, VXCONSOLE_FONT ); g_hFixedFont = CreateFontIndirect( &lf ); if ( !g_hFixedFont ) return false; // create proportional font memset( &lf, 0, sizeof( LOGFONT ) ); lf.lfHeight = -11; lf.lfWeight = 400; strcpy( lf.lfFaceName, "Tahoma" ); g_hProportionalFont = CreateFontIndirect( &lf ); if ( !g_hProportionalFont ) return false; // get the font metrics hDC = GetWindowDC( NULL ); hFontOld = ( HFONT )SelectObject( hDC, g_hFixedFont ); GetTextMetrics( hDC, &g_fixedFontMetrics ); SelectObject( hDC, hFontOld ); ReleaseDC( NULL, hDC ); return true; } //----------------------------------------------------------------------------- // Shutdown // // Free all resources //----------------------------------------------------------------------------- void Shutdown() { BugReporter_FreeInterfaces(); if ( g_PrintQueue.bInit ) { DeleteCriticalSection( &g_PrintQueue.CriticalSection ); g_PrintQueue.bInit = false; } if ( g_hCommandReadyEvent ) { CloseHandle( g_hCommandReadyEvent ); g_hCommandReadyEvent = 0; } if ( g_hRichEdit ) { FreeLibrary( g_hRichEdit ); g_hRichEdit = 0; } if ( g_hBackgroundBrush ) { DeleteObject( g_hBackgroundBrush ); g_hBackgroundBrush = 0; } if ( g_hFixedFont ) { DeleteObject( g_hFixedFont ); g_hFixedFont = 0; } if ( g_hProportionalFont ) { DeleteObject( g_hProportionalFont ); g_hProportionalFont = 0; } } //----------------------------------------------------------------------------- // Startup // //----------------------------------------------------------------------------- bool Startup() { // Load the xenon debugging dll (xbdm.dll) manually due to its absence from system path const char *pPath = getenv( "path" ); const char *pXEDKDir = getenv( "xedk" ); if ( !pXEDKDir ) pXEDKDir = ""; int len = strlen( pPath ) + strlen( pXEDKDir ) + 256; char *pNewPath = (char*)_alloca( len ); sprintf( pNewPath, "path=%s;%s\\bin\\win32", pPath, pXEDKDir ); _putenv( pNewPath ); HMODULE hXBDM = LoadLibrary( TEXT( "xbdm.dll" ) ); if ( !hXBDM ) { if ( pXEDKDir[0] ) Sys_Error( "Couldn't load xbdm.dll" ); else Sys_Error( "Couldn't load xbdm.dll\nXEDK environment variable not set." ); } LoadConfig(); if ( !CreateResources() ) return false; // set up our print queue InitializeCriticalSection( &g_PrintQueue.CriticalSection ); g_PrintQueue.bInit = true; // manual reset, initially signaled g_hCommandReadyEvent = CreateEvent( NULL, TRUE, TRUE, NULL ); // set up our window class WNDCLASS wndclass; memset( &wndclass, 0, sizeof( wndclass ) ); wndclass.style = 0; wndclass.lpfnWndProc = Main_DlgProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = VXCONSOLE_WINDOWBYTES; wndclass.hInstance = g_hInstance; wndclass.hIcon = g_hIcons[ICON_DISCONNECTED]; wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); wndclass.hbrBackground = g_hBackgroundBrush; wndclass.lpszMenuName = MAKEINTRESOURCE( MENU_VXCONSOLE ); wndclass.lpszClassName = VXCONSOLE_CLASSNAME; if ( !RegisterClass( &wndclass ) ) return false; g_hAccel = LoadAccelerators( g_hInstance, MAKEINTRESOURCE( IDR_MAIN_ACCEL ) ); if ( !g_hAccel ) return false; // Create our main dialog g_hDlgMain = CreateDialog( g_hInstance, MAKEINTRESOURCE( IDD_VXCONSOLE ), 0, NULL ); if ( !g_hDlgMain ) return false; SetWindowLong( g_hDlgMain, VXCONSOLE_CONFIGID, g_configID ); if ( !BugDlg_Init() ) return false; if ( !CpuProfile_Init() ) return false; if ( !TexProfile_Init() ) return false; if ( !MemProfile_Init() ) return false; if ( !Bindings_Init() ) return false; if ( !SyncFilesDlg_Init() ) return false; if ( !ShowMaterials_Init() ) return false; if ( !ShowTextures_Init() ) return false; if ( !ShowSounds_Init() ) return false; if ( !ShowMemDump_Init() ) return false; if ( !TimeStampLog_Init() ) return false; if ( !ExcludePathsDlg_Init() ) return false; g_hwndOutputWindow = GetDlgItem( g_hDlgMain, IDC_OUTPUT ); g_hwndCommandCombo = GetDlgItem( g_hDlgMain, IDC_COMMAND ); CreateCommandHint(); // subclass our dropdown command listbox to handle return key g_hwndCommandSubclassed = SubclassWindow( GetWindow( g_hwndCommandCombo, GW_CHILD ), CommandWindow_SubclassedProc ); // Change the font type of the output window to courier CHARFORMAT cf; cf.cbSize = sizeof( CHARFORMAT ); SendMessage( g_hwndOutputWindow, EM_GETCHARFORMAT, 0, ( LPARAM )&cf ); cf.dwMask &= ~CFM_COLOR; cf.yHeight = VXCONSOLE_FONTSIZE*20; lstrcpyA( cf.szFaceName, VXCONSOLE_FONT ); SendMessage( g_hwndOutputWindow, EM_SETCHARFORMAT, SCF_ALL, ( LPARAM )&cf ); SendMessage( g_hwndOutputWindow, EM_SETBKGNDCOLOR, 0, g_backgroundColor ); // ensure the output window adheres to its z ordering LONG style = GetWindowLong( g_hwndOutputWindow, GWL_STYLE ); style |= WS_CLIPSIBLINGS; SetWindowLong( g_hwndOutputWindow, GWL_STYLE, style ); // change the font of the command and its hint window to courier SendDlgItemMessage( g_hDlgMain, IDC_COMMAND, WM_SETFONT, ( WPARAM )g_hFixedFont, TRUE ); // set the window title SetMainWindowTitle(); ConsoleWindowPrintf( XBX_CLR_DEFAULT, "VXConsole %s [%s Build: %s %s] [Protocol: %d]\n", VXCONSOLE_VERSION, VXCONSOLE_BUILDTYPE, __DATE__, __TIME__, VXCONSOLE_PROTOCOL_VERSION ); ConsoleWindowPrintf( XBX_CLR_DEFAULT, "type '*help' for list of commands...\n\n" ); g_currentIcon = -1; SetConnectionIcon( ICON_DISCONNECTED ); if ( g_alwaysAutoConnect) { // user wants to auto-connect at startup lc_autoConnect( 0, NULL ); } if ( g_mainWindowRect.right && g_mainWindowRect.bottom ) MoveWindow( g_hDlgMain, g_mainWindowRect.left, g_mainWindowRect.top, g_mainWindowRect.right-g_mainWindowRect.left, g_mainWindowRect.bottom-g_mainWindowRect.top, FALSE ); // ready for display int cmdShow = SW_SHOWNORMAL; if ( g_startMinimized ) cmdShow = SW_SHOWMINIMIZED; ShowWindow( g_hDlgMain, cmdShow ); // success return true; } //----------------------------------------------------------------------------- // WinMain // // Entry point for program //----------------------------------------------------------------------------- int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow ) { bool error = true; MSG msg = {0}; g_hInstance = hInstance; g_bSuppressBlink = ParseCommandLineArg( pCmdLine, "-noblink", NULL, 0 ); // optional -config <ID> can specify a specific configuration char buff[128]; buff[0] = '\0'; ParseCommandLineArg( pCmdLine, "-config ", buff, sizeof( buff ) ); g_configID = atoi( buff ); MakeConfigString( VXCONSOLE_REGISTRY, g_configID, buff, sizeof( buff ) ); Sys_SetRegistryPrefix( buff ); HWND hwnd = FindWindow( VXCONSOLE_CLASSNAME, NULL ); if ( hwnd && GetWindowLong( hwnd, VXCONSOLE_CONFIGID ) == g_configID ) { // single instance only // bring other version to foreground if ( IsIconic( hwnd ) ) ShowWindow( hwnd, SW_RESTORE ); SetForegroundWindow( hwnd ); return ( FALSE ); } if ( !Startup() ) goto cleanUp; // message pump while ( GetMessage( &msg, NULL, 0, 0 ) ) { if ( !TranslateAccelerator( g_hDlgMain, g_hAccel, &msg ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } } // no-error, end of app error = false; cleanUp: if ( error ) { char str[255]; FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), 0, str, 255, NULL ); MessageBox( NULL, str, NULL, MB_OK ); } Shutdown(); return ( msg.wParam ); }