ChillerDragon
f0762eee9d
But the control flag seems to be working So parse packets with the control flag in the header as control message and all other messages as connection messsages aka CClient::ProcessServerPacket()
317 lines
8.4 KiB
Ruby
Executable file
317 lines
8.4 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
|
|
require 'socket'
|
|
|
|
require 'huffman_tw'
|
|
|
|
require_relative 'lib/string'
|
|
require_relative 'lib/array'
|
|
require_relative 'lib/bytes'
|
|
require_relative 'lib/packet'
|
|
|
|
# randomize this
|
|
MY_TOKEN = [0x73, 0x34, 0xB4, 0xA0]
|
|
|
|
MSG_TOKEN = [0x04, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x05] + MY_TOKEN + Array.new(512, 0x00)
|
|
MSG_INFO = [0x40, 0x19, 0x01, 0x03, 0x30, 0x2E, 0x37, 0x20, 0x38, 0x30, 0x32, 0x66, # @...0.7 802f
|
|
0x31, 0x62, 0x65, 0x36, 0x30, 0x61, 0x30, 0x35, 0x36, 0x36, 0x35, 0x66, # 1be60a05665f
|
|
0x00, 0x00, 0x85, 0x1C]
|
|
MSG_STARTINFO = [0x41, 0x19, 0x03, 0x36, 0x6E, 0x61, 0x6D, 0x65 , 0x6C, 0x65, 0x73, 0x73, # A..6nameless
|
|
0x20, 0x6D, 0x65, 0x00, 0x00, 0x40, 0x67, 0x72 , 0x65, 0x65, 0x6E, 0x73, # me..@greens
|
|
0x77, 0x61, 0x72, 0x64, 0x00, 0x64, 0x75, 0x6F , 0x64, 0x6F, 0x6E, 0x6E, # ward.duodonn
|
|
0x79, 0x00, 0x00, 0x73, 0x74, 0x61, 0x6E, 0x64 , 0x61, 0x72, 0x64, 0x00, # y..standard
|
|
0x73, 0x74, 0x61, 0x6E, 0x64, 0x61, 0x72, 0x64 , 0x00, 0x73, 0x74, 0x61, # standard.sta
|
|
0x6E, 0x64, 0x61, 0x72, 0x64, 0x00, 0x01, 0x01 , 0x00, 0x00, 0x00, 0x00, # ndard.......
|
|
0x80, 0xFC, 0xAF, 0x05, 0xEB, 0x83, 0xD0, 0x0A , 0x80, 0xFE, 0x07, 0x80, # ............
|
|
0xFE, 0x07, 0x80, 0xFE, 0x07, 0x80, 0xFE, 0x07]
|
|
|
|
|
|
NET_CTRLMSG_CONNECT = 0x01
|
|
NET_CTRLMSG_ACCEPT = 0x02
|
|
NET_CTRLMSG_CLOSE = 0x04
|
|
NET_CTRLMSG_TOKEN = 0x05
|
|
|
|
NET_CONNSTATE_OFFLINE = 0
|
|
NET_CONNSTATE_TOKEN = 1
|
|
NET_CONNSTATE_CONNECT = 2
|
|
NET_CONNSTATE_PENDING = 3
|
|
NET_CONNSTATE_ONLINE = 4
|
|
NET_CONNSTATE_ERROR = 5
|
|
|
|
NET_MAX_PACKETSIZE = 1400
|
|
|
|
CONTROL_HEADER_SIZE = 7
|
|
|
|
class ServerInfo
|
|
attr_reader :version, :name, :map, :gametype
|
|
|
|
def initialize(infos)
|
|
@version = infos[0]
|
|
@name = infos[1]
|
|
@map = infos[2]
|
|
@gametype = infos[3]
|
|
end
|
|
|
|
def to_s
|
|
"version=#{@version} gametype=#{gametype} map=#{map} name=#{name}"
|
|
end
|
|
end
|
|
|
|
class TwClient
|
|
attr_reader :state
|
|
|
|
def initialize
|
|
@client_token = MY_TOKEN.map { |b| b.to_s(16) }.join('')
|
|
puts "client token #{@client_token}"
|
|
@s = UDPSocket.new
|
|
@state = NET_CONNSTATE_OFFLINE
|
|
@ip = 'localhost'
|
|
@port = 8303
|
|
@huffman = Huffman.new
|
|
@packet_flags = {}
|
|
end
|
|
|
|
def send_msg(data)
|
|
# size and flags
|
|
header = [0x00, 0x00, 0x01] + str_bytes(@token)
|
|
msg = header + data
|
|
@s.send(msg.pack('C*'), 0, @ip, @port)
|
|
end
|
|
|
|
def send_msg_connect()
|
|
header = [0x04, 0x00, 0x00] + str_bytes(@token)
|
|
msg = header + [NET_CTRLMSG_CONNECT] + str_bytes(@client_token) + Array.new(501, 0x00)
|
|
@s.send(msg.pack('C*'), 0, @ip, @port)
|
|
end
|
|
|
|
def send_ctrl_with_token()
|
|
@state = NET_CONNSTATE_TOKEN
|
|
@s.send(MSG_TOKEN.pack('C*'), 0, @ip, @port)
|
|
end
|
|
|
|
def send_info()
|
|
send_msg(MSG_INFO)
|
|
end
|
|
|
|
def send_msg_startinfo()
|
|
header = [0x00, 0x04, 0x01] + str_bytes(@token)
|
|
msg = header + MSG_STARTINFO
|
|
@s.send(msg.pack('C*'), 0, @ip, @port)
|
|
end
|
|
|
|
def send_msg_ready()
|
|
header = [0x00, 0x01, 0x01] + str_bytes(@token)
|
|
msg = header + [0x40, 0x01, 0x02, 0x25]
|
|
@s.send(msg.pack('C*'), 0, @ip, @port)
|
|
end
|
|
|
|
def send_enter_game()
|
|
header = [0x00, 0x07, 0x01] + str_bytes(@token)
|
|
msg = header + [0x40, 0x01, 0x04, 0x27]
|
|
@s.send(msg.pack('C*'), 0, @ip, @port)
|
|
end
|
|
|
|
def send_input
|
|
header = [0x10, 0x0A, 01] + str_bytes(@token)
|
|
random_compressed_input = [
|
|
0x4D, 0xE9, 0x48, 0x13, 0xD0, 0x0B, 0x6B, 0xFC, 0xB7, 0x2B, 0x6E, 0x00, 0xBA
|
|
]
|
|
# this wont work we need to ack the ticks
|
|
# and then compress it
|
|
# CMsgPacker Msg(NETMSG_INPUT, true);
|
|
# Msg.AddInt(m_AckGameTick);
|
|
# Msg.AddInt(m_PredTick);
|
|
# Msg.AddInt(Size);
|
|
msg = header + random_compressed_input
|
|
@s.send(msg.pack('C*'), 0, @ip, @port)
|
|
end
|
|
|
|
def on_msg_token(data)
|
|
@token = bytes_to_str(data)
|
|
puts "Got token #{@token}"
|
|
send_msg_connect()
|
|
end
|
|
|
|
def on_msg_accept
|
|
puts "got accept. connection online"
|
|
@state = NET_CONNSTATE_ONLINE
|
|
send_info
|
|
end
|
|
|
|
def on_msg_close
|
|
puts "got NET_CTRLMSG_CLOSE"
|
|
end
|
|
|
|
def get_strings(data)
|
|
strings = []
|
|
str = ""
|
|
data.chars.each do |b|
|
|
# use a bunch of control characters as delimiters
|
|
# https://en.wikipedia.org/wiki/Control_character
|
|
if (0x00..0x0F).to_a.include?(b.unpack('C*').first)
|
|
strings.push(str) unless str.length.zero?
|
|
str = ""
|
|
next
|
|
end
|
|
|
|
str += b
|
|
end
|
|
strings
|
|
end
|
|
|
|
def on_msg_map_change(data)
|
|
mapname = get_strings(data).first
|
|
puts "map: #{mapname}"
|
|
send_msg_ready()
|
|
end
|
|
|
|
def connect(ip, port)
|
|
@ip = ip
|
|
@port = port
|
|
puts "connecting to #{@ip}:#{@port} .."
|
|
# s.bind("127.0.0.1", 7878)
|
|
@s.connect(ip, port)
|
|
send_ctrl_with_token
|
|
loop do
|
|
# 10.times do
|
|
tick
|
|
end
|
|
end
|
|
|
|
def on_motd(data)
|
|
puts "motd: #{get_strings(data)}"
|
|
end
|
|
|
|
# wat is dis?
|
|
def on_what(what, data)
|
|
case what
|
|
when '32'
|
|
# hex 32
|
|
on_msg_map_change(data)
|
|
when '01' # dont know what that is but client responds with enter game
|
|
send_enter_game
|
|
when '27' # DM server name and gametype as strings
|
|
@server_info = ServerInfo.new(get_strings(data)[1..])
|
|
puts @server_info
|
|
when '28' # CTF server name and gametype as strings
|
|
@server_info = ServerInfo.new(get_strings(data)[1..])
|
|
puts @server_info
|
|
when '06'
|
|
# $nameless me@greenswardduodonnystandardstandardstandard
|
|
puts get_strings(data)
|
|
when '31'
|
|
# 1ctf]
|
|
when '12'
|
|
# got this when connecting to blchill
|
|
# 3xNPY&@!7usAy?B0{<94\BlmapChill1=L{:'m[Pnb̨Ϧ7https://maps.zillyhuhn.com/BlmapChill_313db8824c7bcc3aa22793dad56d5b50ad6eee629307df01cca896cfa603c137.map@8BlmapChill1=L{:'m[Pnb̨Ϧ7
|
|
send_msg_ready
|
|
when '13'
|
|
# idk some garbage
|
|
else
|
|
puts "Unkown what #{what}"
|
|
puts "hex: #{str_hex(data)}"
|
|
puts "raw: #{data}"
|
|
exit
|
|
end
|
|
end
|
|
|
|
def on_playerinfo(data)
|
|
puts "playerinfo: #{get_strings(data).join(', ')}"
|
|
end
|
|
|
|
# CClient::ProcessServerPacket
|
|
def on_message(msg, data)
|
|
what = get_byte(data)
|
|
puts "msg=#{msg} what=#{what}"
|
|
# data = data[1..]
|
|
case msg
|
|
when '40' then on_what(what, data)
|
|
when 'C0' then on_what(what, data)
|
|
when '52' then puts "got 0x52 is this keep alive idk? ignoring it"
|
|
when 'C1' then on_playerinfo(data)
|
|
when '41' then on_playerinfo(data)
|
|
when '43' then on_motd(data)
|
|
else
|
|
puts "Unkown message #{msg}"
|
|
puts "hex: #{str_hex(data)}"
|
|
puts "raw: #{data}"
|
|
exit (1)
|
|
end
|
|
end
|
|
|
|
# CClient::ProcessConnlessPacket
|
|
def on_ctrl_message(msg, data)
|
|
case msg
|
|
when NET_CTRLMSG_TOKEN then on_msg_token(data)
|
|
when NET_CTRLMSG_ACCEPT then on_msg_accept
|
|
when NET_CTRLMSG_CLOSE then on_msg_close
|
|
else
|
|
puts "Uknown control message #{msg}"
|
|
exit(1)
|
|
end
|
|
end
|
|
|
|
def process_server_packet(data)
|
|
puts "server packet with data:"
|
|
puts str_hex(data)
|
|
end
|
|
|
|
def tick
|
|
# puts "tick"
|
|
begin
|
|
pck = @s.recvfrom_nonblock(1400)
|
|
rescue
|
|
pck = nil
|
|
end
|
|
return unless pck
|
|
|
|
data = pck.first
|
|
|
|
packet = Packet.new(data)
|
|
puts packet.to_s
|
|
gets
|
|
|
|
# process connless packets data
|
|
if packet.flags_control
|
|
msg = data[CONTROL_HEADER_SIZE].unpack("C*").first
|
|
on_ctrl_message(msg, data[(CONTROL_HEADER_SIZE + 1)..])
|
|
else # process non-connless packets
|
|
process_server_packet(data)
|
|
end
|
|
|
|
# # check flags properly instead
|
|
# if get_byte(data, 0) == '00'
|
|
# # parse msg with bit flips instead
|
|
# on_message(msg, data[(header_size + 1)..])
|
|
# elsif get_byte(data, 0) == '10' # size 7 flags compression
|
|
# payload = data[header_size..]
|
|
# # puts "payload compressed: " + str_hex(payload)
|
|
# payload = @huffman.decompress(payload.unpack("C*"))
|
|
# # puts "payload decompressed: " + str_hex(payload.pack("C*"))
|
|
|
|
# # debug this datatype
|
|
# # the byte 0x11 is being sent
|
|
# # the tw server somehow reads 8 as NETMSG_SNAPSINGLE
|
|
# # and ruby gets 17 here which is the decimal of 0x11
|
|
# msg = payload[2]
|
|
# puts "msg=#{msg} msgtype=#{msg.class} payloadtype=#{payload.class}"
|
|
# if @server_info.nil?
|
|
# send_msg_startinfo
|
|
# else # assume snap reply with input to keep alive
|
|
# send_input
|
|
# end
|
|
# else
|
|
# on_ctrl_message(msg.to_i(16), data[(header_size + 1)..])
|
|
# end
|
|
end
|
|
|
|
def disconnect
|
|
@s.close
|
|
end
|
|
end
|
|
|
|
client = TwClient.new
|
|
|
|
client.connect(ARGV[0] || "localhost", ARGV[1] ? ARGV[1].to_i : 8303)
|
|
|