Game Development Community

Sending console commands via stdin (C#)

by Peter Simard · in Torque Game Engine · 02/19/2008 (6:23 pm) · 7 replies

I am currently developing a TGE server manager in C# and am experiencing a problem. I have successfully started the zones, and can read the console output via stdout fine. When I attempt to send back commands to the servers via stdin I get no response. I am relatively sure I have the process defined correctly as stdout works fine. Here is the code I am using:

StreamWriter npin = selectedServer.process.StandardInput;            
            npin.WriteLine(textBox1.Text + "\r");

Judging from the console c++ files, \r is the correct line terminator. I have also tried other combinations. No matter what I send via stdin I see no results from the console. So am I missing something obvious?

#1
02/21/2008 (4:19 am)
Why do you want to use Stdin for TGE's (custom) console? Sorry if I'm totally misunderstanding your intentions, but wouldn't you just do Con::evaluate () just like TGE's console does when you type something into it?
#2
02/21/2008 (5:08 am)
I need to issue commands from a separate process. I am not entering the commands directly into TGE. So the problem is getting the text to TGE for it to do the eval on.
#3
02/21/2008 (9:43 am)
I'm not sure I fully understand. Are you connected to the TCP/IP administration console on the TGE server and trying to use stdin to output back over this connection?
#4
02/21/2008 (10:15 am)
I understand that, but why don't you just send a string to TGE (from your Server Manager, I suppose you're connected via a socket somehow?) and let TGE do the evaluate.
#5
02/21/2008 (10:19 am)
I had to hack support for this into Windows dedicated server. We use it for testing.

I added these two functions:
static bool isRedirected(HANDLE stdHandle)
{
   DWORD type = GetFileType(stdHandle);
   if (type == FILE_TYPE_DISK || type == FILE_TYPE_PIPE)
      return true;
   return false;
}

void redirectStdio()
{
   FILE* fp;
   HANDLE stdHandle;
   int conHandle;
   
   CONSOLE_SCREEN_BUFFER_INFO coninfo;

   GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
   coninfo.dwSize.Y = 2048;
   SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);

   stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
   conHandle = _open_osfhandle((long)stdHandle, _O_TEXT);
   if (conHandle != -1)
   {
      fp = _fdopen(conHandle, "w");
      *stdout = *fp;
      setvbuf(stdout, NULL, _IONBF, 0);
   }

   stdHandle = GetStdHandle(STD_INPUT_HANDLE);
   conHandle = _open_osfhandle((long)stdHandle, _O_TEXT);
   if (conHandle != -1)
   {
      fp = _fdopen(conHandle, "r");
      *stdin = *fp;
      setvbuf(stdin, NULL, _IONBF, 0);
   }

   stdHandle = GetStdHandle(STD_ERROR_HANDLE);
   conHandle = _open_osfhandle((long)stdHandle, _O_TEXT);
   if (conHandle != -1)
   {
      fp = _fdopen(conHandle, "w");
      *stderr = *fp;
      setvbuf(stderr, NULL, _IONBF, 0);
   }
}

and changed WinConsole::process:
void WinConsole::process()
{
   if (!WindowsConsole)
      return;
   
   char outbuf[512];
   S32 outpos = 0;

   INPUT_RECORD rec[20];
   DWORD i;
   DWORD numEvents = 0;
   DWORD availBytes = 0;
   
   if (gStdInRedir)
   {
      DWORD type = GetFileType(stdIn);

      if (type == FILE_TYPE_PIPE)
      {
         if (!PeekNamedPipe(stdIn, NULL, 0, NULL, &availBytes, NULL))
            return;
      }
      
      if (type == FILE_TYPE_DISK || availBytes > 0)
      {
         if (!ReadFile(stdIn, stdInBuf, 512, &numEvents, NULL))
            printf("error reading stdIn: %d\n", GetLastError());
         else
         {
            bool newLine = false;
            int i = 0;
            while (i < numEvents)
            {
               if (stdInBuf[i] == '\r' || stdInBuf[i] == '\n')
                  newLine = true;
               WindowsConsole->processKey(outbuf, outpos, stdInBuf[i]);
               if (newLine)
               {
                  while ((i < numEvents) && (stdInBuf[i] == '\r' || stdInBuf[i] == '\n'))
                     i++;
                  newLine = false;
               }
               else
                  i++;
            }
         }
      }
   }

   // if "PeekConsoleInput" fails, it probably means that stdIn is not a console
   else if (winConsoleEnabled && PeekConsoleInput(stdIn, rec, 20, &numEvents))
   {
      if (numEvents)
      {
         ReadConsoleInput(stdIn, rec, 20, &numEvents);

         for (i = 0; i < numEvents; i++)
         {
            if (rec[i].EventType == KEY_EVENT)
            {
               KEY_EVENT_RECORD* ke = &(rec[i].Event.KeyEvent);
               if (ke->bKeyDown)
               {
                  if (ke->uChar.AsciiChar == 0)
                     WindowsConsole->processVirtualKey(outbuf, outpos, ke);
                  else
                     WindowsConsole->processKey(outbuf, outpos, ke);
               }
            }
         }
      }
   }
            
   if (outpos)
   {
      outbuf[outpos] = 0;
      printf("%s", outbuf);
   }
}

