├── .gitattributes ├── .gitignore ├── LICENSE ├── README.txt ├── RemoteGameControllerClient.py ├── RemoteGameControllerServer.py └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kiril Tzvetanov Goguev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | DirectX Game controller Server/Client 2 | 3 | This is a quick project done in the summer of 2014 to play directX games remotely from a diffrent keyboard. 4 | 5 | I created this project as a way to play Tales of Monkey island on a TV by sending commands from a diffrent keyboard 6 | (i.e: one embedded in another laptop). I did this because at the time we did not have a wireless keyboard to attach 7 | to the laptop by the TV. 8 | 9 | To use: start the server on the computer intended to run the game, then start the game. Ensure it is in the foreground. 10 | On the other computer start the client, make sure the ip address in the python file matches the server's ip shown in the 11 | console window. 12 | 13 | You will have to edit the ip adress in the client script and run it through python. 14 | 15 | Requires Python 2.7 16 | 17 | Also included setup utils to create a python exe for the client. 18 | 19 | Kiril Tzvetanov Goguev -------------------------------------------------------------------------------- /RemoteGameControllerClient.py: -------------------------------------------------------------------------------- 1 | # Python Direct X game controller client 2 | # DirectX works with Scancodes for Direct Input 3 | # Sends keys to UDP server running a DirectX game in the foreground 4 | # Quick project test for playing a game running on a laptop connected to a TV with a keyboard from another computer through the Network. 5 | #Tested on Fallout 3, Tales of Monkey Island 6 | 7 | # TODO: Create a UDP broadcast function for auto discovery of clients 8 | # TODO: Try to fix some of the lag, currently holding down the keys for long periods of time creates problems. 9 | # TODO: Simulate Mouse movements from a mouse through networking. 10 | 11 | 12 | import socket,sys 13 | import msvcrt 14 | sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 15 | #TODO Change the hard coded addr to a function that will find the server 16 | server_address =('192.168.2.4',3056) 17 | #sock.connect(server_address) 18 | 19 | 20 | print 'type anything on the keyboard:\n' 21 | while True: 22 | pressedKey = msvcrt.getch() 23 | if pressedKey == 'x': 24 | sys.exit() 25 | else: 26 | print "Key Pressed:" + str(pressedKey) 27 | print ord(pressedKey) 28 | sock.sendto(str(ord(pressedKey)),server_address) -------------------------------------------------------------------------------- /RemoteGameControllerServer.py: -------------------------------------------------------------------------------- 1 | # Python Direct X game controller server 2 | # DirectX works with Scancodes for Direct Input 3 | # Added UDP Networking functionality to existing Keyboard simulataions functions provided here(http://stackoverflow.com/a/23468236) thx /u/hodka! 4 | # Quick project test for playing a game running on a laptop connected to a TV with a keyboard from another computer through the Network. 5 | #Tested on Fallout 3, Tales of Monkey Island 6 | 7 | # TODO: Create a UDP broadcast function for auto discovery of clients 8 | # TODO: Try to fix some of the lag, currently holding down the keys for long periods of time creates problems. 9 | # TODO: Simulate Mouse movements from a mouse through networking. 10 | 11 | 12 | import socket,sys,time,ctypes 13 | Port =3056 14 | 15 | SendInput = ctypes.windll.user32.SendInput 16 | 17 | # C struct redefinitions 18 | PUL = ctypes.POINTER(ctypes.c_ulong) 19 | class KeyBdInput(ctypes.Structure): 20 | _fields_ = [("wVk", ctypes.c_ushort), 21 | ("wScan", ctypes.c_ushort), 22 | ("dwFlags", ctypes.c_ulong), 23 | ("time", ctypes.c_ulong), 24 | ("dwExtraInfo", PUL)] 25 | 26 | class HardwareInput(ctypes.Structure): 27 | _fields_ = [("uMsg", ctypes.c_ulong), 28 | ("wParamL", ctypes.c_short), 29 | ("wParamH", ctypes.c_ushort)] 30 | 31 | class MouseInput(ctypes.Structure): 32 | _fields_ = [("dx", ctypes.c_long), 33 | ("dy", ctypes.c_long), 34 | ("mouseData", ctypes.c_ulong), 35 | ("dwFlags", ctypes.c_ulong), 36 | ("time",ctypes.c_ulong), 37 | ("dwExtraInfo", PUL)] 38 | 39 | class Input_I(ctypes.Union): 40 | _fields_ = [("ki", KeyBdInput), 41 | ("mi", MouseInput), 42 | ("hi", HardwareInput)] 43 | 44 | class Input(ctypes.Structure): 45 | _fields_ = [("type", ctypes.c_ulong), 46 | ("ii", Input_I)] 47 | 48 | #Direct X scancode definitions can be found here: http://www.gamespp.com/directx/directInputKeyboardScanCodes.html 49 | DIK_ESCAPE=0x01 50 | DIK_1=0x02 51 | DIK_2 =0x03 52 | DIK_3 =0x04 53 | DIK_4 =0x05 54 | DIK_5 =0x06 55 | DIK_6 =0x07 56 | DIK_7 =0x08 57 | DIK_8 =0x09 58 | DIK_9 =0x0A 59 | DIK_0 =0x0B 60 | DIK_MINUS =0x0C #/* - on main keyboard */ 61 | DIK_EQUALS =0x0D 62 | DIK_BACK =0x0E #/* backspace */ 63 | DIK_TAB =0x0F 64 | DIK_Q =0x10 65 | DIK_W =0x11 66 | DIK_E =0x12 67 | DIK_R =0x13 68 | DIK_T =0x14 69 | DIK_Y =0x15 70 | DIK_U =0x16 71 | DIK_I =0x17 72 | DIK_O =0x18 73 | DIK_P =0x19 74 | DIK_LBRACKET =0x1A 75 | DIK_RBRACKET =0x1B 76 | DIK_RETURN =0x1C #/* Enter on main keyboard */ 77 | DIK_LCONTROL =0x1D 78 | DIK_A =0x1E 79 | DIK_S =0x1F 80 | DIK_D =0x20 81 | DIK_F =0x21 82 | DIK_G =0x22 83 | DIK_H =0x23 84 | DIK_J =0x24 85 | DIK_K =0x25 86 | DIK_L =0x26 87 | DIK_SEMICOLON =0x27 88 | DIK_APOSTROPHE =0x28 89 | DIK_GRAVE =0x29 #/* accent grave */ 90 | DIK_LSHIFT =0x2A 91 | DIK_BACKSLASH =0x2B 92 | DIK_Z =0x2C 93 | DIK_X =0x2D 94 | DIK_C =0x2E 95 | DIK_V =0x2F 96 | DIK_B =0x30 97 | DIK_N =0x31 98 | DIK_M =0x32 99 | DIK_COMMA =0x33 100 | DIK_PERIOD =0x34 #/* . on main keyboard */ 101 | DIK_SLASH =0x35 #/* / on main keyboard */ 102 | DIK_RSHIFT =0x36 103 | DIK_MULTIPLY =0x37 #/* * on numeric keypad */ 104 | DIK_LMENU =0x38 #/* left Alt */ 105 | DIK_SPACE =0x39 106 | DIK_CAPITAL =0x3A 107 | DIK_F1 =0x3B 108 | DIK_F2 =0x3C 109 | DIK_F3 =0x3D 110 | DIK_F4 =0x3E 111 | DIK_F5 =0x3F 112 | DIK_F6 =0x40 113 | DIK_F7 =0x41 114 | DIK_F8 =0x42 115 | DIK_F9 =0x43 116 | DIK_F10 =0x44 117 | DIK_NUMLOCK =0x45 118 | DIK_SCROLL =0x46 #/* Scroll Lock */ 119 | DIK_NUMPAD7 =0x47 120 | DIK_NUMPAD8 =0x48 121 | DIK_NUMPAD9 =0x49 122 | DIK_SUBTRACT =0x4A #/* - on numeric keypad */ 123 | DIK_NUMPAD4 =0x4B 124 | DIK_NUMPAD5 =0x4C 125 | DIK_NUMPAD6 =0x4D 126 | DIK_ADD =0x4E #/* + on numeric keypad */ 127 | DIK_NUMPAD1 =0x4F 128 | DIK_NUMPAD2 =0x50 129 | DIK_NUMPAD3 =0x51 130 | DIK_NUMPAD0 =0x52 131 | DIK_DECIMAL =0x53 #/* . on numeric keypad */ 132 | DIK_F11 =0x57 133 | DIK_F12 =0x58 134 | RUN_W=0x2A 135 | RUN_S=0x2A 136 | RUN_A=0x2A 137 | RUN_D=0x2A 138 | 139 | #Mapping keyboardCommands to specific keys for use with Tales of Monkey Island... 140 | #Probably a really bad way of doing this as it is specific for one game perhaps a 141 | #better solution is to capture the DirectX window game and load a xml config for each one? 142 | def convertKBCommand(input): 143 | print input 144 | if input == '119': 145 | return 0x11 146 | elif input== '115': 147 | return 0x1F 148 | elif input == '97': 149 | return 0x1E 150 | elif input == '100': 151 | return 0x20 152 | elif input == '105': 153 | return 0x17 154 | elif input == '13': 155 | return 0x1C 156 | elif input == '32': 157 | return 0x39 158 | elif input == '27': 159 | return 0x01 160 | elif input == '87': 161 | return RUN_W 162 | elif input == '83': 163 | return RUN_S 164 | elif input=='65': 165 | return RUN_A 166 | elif input=='68': 167 | return RUN_D 168 | else: 169 | return 0x53 170 | 171 | 172 | 173 | # Functions for pressing and releaseing the keys 174 | 175 | def PressKey(hexKeyCode): 176 | extra = ctypes.c_ulong(0) 177 | ii_ = Input_I() 178 | ii_.ki = KeyBdInput( 0, hexKeyCode, 0x0008, 0, ctypes.pointer(extra) ) 179 | x = Input( ctypes.c_ulong(1), ii_ ) 180 | ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 181 | 182 | def ReleaseKey(hexKeyCode): 183 | extra = ctypes.c_ulong(0) 184 | ii_ = Input_I() 185 | ii_.ki = KeyBdInput( 0, hexKeyCode, 0x0008 | 0x0002, 0, ctypes.pointer(extra) ) 186 | x = Input( ctypes.c_ulong(1), ii_ ) 187 | ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 188 | 189 | #Starts the UDP server & will send keys into the game once a client is connected. 190 | def start_server(): 191 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 192 | IP=getAddrIP(); 193 | sock.bind((IP,3056)) 194 | 195 | print 'started on',IP,'with Port #', Port 196 | print 'now run the Directx game and make sure the game is in the foreground'; 197 | print 198 | while True: 199 | result,addr=sock.recvfrom(3) 200 | print 'client sent',result 201 | convertedKey=convertKBCommand(result) 202 | print convertedKey 203 | 204 | #For simulating running by Left Shift + WSAD keys 205 | if convertedKey ==RUN_W: 206 | PressKey(DIK_LSHIFT) 207 | time.sleep(0.001) 208 | PressKey(DIK_W) 209 | elif convertedKey == RUN_S: 210 | PressKey(DIK_LSHIFT) 211 | time.sleep(0.001) 212 | PressKey(DIK_S) 213 | elif convertedKey == RUN_A: 214 | PressKey(DIK_LSHIFT) 215 | time.sleep(0.001) 216 | PressKey(DIK_A) 217 | elif convertedKey== RUN_D: 218 | PressKey(DIK_LSHIFT) 219 | time.sleep(0.001) 220 | PressKey(DIK_D) 221 | else: 222 | PressKey(convertedKey) 223 | time.sleep(0.35) 224 | ReleaseKey(convertedKey) 225 | 226 | def getAddrIP(): 227 | return socket.gethostbyname(socket.getfqdn()); 228 | 229 | 230 | if __name__=='__main__': 231 | start_server() -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | import py2exe 3 | 4 | setup(console=['RemoteGameControllerClient.py']) --------------------------------------------------------------------------------