Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I programmatically launch my C console game in ConHost without requiring user configuration or admin privileges?

I’ve developed a console game using C, and one of the main features is that it runs in the ConHost console because it gives me much more control over the console's appearance (fonts, colors, window size, etc.). However, I’m running into a problem: if I share the game with a friend, they have to manually configure the console to use ConHost since Windows now defaults to using the Windows Terminal.

There are a few workarounds that I’ve seen:

  • Running the program as an administrator, which forces ConHost to open, but I feel uncomfortable asking my friends to run an unknown program with admin rights—it doesn’t seem safe.
  • Using a .bat file to launch the game, which can force the program to run in ConHost, but this feels like more of a hack than a solution.

I’ve tried several methods to make my game run directly in ConHost, such as:

  • Running the game as an administrator, which automatically launches ConHost, but I don’t feel comfortable asking users to grant admin privileges to an unknown program.
  • Using a .bat file to launch the game in ConHost, which works, but it’s not an ideal solution since it adds an extra step for the user.
  • I’ve also tried using AllocConsole() to create a new console, but this doesn’t seem to force ConHost to be used, and instead defaults to Windows Terminal. What I expect is a way to launch my console game directly in ConHost when the user runs the executable without needing to modify any console settings, use admin privileges, or rely on additional scripts like a .bat file.

Ideally, I’d like my game to open in ConHost programmatically when the user runs it, without any need for manual configuration, admin privileges, or having to rely on a separate script. Is there a way to accomplish this?

like image 808
BlackRyu Avatar asked Dec 01 '25 19:12

BlackRyu


1 Answers

I finally found the solution to the problem a while ago! To get the conhost console instead of the Windows Terminal, I found two alternatives:

Solution 1: Use a Windows Application to Open Conhost

You only need this solution if you specifically want to open conhost and no other console environment or process. If that's the case, don't even bother reading solution 2, as it's unnecessarily complex for this problem (unless, of course, you want to experience the demons I was battling back then).

Forget entirely about making a console application (I stubbornly focused on creating a console app and didn’t consider this possibility at all). Instead, create a Windows Application—it won't have a graphical interface nor a console, but your process will run in the background. All you need to do is allocate a console to it, and voilà, Windows will open conhost instead of the Windows Terminal, since conhost is the default for this kind of application. This is very useful!

Here’s the code to do exactly that:

    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, int nCmdShow) {  
    
        AllocConsole();
        
        freopen("CONIN$", "r", stdin);
        freopen("CONOUT$", "w", stdout);
        freopen("CONOUT$", "w", stderr);
    }

That's it, it's that simple! If this ever stops working, you'll have to resort to solution 2, and trust me, it’s a lot more painful than this.

If you're developing a game, there is some useful code on my GitHub repository, along with the game itself if you'd like to play it.

Solution 2: Creating a Child Process to Open Conhost

So, if solution 1 doesn't work for you and you're here, I’m sorry. This is the alternative approach. You need to start by creating a console application, then through this parent console application, you’ll create a child process using the Windows API to open your app/game in a child console process. This will ensure you’re using conhost programmatically.

It’s very important that you pass a command line argument while creating the new process so that it knows it’s already the child process and doesn't end up in a recursive loop (where one process keeps creating another). This way, it will open itself in this new console.

Once that’s done, you need to kill the parent console, and then you’ll have your app running on the desired console environment.

This might be useful if you want your game/program to open in a specific console environment, even if it’s not conhost.

Here’s some code that may be helpful. The original working code is lost because, unfortunately, I didn’t commit it (I thought I did). So, this might or might not work, but it should give you the main ideas:

    
    int main(int argc, char* argv[]) {
        wchar_t executable[MAX_PATH];
        GetModuleFileNameW(NULL, executable, MAX_PATH);
        const wchar_t* args = L"started"; // Arguments you may pass to tell the new process that it is the child, its indeferent what you pass as long as you pass something
        
        // Verify if the archive exists
        if (PathFileExistsW(executable)) {
             // If the argument is present (argc>1), do not execute the game again
            if (argc == 1)
            {
                
                // Calls the function to create the process    
                CreateProcessWithPath2(executable, args);
                CloseOriginalConsole2(); //kills parent process
                exit(0);
            }
        }
        else {
           wprintf(L"Archive isnt in specified route: %s\n", executable);
        }
    }

