1. Post #1
    Xentec's Avatar
    October 2007
    77 Posts
    Greetings Facepunch

    I'm writing a cross-plattform server manager application which should start Source servers silently (no window) and capture their output.
    But the problem is that srcds.exe doesn't like it and refuses to cooperate.

    In detail:
    Code:
    //defined in process.h
    ::HANDLE hIn, hOut, hErr;
    ::PROCESS_INFORMATION pid;
    //-------------
    void Process::start(std::string exe) {
        std::wstring uexe;
        utf8::utf8to16(exe.begin(),exe.end(),std::back_inserter(uexe));
    
        ::STARTUPINFO ProcSi;
        ::memset(&ProcSi, 0, sizeof(ProcSi));
        ProcSi.cb = sizeof(ProcSi);
    
        ::SECURITY_ATTRIBUTES ProcSA;
        ProcSA.nLength = sizeof(SECURITY_ATTRIBUTES);
        ProcSA.lpSecurityDescriptor = 0;
        ProcSA.bInheritHandle = 1;
    
        ::HANDLE hInTmp[2], hOutTmp[2], hErrTmp[2];
    
        //::CreatePipe(&hInTmp[1], &hInTmp[0], &ProcSA,0);
        ::CreatePipe(&hOutTmp[0],&hOutTmp[1],&ProcSA,0);
        ::CreatePipe(&hErrTmp[0],&hErrTmp[1],&ProcSA,0);
    
        //::DuplicateHandle(GetCurrentProcess(),hInTmp[0],  GetCurrentProcess(),&hIn,  0,0,DUPLICATE_SAME_ACCESS);
        ::DuplicateHandle(GetCurrentProcess(),hOutTmp[0], GetCurrentProcess(),&hOut, 0,0,DUPLICATE_SAME_ACCESS);
        ::DuplicateHandle(GetCurrentProcess(),hErrTmp[0], GetCurrentProcess(),&hErr, 0,0,DUPLICATE_SAME_ACCESS);
    
        //::CloseHandle(hInTmp[0]);
        ::CloseHandle(hOutTmp[0]);
        ::CloseHandle(hErrTmp[0]);
    
        ProcSi.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
        ProcSi.wShowWindow = SW_HIDE;
    
        //ProcSi.hStdInput = hInTmp[1];
        ProcSi.hStdOutput = hOutTmp[1];
        ProcSi.hStdError = hErrTmp[1];
    
        BOOL e = ::CreateProcess( NULL,   // No module name (use command line)
            const_cast<wchar_t*>(uexe.c_str()),        // Command line
            NULL,                // Process handle not inheritable
            NULL,                // Thread handle not inheritable
            TRUE,                // handle inheritance
            0,                    // No creation flags
            NULL,                // Use parent's environment block
            NULL,                // Use parent's starting directory
            &ProcSi,            // Pointer to STARTUPINFO structure
            &pid );                // Pointer to PROCESS_INFORMATION structure
        DWORD err = ::GetLastError();
    
        //::CloseHandle(hInTmp[1]);
        ::CloseHandle(hOutTmp[1]);
        ::CloseHandle(hErrTmp[1]);
        if(!e && err)
            throw new Exception(__func__,err);
    }
    
    long Process::readStdout(char* data, ulong size) {
    	DWORD read;
    	BOOL e = ReadFile(hOut, data, (DWORD)size, &read, 0);
    	DWORD err = ::GetLastError();
    	if(!e && err)
    		throw new Exception(__func__,err);
    	return (long) read;
    }
    CreateProcess (WinAPI) is configured to redirect stdin, -out and -err of the child process through a pipe to parent and then start srcds.
    Works well until srcds crashes with the error:
    CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents

    I've searched for solutions and one is not to redirect stdin which I tried (as seen in code) but it still won't work. The only way
    without crash is to remove STARTF_USESTDHANDLES flag that also prevents from getting the std output.

    Hiding the srcds window appears also impossible with dwFlags and CreateProcess creation flag parameter.


    So my questions are:

    o Is there a workaround to get srcds.exe console output?
    o Is there also a way to hide it's window properly?
    o And how can I filter these junk bytes which I get by reading the stdout pipe?

    Thanks in advance.

  2. Post #2
    Gold Member
    Jookia's Avatar
    July 2007
    6,768 Posts
    Use a shell script and pipes.
    Reply With Quote Edit / Delete Reply Linux Australia Show Events Agree Agree x 2 (list)

  3. Post #3
    Xentec's Avatar
    October 2007
    77 Posts
    Nope. Already tried with simple redirection to text files.
    Result:
    stdout.txt posted:
    Using breakpad minidump system
    [stopped server here]
    Reference Count for Material __depthwrite00 (-1) != 0
    Reference Count for Material __depthwrite01 (-1) != 0
    Reference Count for Material __depthwrite10 (-1) != 0
    Reference Count for Material __depthwrite11 (-1) != 0
    Reference Count for Material __particlesdepthwrite (1) != 0
    That's all I got.
    As I said, Source hates pipes so I need something other.

  4. Post #4
    Gold Member
    jack5500's Avatar
    November 2007
    125 Posts
    Tryed it in C#, seemed impossible too. :/
    Reply With Quote Edit / Delete Reply Windows 7 Germany Show Events Agree Agree x 2 (list)

  5. Post #5
    burak575's Avatar
    January 2008
    83 Posts
    If srcds.exe is a win32 application it probably creating its own console window by using win32 api which is "AllocConsole" function.
    Then setting the std in out and err to different values which will work with new console.

    So creating the pipes when process is first started maybe taking the pointers which going to be replaced.

    There is lot of ways to accomplish this stuff. I am not investigated exe but, most hardcore way will be injecting dll which finds and patches AllocConsole or even printf function and pipe wherever you want (if they did this way).

    Let me investigate executables for accurate solution.

    Edited:

    Okay first findings,

    srcds.exe is just a placeholder executable which loads the bin/dedicated.dll and informs the steam about start of application.

    dedicated.dll uses AllocConsole, FreeConsole stuff. When AllocConsole success it does some calls.

    So you may try DuplicateHandle after this point. Like using simple random untrustable delays of few second sleep or waiting for it properly.

    If you want to wait properly;

    Program stores its handles for console output and input after AllocConsole in virtual address of 100E4E40 and 100E4E2C in dedicated.dll. If you want to find this address you can create signatures for this function 10009F40.

    But it will be better to attach as debugger to your child process, and just search that signature and create a break point where it calls getstdhandle and use that handle for your dirty jobs.

    Another way is creating a small placeholder asm function or dll that you could inject to process and modify import table of dedicated.dll to route AllocConsole to your function. Which will call original AllocConsole and GetStdHandle and inform your application.

    There could be simple ways, but I like hackish ways :P

    For cross-platform , I don't think they used stuff like these in linux. So I assume regular piping should work.

    If you making this as open source or you willing send the code, I can implement these stuff in it.
    Reply With Quote Edit / Delete Reply Windows 7 Turkey Show Events Informative Informative x 3Friendly Friendly x 1Artistic Artistic x 1 (list)

  6. Post #6
    Xentec's Avatar
    October 2007
    77 Posts
    Many thanks!

    I've made also some progress.
    srcds crash was resolved by letting real stdin to child process through.
    - ProcSi.hStdInput = hInTmp[1];
    + ProcSi.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    And "junk bytes" were removed by using utf16to8 conversion on the stdout pipe.

  7. Post #7
    Gold Member
    deathshead's Avatar
    June 2005
    25 Posts
    stop trying
    Reply With Quote Edit / Delete Reply Windows 7 United States Show Events Dumb Dumb x 20Agree Agree x 1Late Late x 1 (list)

  8. Post #8
    stop posting
    Reply With Quote Edit / Delete Reply Netherlands Show Events Winner Winner x 9Friendly Friendly x 1 (list)

  9. Post #9
    Se1f_Distruct's Avatar
    April 2011
    631 Posts
    Patch the undesired function calls in olly?
    Reply With Quote Edit / Delete Reply United States Show Events Dumb Dumb x 2 (list)

  10. Post #10
    Spirit532's Avatar
    February 2012
    7 Posts
    Reviving a retardedly old thread, because I got the same issue when playing with it.
    Here's the function that does this, in IDA:
    Code:
    .text:10005260                 push    ebp
    .text:10005261                 mov     ebp, esp
    .text:10005263                 mov     eax, 5020h
    .text:10005268                 call    __alloca_probe
    .text:1000526D                 push    ebx
    .text:1000526E                 push    esi
    .text:1000526F                 push    edi
    .text:10005270                 mov     esi, ecx
    .text:10005272                 lea     eax, [ebp-8]
    .text:10005275                 push    eax
    .text:10005276                 push    dword ptr [esi+2020h]
    .text:1000527C                 call    ds:GetNumberOfConsoleInputEvents
    .text:10005282                 test    eax, eax
    .text:10005284                 jz      loc_100053FD
    .text:1000528A                 lea     ebx, [ebx+0]
    .text:10005290
    .text:10005290 loc_10005290:                           ; CODE XREF: .text:100053F7j
    .text:10005290                 cmp     dword ptr [ebp-8], 0
    .text:10005294                 jbe     loc_10005408
    .text:1000529A                 lea     eax, [ebp-4]
    .text:1000529D                 push    eax
    .text:1000529E                 push    400h
    .text:100052A3                 lea     eax, [ebp-5020h]
    .text:100052A9                 push    eax
    .text:100052AA                 push    dword ptr [esi+2020h]
    .text:100052B0                 call    ds:ReadConsoleInputA
    .text:100052B6                 test    eax, eax
    .text:100052B8                 jz      loc_10005413
    .text:100052BE                 mov     eax, [ebp-4]
    .text:100052C1                 test    eax, eax
    .text:100052C3                 jz      loc_10005408
    .text:100052C9                 xor     ebx, ebx
    .text:100052CB                 test    eax, eax
    .text:100052CD                 jle     loc_100053E5
    .text:100052D3                 lea     edi, [ebp-5016h]
    .text:100052D9                 lea     esp, [esp+0]
    Here is the same thing in the original code(sdk 2013) - /dedicated/console/TextConsoleWin32.cpp
    Code:
    char * CTextConsoleWin32::GetLine( void )
    {
        while ( 1 )
        {
            INPUT_RECORD    recs[ 1024 ];
            unsigned long    numread;
            unsigned long    numevents;
    
            if ( !GetNumberOfConsoleInputEvents( hinput, &numevents ) )
            {
                Error("CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents");
    
                return NULL;
            }
    
            if ( numevents <= 0 )
                break;
    
            if ( !ReadConsoleInput( hinput, recs, ARRAYSIZE( recs ), &numread ) )
            {
                Error("CTextConsoleWin32::GetLine: !ReadConsoleInput");
    
                return NULL;
            }
    
            if ( numread == 0 )
                return NULL;
    ...
    }
    For a crude fix, you want to nop this:

    Code:
    .text:1000527C                 call    ds:GetNumberOfConsoleInputEvents
    .text:10005282                 test    eax, eax
    .text:10005284                 jz      loc_100053FD <=== this
    .text:1000528A                 lea     ebx, [ebx+0]
    .text:10005290
    .text:10005290 loc_10005290:                           ; CODE XREF: .text:100053F7j
    .text:10005290                 cmp     dword ptr [ebp-8], 0
    .text:10005294                 jbe     loc_10005408
    .text:1000529A                 lea     eax, [ebp-4]
    .text:1000529D                 push    eax
    .text:1000529E                 push    400h
    .text:100052A3                 lea     eax, [ebp-5020h]
    .text:100052A9                 push    eax
    .text:100052AA                 push    dword ptr [esi+2020h]
    .text:100052B0                 call    ds:ReadConsoleInputA
    .text:100052B6                 test    eax, eax
    .text:100052B8                 jz      loc_10005413 <=== this
    .text:100052BE                 mov     eax, [ebp-4]
    .text:100052C1                 test    eax, eax
    .text:100052C3                 jz      loc_10005408 <=== this
    .text:100052C9                 xor     ebx, ebx
    .text:100052CB                 test    eax, eax
    .text:100052CD                 jle     loc_100053E5 <=== this
    For a less crude fix, do it passively without redirecting stdio.
    Have fun.

    Note: this was taken a few versions of the library ago, the offsets are different. Shouldn't be too hard to find by the error message, it's the first and only xref.
    Reply With Quote Edit / Delete Reply Windows 7 Chrome Belarus Show Events Late Late x 2 (list)

  11. Post #11
    Gold Member
    Megalan's Avatar
    October 2005
    1,139 Posts
    Reviving a retardedly old thread, because I got the same issue when playing with it.
    I'm pretty sure Source still has command line options for redirecting console to another app via passing handles to -HFILE, -HPARENT and -HCHILD parameters. Note that you should supply all 3 or it won't work.

    Reply With Quote Edit / Delete Reply Windows 8.1 Chrome Russian Federation Show Events Winner Winner x 1Informative Informative x 1 (list)