Log in

View Full Version : AHK network boxing



Narcoz
02-24-2008, 01:00 PM
EDIT: This is now correctly posted ('http://www.dual-boxing.com/forums/index.php?page=Thread&threadID=4717') under software tools. Go there ('http://www.dual-boxing.com/forums/index.php?page=Thread&threadID=4717') to get the most up to date version of these scripts.



Hello everyone!

This post is about using AutoHotKey to remotely control a PC on your network. (My scripts included below.)

(warning for large post, and that my English is not very good.)

I'm a completely new boxer and I just made a 5-char WoW setup using 2 computers.

I however wanted to share with you my custom AHK script source that i use to send keystrokes to all instances of WoW across the network.

Right now my setup looks like this:

Computer#1:
1 WoW running (my main char)
My "server_sender.ahk" script (which i compiled for ease of use)


Computer#2:
4 WoWs running (my alts)
My "client_receiver.ahk" script.

As it works now i when i press keys 0..9 and a..z on the Main computer (computer#1), that keystroke is passed to every window on the Alt computer (computer#2) that has the text "World of Warcraft" in the window-title.

To start it up i recommend this sequence:

1. Start up all WoWs and log them in to the char screen.
2. Start up the server script (computer#1) "server_sender.ahk". [Small window shows up.]
3. Start up the client script (computer#2) "client_receiver.ahk". [Window with an edit-field and connect-button shows up.]
4. Enter the local ip-address of the Main (computer#1) into the client on the Alt computer (computer#2) and press the <connect> key.
5. Enter WoW and start configuring your ingame key-mappings and macros.

NOTE1: To "pause" the server to not send keystrokes to client (like when you want to chat with people on your main) press the <pause>-key on your keyboard. Press it again to toggle it back.

NOTE2: I found the AHK-socket-network code on the AHK-forums. I found the original WoW-multiplexer code on these forums. Im not taking credit for other peoples work. What I did was simply to combine those two ideas into one (imho) more useful one. :)

Known Issues: If you mash buttons really really fast, sometimes keys are not transmitted or (in rare cases) the wrong button is sent over. I will look into that, but so far these scripts work so well for me that i chose to release them as they are. Use them as beta-version. This way all users can use them and even post updates on the forums. open source software.

Usual software disclaimer: I don't take any responsibility. Read the code yourself. By using these scripts you agree that nothing that happens to your computer, lawn mover, car, cat, wife and any other things as a result of running my scripts are my fault, and that I cannot be held responsible for anything either directly or indirectly. <- hate to write this as it's so obvious but better safe than sorry.


/Narcoz - 5 druid team

Narcoz
02-24-2008, 01:01 PM
--------SERVER SCRIPT FOLLOWS (server_sender.ahk) ------------

; -------------------------------------------------
; ------------SERVERSCRIPT------------------
; -------------------------------------------------
; CONFIGURATION SECTION:

; Specify Your own Network's address and port.
;Network_Address = 127.0.0.1
Network_Address = 0.0.0.0
Network_Port = 8765
; ----------------------------
; END OF CONFIGURATION SECTION
; ----------------------------


killbroadcast = 0
KEY_0=0
KEY_1=1
KEY_2=2
KEY_3=3
KEY_4=4
KEY_5=5
KEY_6=6
KEY_7=7
KEY_8=8
KEY_9=9
KEY_A=10
KEY_B=11
KEY_C=12
KEY_D=13
KEY_E=14
KEY_F=15
KEY_G=16
KEY_H=17
KEY_I=18
KEY_J=19
KEY_K=20
KEY_L=21
KEY_M=22
KEY_N=23
KEY_O=24
KEY_P=25
KEY_Q=26
KEY_R=27
KEY_S=28
KEY_T=29
KEY_U=30
KEY_V=31
KEY_W=32
KEY_X=33
KEY_Y=34
KEY_Z=35


conectioncheck=0

;Gui, Add, Text,, Send:
;Gui, Add, Edit, w100 vSendText
;Gui, Add, Text,, Repeat:
;Gui, Add, Edit, w40 vRepeat, 5
;Gui, Add, Text,, Delay (ms):
;Gui, Add, Edit, w40 vDelay, 50
;Gui, Add, Button, gSendviaNet, Send
Gui, Add, Text,, Key sender server (transmits wow keystrokes to client):
Gui, Show

Gosub Connection_Init
return

Connection_Init:
OnExit, ExitSub ; For connection cleanup purposes.

; set up a very basic server:
socket := PrepareForIncomingConnection(Network_Address, Network_Port)
if socket = -1 ; Connection failed (it already displayed the reason).
ExitApp

; Find this script's main window:
Process, Exist ; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts).
DetectHiddenWindows On
ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel)
DetectHiddenWindows Off

; Set up the connection to notify this script via message whenever new data has arrived.
; This avoids the need to poll the connection and thus cuts down on resource usage.
FD_READ = 1 ; Received when data is available to be read.
;FD_WRITE =
FD_CLOSE = 32 ; Received when connection has been closed.
FD_CONNECT = 20 ; Received when connection has been made.
if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_CLOSE|FD_CONNECT)
{
MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
ExitApp
}