I also added:
static void initConsole(bool allocConsole)
{
   // this has to happen straight off, otherwise the handles
   // get invalidated by the other init code
   HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
   gStdInRedir = isRedirected(stdIn);
   HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   gStdOutRedir = isRedirected(stdOut);

   gInteractive = !gStdInRedir && !gStdOutRedir;

   if (allocConsole)
      AllocConsole();
   else if (!gStdInRedir || !gStdOutRedir)
   {
      AttachConProc func =(AttachConProc)GetProcAddress(GetModuleHandle("kernel32.dll"), "AttachConsole");
      if (func != NULL)
        func(ATTACH_PARENT_PROCESS);
   }

   if (gStdInRedir || !allocConsole)
      SetStdHandle(STD_INPUT_HANDLE, stdIn);
   if (gStdOutRedir || !allocConsole)
      SetStdHandle(STD_OUTPUT_HANDLE, stdOut);

   redirectStdio();
}

which is called from WinMain.
#6
02/21/2008 (10:48 am)
Thank you Tim! I appreciate you posting your code here. I am getting lots of compile errors when I drop in the code. I was able to fix most of them by including the proper headers and defining some global variables. However it seems there are two functions missing:

WindowsConsole->processVirtualKey(outbuf, outpos, ke);
                     WindowsConsole->processKey(outbuf, outpos, ke);

Are those custom functions you added to WindowsConsole? The only other error is with:

AttachConProc func =(AttachConProc)GetProcAddress(GetModuleHandle("kernel32.dll"), "AttachConsole");
      if (func != NULL)
        func(ATTACH_PARENT_PROCESS);

It seems AttachConProc is undefined as well as ATTACH_PARENT_PROCESS. Looking on MSDN ATTACH_PARENT_PROCESS should be defined in Wincon.h, but including it doesn't fix it.

Thanks for all the assistance.
#7
02/21/2008 (11:37 am)
Our code is pretty changed from stock Torque 1.3.

I was pretty much providing it as a starting point...

Here's AttachConProc:
typedef BOOL (WINAPI *AttachConProc)(DWORD);