Here is the dough. This is primarily where errors might occur since I can’t check if the code works or not. However, I still want to post it because I know that with a little bit of adaptation, this approach will work, and someone might find it useful:

    void CreateProcessWithPath2(const wchar_t* executable, const wchar_t* args) {
         // Generate pipes
         HANDLE hChildStdInRead, hChildStdInWrite;
         HANDLE hChildStdOutRead, hChildStdOutWrite;
         SECURITY_ATTRIBUTES saAttr;
         saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
         saAttr.bInheritHandle = TRUE;  // Handles can be hereditary to the child
         saAttr.lpSecurityDescriptor = NULL;
         
         // Create output pipe
         if (!CreatePipe(&hChildStdOutRead, &hChildStdOutWrite, &saAttr, 0)) {
             printf("Error creating output pipe: %d\n", GetLastError());
             return;
         }
         // Create input pipe
         if (!CreatePipe(&hChildStdInRead, &hChildStdInWrite, &saAttr, 0)) {
             printf("Error creating input pipe: %d\n", GetLastError());
             return;
         }
         
         // Configure structure of STARTUPINFO
         STARTUPINFO si;
         ZeroMemory(&si, sizeof(si));
         si.cb = sizeof(si);
         si.dwFlags = STARTF_USESTDHANDLES;
         si.hStdInput = hChildStdInRead;  // Redirect stdin
         si.hStdOutput = hChildStdOutWrite;  // Redirect stdout
         si.hStdError = hChildStdOutWrite;  // Redirect stderr if you also wish to
         
         // Process info
         PROCESS_INFORMATION pi;
         ZeroMemory(&pi, sizeof(pi));
         
         
         wchar_t commandLine[512];
         if (args) {
             wsprintf(commandLine, L"conhost.exe cmd /C \"%s\" %s", executable, args);
         }
         else {
         
         }
         
         // Create process
         if (!CreateProcessW(
         NULL,               // Name of the executable (NULL since we usecommandLine)
         commandLine,        // Comand line, useful since we want to open in conhost and pass a command line argument 
         NULL,               // Child process
         NULL,               // Childe thread
         TRUE,              // Dont inherit handle
         CREATE_NEW_CONSOLE, // Create new console
         NULL,               // Environment
         NULL,               // Work directory
         &si,           // Process configuration
         &pi            // Process information
         )) {
             
             WaitForSingleObject(pi.hProcess, INFINITE);
             
             // Read from output pipe
             DWORD bytesRead;
             CHAR buffer[4096];
             while (ReadFile(hChildStdOutRead, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) {
             buffer[bytesRead] = '\0';
             printf("%s", buffer);  // Show ouptput on windows console
         }
         
         // Close hanldes
         CloseHandle(pi.hProcess);
         CloseHandle(pi.hThread);
         CloseHandle(hChildStdInRead);
         CloseHandle(hChildStdInWrite);
         CloseHandle(hChildStdOutRead);
         CloseHandle(hChildStdOutWrite);
         }
         else {
         // If the process fails
         printf("CreateProcess failed. Error: %d\n", GetLastError());
         }
     }
     
     
     
     
 void CloseOriginalConsole2() {
     DWORD dwPID;
     GetWindowThreadProcessId(GetConsoleWindow(), &dwPID); // Obtain PID of the console
     
     // Obtain process list to find conhost.exe
     HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
     if (hSnap == INVALID_HANDLE_VALUE) return;
     
     PROCESSENTRY32 pe;
     pe.dwSize = sizeof(PROCESSENTRY32);
     
     if (Process32First(hSnap, &pe)) {
         do {
             if (pe.th32ParentProcessID == dwPID) { // Find child process of the console
             HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID);
             if (hProcess) {
                 TerminateProcess(hProcess, 0);
                 CloseHandle(hProcess);
             }
         }
     } while (Process32Next(hSnap, &pe));
     }
     
     CloseHandle(hSnap);
     
     // Now we close the process of the original console
     HANDLE hConsole = OpenProcess(PROCESS_TERMINATE, FALSE, dwPID);
     if (hConsole) {
         TerminateProcess(hConsole, 0);
         CloseHandle(hConsole);
     }
 }

This version of CreateProcessWithPath is simpler, you have less control, but it can def work for you:

void CreateProcessWithPath(const wchar_t* executable, const wchar_t* args) {
 
 STARTUPINFO si;
 PROCESS_INFORMATION pi;
 
 ZeroMemory(&si, sizeof(si));
 si.cb = sizeof(si);
 ZeroMemory(&pi, sizeof(pi));
 
 // Construct comand line
 wchar_t commandLine[512];
 if (args) {
     wsprintf(commandLine, L"conhost.exe cmd /C \"%s\" %s", executable, args);
 }
 else {
       }
 
 // Close proces
 if (!CreateProcessW(
     NULL,               // Name of the executable (NULL since we usecommandLine)
     commandLine,        // Comand line, useful since we want to open in conhost and pass a command line argument 
     NULL,               // Child process
     NULL,               // Childe thread
     TRUE,              // Dont inherit handle
     CREATE_NEW_CONSOLE, // Create new console
     NULL,               // Environment
     NULL,               // Work directory
     &si,           // Process configuration
     &pi            // Process information
     ) {
     wprintf(L"CreateProcess failed (%d).\n", GetLastError());
     return;
 }
 
 // Close handles
 CloseHandle(pi.hProcess);
 CloseHandle(pi.hThread);
 
}
like image 104
BlackRyu Avatar answered Dec 04 '25 07:12

BlackRyu



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!