Loop ; Wait for incomming connections
{
; accept requests that are in the pipeline of the socket
conectioncheck := DllCall("Ws2_32\accept", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
; Ws2_22/accept returns the new Connection-Socket if a connection request was in the pipeline
; on failure it returns an negative value
if conectioncheck > 1
{
MsgBox Incoming connection accepted
break
}
sleep 500 ; wait half 1 second then accept again
}
return

;SendviaNet:
;Gui, Submit, NoHide
;SendData(conectioncheck,SendText,Repeat,Delay)
;SentText =
;return

PrepareForIncomingConnection(IPAddress, Port)
; This can connect to most types of TCP servers, not just Network.
; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success.
{
VarSetCapacity(wsaData, 32) ; The struct is only about 14 in size, so 32 is conservative.
result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002)
; Since WSAStartup() will likely be the first Winsock function called by this script,
; check ErrorLevel to see if the OS has Winsock 2.0 available:
if ErrorLevel
{
MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
return -1
}
if result ; Non-zero, which means it failed (most Winsock functions return 0 upon success).
{
MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
return -1
}

AF_INET = 2
SOCK_STREAM = 1
IPPROTO_TCP = 6
socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
if socket = -1
{
MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
return -1
}

; Prepare for connection:
SizeOfSocketAddress = 16
VarSetCapacity(SocketAddress, SizeOfSocketAddress)
InsertInteger(2, SocketAddress, 0, AF_INET) ; sin_family
InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2) ; sin_port
InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4) ; sin_addr.s_addr

; Bind to socket:
if DllCall("Ws2_32\bind", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
{
MsgBox % "bind() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
return -1
}
if DllCall("Ws2_32\listen", "UInt", socket, "UInt", "SOMAXCONN")
{
MsgBox % "LISTEN() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
return -1
}

return socket ; Indicate success by returning a valid socket ID rather than -1.
}

SendData(wParam,SendData, Repeat, Delay)
{
socket := wParam
; SendDataSize := VarSetCapacity(SendData)
; SendDataSize += 1
Loop % Repeat
{
; SendIt := SendData . "(" . A_Index . ")"
; SendDataSize := VarSetCapacity(SendIt)
; SendDataSize += 1

; MsgBox sending %SendIt% to the socket

; sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendIt, "Int", SendDatasize, "Int", 0)
; sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendIt, "Int", strlen(SendIt), "Int", 0)
sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendData, "Int", strlen(SendData), "Int", 0)
WinsockError := DllCall("Ws2_32\WSAGetLastError")
if WinsockError <> 0 ; WSAECONNRESET, which happens when Network closes via system shutdown/logoff.
; Since it's an unexpected error, report it. Also exit to avoid infinite loop.
MsgBox % "send() indicated Winsock error " . WinsockError
sleep,Delay
}
;send( sockConnected,> welcome, strlen(welcome) + 1, NULL);
}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity. To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data.
DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}


~Pause::
KeyWait, Pause, D
If(%killbroadcast% = 0)
{
killbroadcast = 1
}
else
{
killbroadcast = 0
;WinGet, targetWindowIds, List, %windowTitle%
}
Return


~0::
KeyWait, 0
SendKey(KEY_0)
Return

~1::
KeyWait, 1
SendKey(KEY_1)
Return

~2::
KeyWait, 2
SendKey(KEY_2)
Return

~3::
KeyWait, 3
SendKey(KEY_3)
Return

