- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 598
- Reaction score
- 7
Bypassing the single-instance limitation in Combat Arms usually boils down to how the game handles its global mutex. For years, the signature identifier has been CA_GAME. If you try to launch a second instance, the client checks if this mutex exists and kills the process if it finds a handle.
I found this implementation using Microsoft Detours to hijack the Windows API call. While the logic below attempts to rename the mutex on the fly, keep in mind that newer builds or alternative versions might be calling the Wide-character variant or checking for other synchronization objects.
Technical Implementation
This snippet hooks CreateMutexA. If the game attempts to create an object named "CA_GAME", the hook intercepts it and swaps the name for something unique, theoretically allowing the second client to initialize its own "NewMutexName" without conflict.
Troubleshooting & Logic Flaws
If you are still getting errors when opening the second client, consider these common roadblocks:
Anyone else noticed if they moved the check to a Semaphore or if they started checking for the process name in the latest patch?
I found this implementation using Microsoft Detours to hijack the Windows API call. While the logic below attempts to rename the mutex on the fly, keep in mind that newer builds or alternative versions might be calling the Wide-character variant or checking for other synchronization objects.
Technical Implementation
This snippet hooks CreateMutexA. If the game attempts to create an object named "CA_GAME", the hook intercepts it and swaps the name for something unique, theoretically allowing the second client to initialize its own "NewMutexName" without conflict.
Code:
#include <windows.h>
#include <detours.h>
#include <iostream>
typedef HANDLE(WINAPI* CreateMutexA_t)(LPSECURITY_ATTRIBUTES, BOOL, LPCSTR);
CreateMutexA_t TrueCreateMutexA = CreateMutexA;
// Hook function
HANDLE WINAPI HookedCreateMutexA(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCSTR lpName) {
if (lpName && strcmp(lpName, "CA_GAME") == 0) {
std::cout << "Old mutex name 'CA_GAME' detected. Changing to 'NewMutexName'." << std::endl;
lpName = "NewMutexName";
}
else {
std::cout << "CreateMutexA called! Mutex name: " << (lpName ? lpName : "No name") << std::endl;
}
return TrueCreateMutexA(lpMutexAttributes, bInitialOwner, lpName);
}
void StartConsole() {
AllocConsole();
FILE* consoleOutput;
freopen_s(&consoleOutput, "CONOUT$", "w", stdout);
std::cout << "Console started." << std::endl;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
if (fdwReason == DLL_PROCESS_ATTACH) {
StartConsole();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)TrueCreateMutexA, HookedCreateMutexA);
DetourTransactionCommit();
}
return TRUE;
}
Troubleshooting & Logic Flaws
If you are still getting errors when opening the second client, consider these common roadblocks:
- Check for CreateMutexW: Many games use the Wide-character version of the API. If the client calls the W variant and you only hooked the A variant, your bypass does nothing.
- OpenMutex: The game might be using OpenMutexA/W to check for the existing instance instead of trying to create a new one and catching the ERROR_ALREADY_EXISTS code.
- Other Objects: Some engines use Semaphores or Events. Check the handle list in Process Explorer to see what else might be globally named.
- Anti-Cheat: If the AC is active, it might be stripping handles or detecting the Detours hook in the IAT/EAT.
If you're running this on a protected server, avoid AllocConsole in your final build as it's an easy flag for many older ACs looking for debug windows. Always ensure you have the latest Detours library linked correctly to avoid entry point errors.
Anyone else noticed if they moved the check to a Semaphore or if they started checking for the process name in the latest patch?