Code:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Addon;
namespace Addon
{
public static class TestClients
{
#region Hooking Functions
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, uint flAllocationType, uint flProtect);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
private const uint MEM_COMMIT = 0x1000;
private const uint MEM_RESERVE = 0x2000;
private const uint PAGE_EXECUTE_READWRITE = 0x40;
private static readonly List<GCHandle> GCHandles = new List<GCHandle>();
private static void PerformJmpHook(IntPtr original, byte[] destinationStub)
{
var stubAddress = VirtualAlloc(IntPtr.Zero, (UIntPtr)destinationStub.Length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
Marshal.Copy(destinationStub, 0, stubAddress, destinationStub.Length);
PerformJmpHook(original, stubAddress);
}
private static void PerformJmpHook(IntPtr original, IntPtr destination)
{
uint oldProtect;
VirtualProtect(original, (UIntPtr)5, PAGE_EXECUTE_READWRITE, out oldProtect);
var hook = new byte[5];
hook[0] = 0xE9;
BitConverter.GetBytes((destination.ToInt32() - original.ToInt32()) - 5).CopyTo(hook, 1);
Marshal.Copy(hook, 0, original, hook.Length);
VirtualProtect(original, (UIntPtr)5, oldProtect, out oldProtect);
}
private static IntPtr GetUnmanagedFunctionPointerFromDelegate(Delegate d)
{
GCHandles.Add(GCHandle.Alloc(d));
return Marshal.GetFunctionPointerForDelegate(d);
}
#endregion
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool SvCheckClientDelegate(IntPtr clientPart);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FuncIntDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private delegate void SvCmdTokenizeStringDelegate(string str);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void SvDirectConnectDelegate(netaddr_t adr);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void SvSendClientGameStateDelegate(IntPtr client);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void SvClientEnterWorldDelegate(IntPtr client, byte[] userCmd);
private static readonly IntPtr SvCheckTimeoutAddress = (IntPtr)0x4FE8A0;
private static readonly IntPtr SteamCheckSvAuthAddress = (IntPtr)0x599D20;
private static readonly IntPtr ResetReliableCmdAddress = (IntPtr)0x5029DB;
private static readonly IntPtr SvMaxClientsDvarPtr = (IntPtr)0x5787780;
private static readonly IntPtr ClientAddress = (IntPtr)0x4A0FE90;
private static readonly IntPtr GetStatMajorAddress = (IntPtr)0x4D0560;
private static readonly IntPtr GetStatMinorAddress = (IntPtr)0x4D05A0;
private static readonly IntPtr GetChecksumAddress = (IntPtr)0x40C8C0;
private static readonly IntPtr SvDirectConnectAddress = (IntPtr)0x4F7670;
private static readonly IntPtr SvCmdArgsAddress = (IntPtr)0x1B5B7D8;
private static readonly FuncIntDelegate GetStatMajor;
private static readonly FuncIntDelegate GetStatMinor;
private static readonly FuncIntDelegate GetChecksum;
private static readonly SvCmdTokenizeStringDelegate SvCmdTokenizeString;
private static readonly SvDirectConnectDelegate SvDirectConnect;
private static readonly SvSendClientGameStateDelegate SvSendClientGameState;
private static readonly SvClientEnterWorldDelegate SvClientEnterWorld;
private static ushort BotPort;
private static byte[] SvCheckTimeoutHookStub = new byte[]
{
0x56, // push esi
0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, <handler>
0xFF, 0xD0, // call eax
0x83, 0xC4, 0x04, // add esp, 4
0x84, 0xC0, // test al, al
0x68, 0xA7, 0xE8, 0x4F, 0x00, // push 4FE8A7h
0xC3, // retn
};
private static byte[] SteamCheckSvAuthHookStub = new byte[]
{
0x56, // push esi
0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, <handler>
0xFF, 0xD0, // call eax
0x83, 0xC4, 0x04, // add esp, 4
0x84, 0xC0, // test al, al
0x75, 0x06, // jnz short continueSteamCheck
0x68, 0xBC, 0x9D, 0x59, 0x00, // push 599DBCh
0xC3, // retn
// continueSteamCheck:
0x68, 0x2D, 0x9D, 0x59, 0x00, // push 599D2Dh
0xC3, // retn
};
private static byte[] ResetReliableCmdHookStub = new byte[]
{
0x80, 0xBE, 0xC0, 0x52, 0x04, 0x00, 0xFF, // cmp byte ptr [esi+452C0h], 0FFh
0x75, 0x0C, // jnz short doReturn
0x8B, 0x86, 0x70, 0x0E, 0x02, 0x00, // mov eax, [esi+20E70h]
0x89, 0x86, 0x74, 0x0E, 0x02, 0x00, // mov [esi+20E74h], eax
// doReturn:
0x83, 0x3D, 0x9C, 0x1F, 0x2F, 0x06, 0x00, // cmp dword ptr [62F1F9C], 0
0x68, 0xE2, 0x29, 0x50, 0x00, // push 5029E2h
0xC3 // retn
};
private static byte[] SvCmdTokenizeStringStub = new byte[]
{
0x55, // push ebp
0x8B, 0xEC, // mov ebp, esp
0x56, // push esi
0x51, // push ecx
0xB8, 0x00, 0x02, 0x00, 0x00, // mov eax, 200
0x2B, 0x05, 0xD0, 0xAF, 0xB4, 0x01, // sub eax, dword ptr [1B4AFD0]
0x50, // push eax
0xFF, 0x75, 0x08, // push [ebp+8]
0xB9, 0xB0, 0x87, 0xB4, 0x01, // mov ecx, 1B487B0
0xBE, 0xD8, 0xB7, 0xB5, 0x01, // mov esi, 1B5B7D8
0xB8, 0x00, 0xC1, 0x4C, 0x00, // mov eax, 4CC100
0xFF, 0xD0, // call eax
0x83, 0xC4, 0x08, // add esp, 8
0x59, // pop ecx
0x5E, // pop esi
0x5D, // pop ebp
0xC3 // retn
};
private static readonly byte[] SvSendClientGameStateStub = new byte[]
{
0x55, // push ebp
0x8B, 0xEC, // mov ebp, esp
0x51, // push ecx
0xB9, 0x40, 0x8D, 0x4F, 0x00, // mov ecx, 4F8D40
0x8B, 0x45, 0x08, // mov eax, [ebp+8]
0xFF, 0xD1, // call ecx
0x59, // pop ecx
0x5D, // pop ebp
0xC3 // retn
};
private static readonly byte[] SvClientEnterWorldStub = new byte[]
{
0x55, // push ebp
0x8B, 0xEC, // mov ebp, esp
0x52, // push edx
0xB8, 0xC0, 0x8F, 0x4F, 0x00, // mov eax, 4F8FC0
0xFF, 0x75, 0x08, // push [ebp+8]
0x8B, 0x55, 0x0C, // mov edx, [ebp+C]
0xFF, 0xD0, // call eax
0x83, 0xC4, 0x04, // add esp, 4
0x5A, // pop edx
0x5D, // pop ebp
0xC3 // retn
};
[StructLayout(LayoutKind.Sequential)]
struct netaddr_t
{
public int type;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] ip;
public ushort port;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public byte[] ipx;
}
static TestClients()
{
// Hook required methods.
var svCheckTimeoutFuncAddress = GetUnmanagedFunctionPointerFromDelegate(
new SvCheckClientDelegate(SvCheckTimeoutFunc));
BitConverter.GetBytes(svCheckTimeoutFuncAddress.ToInt32()).CopyTo(SvCheckTimeoutHookStub, 2);
PerformJmpHook(SvCheckTimeoutAddress, SvCheckTimeoutHookStub);
var steamCheckSvAuthFuncAddress = GetUnmanagedFunctionPointerFromDelegate(
new SvCheckClientDelegate(SteamCheckSvAuthFunc));
BitConverter.GetBytes(steamCheckSvAuthFuncAddress.ToInt32()).CopyTo(SteamCheckSvAuthHookStub, 2);
PerformJmpHook(SteamCheckSvAuthAddress, SteamCheckSvAuthHookStub);
PerformJmpHook(ResetReliableCmdAddress, ResetReliableCmdHookStub);
// Prepare function delegates.
GetStatMajor = (FuncIntDelegate)Marshal.GetDelegateForFunctionPointer(GetStatMajorAddress, typeof(FuncIntDelegate));
GetStatMinor = (FuncIntDelegate)Marshal.GetDelegateForFunctionPointer(GetStatMinorAddress, typeof(FuncIntDelegate));
GetChecksum = (FuncIntDelegate)Marshal.GetDelegateForFunctionPointer(GetChecksumAddress, typeof(FuncIntDelegate));
SvDirectConnect = (SvDirectConnectDelegate)Marshal.GetDelegateForFunctionPointer(SvDirectConnectAddress, typeof(SvDirectConnectDelegate));
var svCmdTokenizeStringAddress = VirtualAlloc(IntPtr.Zero, (UIntPtr)SvCmdTokenizeStringStub.Length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
Marshal.Copy(SvCmdTokenizeStringStub, 0, svCmdTokenizeStringAddress, SvCmdTokenizeStringStub.Length);
SvCmdTokenizeString = (SvCmdTokenizeStringDelegate)Marshal.GetDelegateForFunctionPointer(svCmdTokenizeStringAddress, typeof(SvCmdTokenizeStringDelegate));
var svSendClientGameStateAddress = VirtualAlloc(IntPtr.Zero, (UIntPtr)SvSendClientGameStateStub.Length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
Marshal.Copy(SvSendClientGameStateStub, 0, svSendClientGameStateAddress, SvSendClientGameStateStub.Length);
SvSendClientGameState = (SvSendClientGameStateDelegate)Marshal.GetDelegateForFunctionPointer(svSendClientGameStateAddress, typeof(SvSendClientGameStateDelegate));
var svClientEnterWorldAddress = VirtualAlloc(IntPtr.Zero, (UIntPtr)SvClientEnterWorldStub.Length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
Marshal.Copy(SvClientEnterWorldStub, 0, svClientEnterWorldAddress, SvClientEnterWorldStub.Length);
SvClientEnterWorld = (SvClientEnterWorldDelegate)Marshal.GetDelegateForFunctionPointer(svClientEnterWorldAddress, typeof(SvClientEnterWorldDelegate));
}
private static bool SvCheckTimeoutFunc(IntPtr clientPart)
{
var client = (IntPtr)(clientPart.ToInt32() - 136300);
return Marshal.ReadInt32(client, -136260) != 2 &&
(Marshal.ReadInt32(client) == 1 || Marshal.ReadInt32(client, 283328) != -1);
}
private static bool SteamCheckSvAuthFunc(IntPtr clientPart)
{
var client = (IntPtr)(clientPart.ToInt32() - 283325);
return Marshal.ReadInt32(client) >= 5 && Marshal.ReadInt32(client, 283328) != -1;
}
private static IntPtr GetClientFromNum(int clientNum)
{
return (IntPtr)(ClientAddress.ToInt32() + clientNum * 0x78690);
}
private static void SvCmdEndTokenizedString()
{
var argsIndex = Marshal.ReadInt32(SvCmdArgsAddress);
var addr = (IntPtr)0x1B4AFD0;
var value = Marshal.ReadInt32(addr);
var arrValue = Marshal.ReadInt32((IntPtr)0x1B5B81C, argsIndex * 4);
Marshal.WriteInt32(addr, value - arrValue);
addr = (IntPtr)0x1B4AFD4;
value = Marshal.ReadInt32(addr);
arrValue = Marshal.ReadInt32((IntPtr)0x1B4AFB0, argsIndex * 4);
argsIndex--;
Marshal.WriteInt32(SvCmdArgsAddress, argsIndex);
}
private static void Memset(IntPtr ptr, int value, int length)
{
var b = (byte)value;
for (int i = 0; i < length; i++)
Marshal.WriteByte(ptr, i, b);
}
public static void ConnectBot(ServerClient client)
{
if (!IsBot(client))
return;
ConnectBot(GetClientFromNum(client.ClientNum));
}
private static void ConnectBot(IntPtr clientAddress)
{
// Call SV_SendClientGameState
SvSendClientGameState(clientAddress);
// SV_ClientEnterWorld
SvClientEnterWorld(clientAddress, new byte[44]);
}
public static void RemoveBot(int ClientNum)
{
Marshal.WriteInt32((IntPtr)(0x4A0FE90 + ClientNum * 0x78690), 0);
}
public static bool IsBot(ServerClient client)
{
var clientAddress = GetClientFromNum(client.ClientNum);
return Marshal.ReadInt32(clientAddress, 283328) == -1;
}
public static int AddTestClient()
{
// Read the sv_maxclients dvar value.
var maxClients = Marshal.ReadInt32(Marshal.ReadIntPtr(SvMaxClientsDvarPtr), 0xC);
// Look for a free slot.
int index = 0;
for (index = 0; index < maxClients; index++)
{
if (Marshal.ReadInt32(GetClientFromNum(index)) == 0)
break;
}
// No slots available.
if (index == maxClients)
return -1;
// Prepare connection string.
const int protocol = 20601; // as of 1.9.461
var connectString = string.Format(
"connect bot{0} \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\bot{0}\\protocol\\{1}\\checksum\\{2}\\statver\\{3} {4}\\qport\\{5}\"",
BotPort, protocol, GetChecksum(),
GetStatMajor(), (uint)GetStatMinor(),
BotPort + 1);
// Handle client connection.
var botAdr = new netaddr_t { port = BotPort };
SvCmdTokenizeString(connectString.PadRight(1023, '\0'));
SvDirectConnect(botAdr);
SvCmdEndTokenizedString();
BotPort++;
// Get the bot's client number and client address.
index = 0;
IntPtr botClient = IntPtr.Zero;
for (index = 0; index < maxClients; index++)
{
if (Marshal.ReadInt32(botClient = GetClientFromNum(index)) == 0)
continue;
var clientAdr = (netaddr_t)Marshal.PtrToStructure((IntPtr)(botClient.ToInt32() + 40), typeof(netaddr_t));
if (clientAdr.type == botAdr.type && clientAdr.port == botAdr.port)
break;
}
// Failed for some reason.
if (index == maxClients)
return -1;
// Set the "bot" flag (actually the lower dword of the steam id)
Marshal.WriteInt32(botClient, 283328, -1);
// Set stat flags to "already set"
Marshal.WriteInt16(botClient, 283322, 0x3FF);
Memset((IntPtr)(botClient.ToInt32() + 269497), 0, 0x2FFC);
Memset((IntPtr)(botClient.ToInt32() + 281785), 0, 0x600);
// Connect the bots as spectators.
ConnectBot(botClient);
// Return client num.
return index;
}
}
}
I'm not quite sure if it will work for normal clients, I only tested it with bots.
Try to first kick the bot (ServerCommand("kickclient " + clientnum)) and then TestClient.RemoveBot(clientNum). If there is a better solution (I'm pretty sure there is) please let me know.
, I only added the RemoveBot function.