~4::
KeyWait, 4
SendKey(KEY_4)
Return

~5::
KeyWait, 5
SendKey(KEY_5)
Return

~6::
KeyWait, 0
SendKey(KEY_6)
Return

~7::
KeyWait, 7
SendKey(KEY_7)
Return

~8::
KeyWait, 8
SendKey(KEY_8 )
Return

~9::
KeyWait, 9
SendKey(KEY_9)
Return

~A::
KeyWait, A
SendKey(KEY_A)
Return

~B::
KeyWait, B
SendKey(KEY_B)
Return

~C::
KeyWait, C
SendKey(KEY_C)
Return

~D::
KeyWait, D
SendKey(KEY_D)
Return

~E::
KeyWait, E
SendKey(KEY_E)
Return

~F::
KeyWait, F
SendKey(KEY_F)
Return

~G::
KeyWait, G
SendKey(KEY_G)
Return

~H::
KeyWait, H
SendKey(KEY_H)
Return

~I::
KeyWait, I
SendKey(KEY_I)
Return

~J::
KeyWait, J
SendKey(KEY_J)
Return

~K::
KeyWait, K
SendKey(KEY_K)
Return

~L::
KeyWait, L
SendKey(KEY_L)
Return

~M::
KeyWait, M
SendKey(KEY_M)
Return

~N::
KeyWait, N
SendKey(KEY_N)
Return

~O::
KeyWait, O
SendKey(KEY_O)
Return

~P::
KeyWait, P
SendKey(KEY_P)
Return

~Q::
KeyWait, Q
SendKey(KEY_Q)
Return

~R::
KeyWait, R
SendKey(KEY_R)
Return

~S::
KeyWait, S
SendKey(KEY_S)
Return

~T::
KeyWait, T
SendKey(KEY_T)
Return

~U::
KeyWait, U
SendKey(KEY_U)
Return

~V::
KeyWait, V
SendKey(KEY_V)
Return

~W::
KeyWait, W
SendKey(KEY_W)
Return

~X::
KeyWait, X
SendKey(KEY_X)
Return

~Y::
KeyWait, Y
SendKey(KEY_Y)
Return

~Z::
KeyWait, Z
SendKey(KEY_Z)
Return


SendKey(key)
{
global killbroadcast
global conectioncheck
If(%killbroadcast% = 0)
{
killbroadcast = 1 ; stop other threads from sending right now!
if conectioncheck > 1 ; don't send before a client has connected.
{
SendData(conectioncheck,key,1,1)
}
killbroadcast = 0 ; Reactivate sending of keys.
}
}


guiclose:
ExitSub: ; This subroutine is called automatically when the script exits for any reason.
; MSDN: "Any sockets open when WSACleanup is called are reset and automatically
; deallocated as if closesocket was called."
DllCall("Ws2_32\WSACleanup")
ExitApp

-----END OF SERVER SCRIPT --------------

Narcoz
02-24-2008, 01:04 PM
----- CLIENT SCRIPT FOLLOWS (client_receiver.ahk) -------


; -------------------------------------------------
;-----------CLIENTSCRIPT----------------------
; -------------------------------------------------
; CONFIGURATION SECTION:

; Specify address and port of the server.
Network_Address = 127.0.0.1
Network_Port = 8765
windowTitle=Warcraft
; ----------------------------
; END OF CONFIGURATION SECTION
; ----------------------------


KEY_0=0
KEY_1=1
KEY_2=2
KEY_3=3
KEY_4=4
KEY_5=5
KEY_6=6
KEY_7=7
KEY_8=8
KEY_9=9
KEY_A=10
KEY_B=11
KEY_C=12
KEY_D=13
KEY_E=14
KEY_F=15
KEY_G=16
KEY_H=17
KEY_I=18
KEY_J=19
KEY_K=20
KEY_L=21
KEY_M=22
KEY_N=23
KEY_O=24
KEY_P=25
KEY_Q=26
KEY_R=27
KEY_S=28
KEY_T=29
KEY_U=30
KEY_V=31
KEY_W=32
KEY_X=33
KEY_Y=34
KEY_Z=35


SetTitleMatchMode 2 ; match anywhere in the title

WinGet, targetWindowIds, list, %windowTitle%