Here's the other two WindowsConsole functions... I may have added these or, more likely, refactored from existing code:
void WinConsole::processKey(char* outbuf, S32& outpos, char c, bool shiftPressed)
{
   switch (c)
   {
      case '\b':
      {
         if(inpos > 0)
         {
            outbuf[outpos++] = '\b';
            outbuf[outpos++] = ' ';
            outbuf[outpos++] = '\b';
            inpos--;
         }
      }
      break;

      case '\t':
      {
         // In the output buffer, we're going to have to erase the current line (in case
         // we're cycling through various completions) and write out the whole input
         // buffer, so (inpos * 3) + complen <= 512.  Should be OK.  The input buffer is
         // also 512 chars long so that constraint will also be fine for the input buffer.
         {
            // Erase the current line.
            U32 i;
            for (i = 0; i < inpos; i++)
            {
               outbuf[outpos++] = '\b';
               outbuf[outpos++] = ' ';
               outbuf[outpos++] = '\b';
            }
            
            // Modify the input buffer with the completion.
            U32 maxlen = 512 - (inpos * 3);
            inpos = Con::tabComplete(inbuf, inpos, maxlen, !shiftPressed);
            
            // Copy the input buffer to the output.
            for (i = 0; i < inpos; i++)
            {
               outbuf[outpos++] = inbuf[i];
            }
         }
      }
      break;
      
      case '\n':
      case '\r':
      {
         outbuf[outpos++] = '\r';
         outbuf[outpos++] = '\n';

         inbuf[inpos] = 0;
         outbuf[outpos] = 0;
         if (interactive)
            printf("%s", outbuf);

         S32 eventSize;
         eventSize = ConsoleEventHeaderSize;
             
         dStrcpy(postEvent.data, inbuf);
         postEvent.size = eventSize + dStrlen(inbuf) + 1;
         GameInterface::getInterface()->postEvent(postEvent);

         // If we've gone off the end of our array, wrap
         // back to the beginning
         if (iCmdIndex >= MAX_CMDS)
             iCmdIndex %= MAX_CMDS;

         // Put the new command into the array
         strcpy(rgCmds[iCmdIndex ++], inbuf);
         if (interactive)
            printf("%s", Con::getVariable("Con::Prompt"));
         inpos = outpos = 0;
      }
      break;
      
      default:
      {
         inbuf[inpos++] = c;
         outbuf[outpos++] = c;
      }
      break;
   }
}

void WinConsole::processVirtualKey(char* outbuf, S32& outpos, KEY_EVENT_RECORD* ke)
{
   switch (ke->wVirtualKeyCode)
   {
      // UP ARROW
      case 0x26 :
      {
         // Go to the previous command in the cyclic array
         if ((-- iCmdIndex) < 0)
            iCmdIndex = MAX_CMDS - 1;

         // If this command isn't empty ...
         if (rgCmds[iCmdIndex][0] != '[[60c1cad9aee7d]]')
         {
            // Obliterate current displayed text
            for (S32 i = outpos = 0; i < inpos; i ++)
            {
               outbuf[outpos++] = '\b';
               outbuf[outpos++] = ' ';
               outbuf[outpos++] = '\b';
            }

            // Copy command into command and display buffers
            for (inpos = 0; inpos < (S32)strlen(rgCmds[iCmdIndex]); inpos ++, outpos ++)
            {
               outbuf[outpos] = rgCmds[iCmdIndex][inpos];
               inbuf[inpos] = rgCmds[iCmdIndex][inpos];
            }
         }
         // If previous is empty, stay on current command
         else if ((++ iCmdIndex) >= MAX_CMDS)
         {
            iCmdIndex = 0;
         }
      }  
      break;

      // DOWN ARROW
      case 0x28:
      {
         // Go to the next command in the command array, if
         // it isn't empty
         if (rgCmds[iCmdIndex][0] != '[[60c1cad9aee7d]]' && (++ iCmdIndex) >= MAX_CMDS)
             iCmdIndex = 0;

         // Obliterate current displayed text
         for (S32 i = outpos = 0; i < inpos; i ++)
         {
            outbuf[outpos ++] = '\b';
            outbuf[outpos ++] = ' ';
            outbuf[outpos ++] = '\b';
         }

         // Copy command into command and display buffers
         for (inpos = 0; inpos < (S32)strlen(rgCmds[iCmdIndex]); inpos ++, outpos ++)
         {
            outbuf[outpos] = rgCmds[iCmdIndex][inpos];
            inbuf[inpos ] = rgCmds[iCmdIndex][inpos];
         }
      }
      break;

      // LEFT ARROW
      case 0x25:
      break;

      // RIGHT ARROW
      case 0x27:
      break;

      default:
      break;
   }
}


BTW, "AttachConsole" is Windows XP and Windows Vista, only.

From MSDN:
Quote:
To compile an application that uses this function, define _WIN32_WINNT as 0x0501 or later. For more information, see Using the SDK Headers.

http://msdn2.microsoft.com/en-us/library/ms681952.aspx

So, not only do you need to include wincon.h, you probably need to define _WIN32_WINNT to 0x0501.

Hope that helps more.