Gui, Add, Button, gConnection_Init, Connect
Gui, Add, Edit, w150 vServerIp
;Gui, Add, Button, gClear, Clear
Gui, Add, Text,, Key reciever client (transfers keystrokes from the server to the client(s)):
Gui, Show

LinesReceived:=0
return

Connection_Init:
OnExit, ExitSub ; For connection cleanup purposes.

; Connect to any type of server:
Gui, Submit, NoHide
;socket := ConnectToAddress(Network_Address, Network_Port)
socket := ConnectToAddress(ServerIp, Network_Port)
if socket = -1 ; Connection failed (it already displayed the reason).
ExitApp

; Find this script's main window:
Process, Exist ; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts).
DetectHiddenWindows On
ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel)
DetectHiddenWindows Off

; When the OS notifies the script that there is incoming data waiting to be received,
; the following causes a function to be launched to read the data:
NotificationMsg = 0x5555 ; An arbitrary message number, but should be greater than 0x1000.
OnMessage(NotificationMsg, "ReceiveData")

; Set up the connection to notify this script via message whenever new data has arrived.
; This avoids the need to poll the connection and thus cuts down on resource usage.
FD_READ = 1 ; Received when data is available to be read.
FD_CLOSE = 32 ; Received when connection has been closed.
if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_READ|FD_CLOSE)
{
MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
ExitApp
}
return

;Clear:
;ShowReceived=
;LinesReceived:=0
;GuiControl,, MyEdit,
;return

ConnectToAddress(IPAddress, Port)
; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success.
{
VarSetCapacity(wsaData, 32) ; The struct is only about 14 in size, so 32 is conservative.
result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002)
; Since WSAStartup() will likely be the first Winsock function called by this script,
; check ErrorLevel to see if the OS has Winsock 2.0 available:
if ErrorLevel
{
MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
return -1
}
if result ; Non-zero, which means it failed (most Winsock functions return 0 upon success).
{
MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
return -1
}

AF_INET = 2
SOCK_STREAM = 1
IPPROTO_TCP = 6
socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
if socket = -1
{
MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
return -1
}

; Prepare for connection:
SizeOfSocketAddress = 16
VarSetCapacity(SocketAddress, SizeOfSocketAddress)
InsertInteger(2, SocketAddress, 0, AF_INET) ; sin_family
InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2) ; sin_port
InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4) ; sin_addr.s_addr

; Attempt connection:
if DllCall("Ws2_32\connect", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
{
MsgBox % "connect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
return -1
}
return socket ; Indicate success by returning a valid socket ID rather than -1.
}

ReceiveData(wParam, lParam)
; By means of OnMessage(), this function has been set up to be called automatically whenever new data
; arrives on the connection.
{
Critical
global ShowReceived
global MyEdit
global LinesReceived

socket := wParam
ReceivedDataSize = 4096 ; Large in case a lot of data gets buffered due to delay in processing previous data.
VarSetCapacity(ReceivedData, ReceivedDataSize, 0) ; 0 for last param terminates string for use with recv().
Data := ""
Loop ; This loop solves the issue of the notification message being discarded due to thread-already-running.
{
ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", socket, "Str", ReceivedData, "Int", ReceivedDataSize, "Int", 0)
if ReceivedDataLength = 0 ; The connection was gracefully closed,
ExitApp ; The OnExit routine will call WSACleanup() for us.
if ReceivedDataLength = -1
{
WinsockError := DllCall("Ws2_32\WSAGetLastError")
if ( WinsockError = 10035 ) { ; WSAEWOULDBLOCK, which means "no more data to be read".
break
}
if WinsockError <> 10054 ; WSAECONNRESET, which happens when Network closes via system shutdown/logoff.
; Since it's an unexpected error, report it. Also exit to avoid infinite loop.
MsgBox % "recv() indicated Winsock error " . WinsockError
ExitApp ; The OnExit routine will call WSACleanup() for us.
}
Data .= ReceivedData
}
; Otherwise, process the data received.
Loop, parse, Data, `n, `r
{
;LinesReceived++
;if (LinesReceived = 1) {
; ShowReceived = %LinesReceived%: %A_LoopField%
;} else {
; ShowReceived = %ShowReceived%`n%LinesReceived%: %A_LoopField%
;}
;Tooltip % ShowReceived
;GuiControl,, MyEdit, %ShowReceived%
PressKey(A_LoopField)
}
return 1 ; Tell the program that no further processing of this message is needed.
}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity. To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data.
DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}

;Send specified key to all instances of the configured program.
PressKey(key)
{
global targetWindowIds
Loop, %targetWindowIds%
{
target_window := targetWindowIds%A_index%
InternalPressKey(key, target_window)
}
}

InternalPressKey(key, target_window)
{
global

if key = %KEY_0%
{
ControlSend,, 0, ahk_id %target_window%
}
else
if key = %KEY_1%
{
ControlSend,, 1, ahk_id %target_window%
}
else
if key = %KEY_2%
{
ControlSend,, 2, ahk_id %target_window%
}
else
if key = %KEY_3%
{
ControlSend,, 3, ahk_id %target_window%
}
else
if key = %KEY_4%
{
ControlSend,, 4, ahk_id %target_window%
}
else
if key = %KEY_5%
{
ControlSend,, 5, ahk_id %target_window%
}
else
if key = %KEY_6%
{
ControlSend,, 6, ahk_id %target_window%
}
else
if key = %KEY_7%
{
ControlSend,, 7, ahk_id %target_window%
}
else
if key = %KEY_8%
{
ControlSend,, 8, ahk_id %target_window%
}
else
if key = %KEY_9%
{
ControlSend,, 9, ahk_id %target_window%
}
else
if key = %KEY_A%
{
ControlSend,, a, ahk_id %target_window%
}
else
if key = %KEY_B%
{
ControlSend,, b, ahk_id %target_window%
}
else
if key = %KEY_C%
{
ControlSend,, c, ahk_id %target_window%
}
else
if key = %KEY_D%
{
ControlSend,, d, ahk_id %target_window%
}
else
if key = %KEY_E%
{
ControlSend,, e, ahk_id %target_window%
}
else
if key = %KEY_F%
{
ControlSend,, f, ahk_id %target_window%
}
else
if key = %KEY_G%
{
ControlSend,, g, ahk_id %target_window%
}
else
if key = %KEY_H%
{
ControlSend,, h, ahk_id %target_window%
}
else
if key = %KEY_I%
{
ControlSend,, i, ahk_id %target_window%
}
else
if key = %KEY_J%
{
ControlSend,, j, ahk_id %target_window%
}
else
if key = %KEY_K%
{
ControlSend,, k, ahk_id %target_window%
}
else
if key = %KEY_L%
{
ControlSend,, l, ahk_id %target_window%
}
else
if key = %KEY_M%
{
ControlSend,, m, ahk_id %target_window%
}
else
if key = %KEY_N%
{
ControlSend,, n, ahk_id %target_window%
}
else
if key = %KEY_O%
{
ControlSend,, o, ahk_id %target_window%
}
else
if key = %KEY_P%
{
ControlSend,, p, ahk_id %target_window%
}
else
if key = %KEY_Q%
{
ControlSend,, q, ahk_id %target_window%
}
else
if key = %KEY_R%
{
ControlSend,, r, ahk_id %target_window%
}
else
if key = %KEY_S%
{
ControlSend,, s, ahk_id %target_window%
}
else
if key = %KEY_T%
{
ControlSend,, t, ahk_id %target_window%
}
else
if key = %KEY_U%
{
ControlSend,, u, ahk_id %target_window%
}
else
if key = %KEY_V%
{
ControlSend,, v, ahk_id %target_window%
}
else
if key = %KEY_W%
{
ControlSend,, w, ahk_id %target_window%
}
else
if key = %KEY_X%
{
ControlSend,, x, ahk_id %target_window%
}
else
if key = %KEY_Y%
{
ControlSend,, y, ahk_id %target_window%
}
else
if key = %KEY_Z%
{
ControlSend,, z, ahk_id %target_window%
}

} ; END OF InternalPressKey()


guiclose:
ExitSub: ; This subroutine is called automatically when the script exits for any reason.
; MSDN: "Any sockets open when WSACleanup is called are reset and automatically
; deallocated as if closesocket was called."
DllCall("Ws2_32\WSACleanup")
ExitApp

----- END OF CLIENT SCRIPT ------

Klamor
02-24-2008, 06:58 PM
ooh, thanks for the help, i was planning on getting a second computer to help me multibox :)