teeworlds_network 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/array.rb +28 -0
- data/lib/bytes.rb +139 -0
- data/lib/chunk.rb +177 -0
- data/lib/config.rb +44 -0
- data/lib/context.rb +29 -0
- data/lib/game_client.rb +196 -0
- data/lib/game_server.rb +122 -0
- data/lib/messages/cl_emoticon.rb +63 -0
- data/lib/messages/cl_say.rb +52 -0
- data/lib/messages/client_info.rb +141 -0
- data/lib/messages/game_info.rb +25 -0
- data/lib/messages/input_timing.rb +48 -0
- data/lib/messages/maplist_entry_add.rb +44 -0
- data/lib/messages/maplist_entry_rem.rb +44 -0
- data/lib/messages/rcon_cmd_add.rb +52 -0
- data/lib/messages/rcon_cmd_rem.rb +44 -0
- data/lib/messages/rcon_line.rb +44 -0
- data/lib/messages/server_info.rb +64 -0
- data/lib/messages/server_settings.rb +23 -0
- data/lib/messages/start_info.rb +129 -0
- data/lib/messages/sv_client_drop.rb +57 -0
- data/lib/models/chat_message.rb +39 -0
- data/lib/models/map.rb +57 -0
- data/lib/models/net_addr.rb +18 -0
- data/lib/models/packet_flags.rb +42 -0
- data/lib/models/player.rb +56 -0
- data/lib/models/token.rb +20 -0
- data/lib/net_base.rb +106 -0
- data/lib/network.rb +148 -0
- data/lib/packer.rb +194 -0
- data/lib/packet.rb +73 -0
- data/lib/snapshot/events/damage.rb +24 -0
- data/lib/snapshot/events/death.rb +20 -0
- data/lib/snapshot/events/explosion.rb +16 -0
- data/lib/snapshot/events/hammer_hit.rb +16 -0
- data/lib/snapshot/events/sound_world.rb +20 -0
- data/lib/snapshot/events/spawn.rb +16 -0
- data/lib/snapshot/items/character.rb +43 -0
- data/lib/snapshot/items/client_info.rb +22 -0
- data/lib/snapshot/items/flag.rb +22 -0
- data/lib/snapshot/items/game_data.rb +22 -0
- data/lib/snapshot/items/game_data_flag.rb +24 -0
- data/lib/snapshot/items/game_data_team.rb +21 -0
- data/lib/snapshot/items/laser.rb +24 -0
- data/lib/snapshot/items/pickup.rb +22 -0
- data/lib/snapshot/items/player_info.rb +22 -0
- data/lib/snapshot/items/player_input.rb +32 -0
- data/lib/snapshot/items/projectile.rb +25 -0
- data/lib/snapshot/items/spectator_info.rb +23 -0
- data/lib/snapshot/items/tune_params.rb +90 -0
- data/lib/snapshot/snap_event_base.rb +14 -0
- data/lib/snapshot/snap_item_base.rb +86 -0
- data/lib/snapshot/unpacker.rb +301 -0
- data/lib/string.rb +81 -0
- data/lib/teeworlds_client.rb +506 -0
- data/lib/teeworlds_network.rb +4 -0
- data/lib/teeworlds_server.rb +363 -0
- data/lib/version.rb +3 -0
- metadata +132 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../network'
|
4
|
+
require_relative '../models/player'
|
5
|
+
require_relative '../packer'
|
6
|
+
|
7
|
+
class ServerInfo
|
8
|
+
attr_accessor :version, :name, :hostname, :map, :gametype, :flags, :num_players, :max_players, :num_clients,
|
9
|
+
:max_clients, :players
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
# short tokenless version
|
13
|
+
@version = GAME_VERSION # '0.7.5'
|
14
|
+
@name = 'unnamed ruby server'
|
15
|
+
@hostname = 'localhost'
|
16
|
+
@map = 'dm1'
|
17
|
+
@gametype = 'dm'
|
18
|
+
@flags = 0
|
19
|
+
@num_players = 1
|
20
|
+
@max_players = MAX_PLAYERS
|
21
|
+
@num_clients = 1
|
22
|
+
@max_clients = MAX_CLIENTS
|
23
|
+
|
24
|
+
# token only
|
25
|
+
@players = [
|
26
|
+
Player.new(
|
27
|
+
id: 0,
|
28
|
+
local: 0,
|
29
|
+
team: 0,
|
30
|
+
name: 'sample player',
|
31
|
+
clan: '',
|
32
|
+
country: -1
|
33
|
+
)
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"version=#{@version} gametype=#{gametype} map=#{map} name=#{name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
# basically to_network
|
42
|
+
# int array the server sends to the client
|
43
|
+
def to_a
|
44
|
+
data = []
|
45
|
+
data = Packer.pack_str(@version) +
|
46
|
+
Packer.pack_str(@name) +
|
47
|
+
Packer.pack_str(@hostname) +
|
48
|
+
Packer.pack_str(@map) +
|
49
|
+
Packer.pack_str(@gametype) +
|
50
|
+
Packer.pack_int(@flags) +
|
51
|
+
Packer.pack_int(@num_players) +
|
52
|
+
Packer.pack_int(@max_players) +
|
53
|
+
Packer.pack_int(@num_clients) +
|
54
|
+
Packer.pack_int(@max_clients)
|
55
|
+
@players.each do |player|
|
56
|
+
data += Packer.pack_str(player.name)
|
57
|
+
data += Packer.pack_str(player.clan)
|
58
|
+
data += Packer.pack_int(player.country)
|
59
|
+
data += Packer.pack_int(player.score)
|
60
|
+
data += Packer.pack_int(0) # TODO: bot and spec flag
|
61
|
+
end
|
62
|
+
data
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ServerSettings
|
4
|
+
def initialize(attr = {})
|
5
|
+
@kick_vote = attr[:kick_vote] || 0
|
6
|
+
@kick_min = attr[:kick_min] || 0
|
7
|
+
@spec_vote = attr[:spec_vote] || 0
|
8
|
+
@team_lock = attr[:team_lock] || 0
|
9
|
+
@team_balance = attr[:team_balance] || 0
|
10
|
+
@player_slots = attr[:player_slots] || 16
|
11
|
+
end
|
12
|
+
|
13
|
+
# basically to_network
|
14
|
+
# int array the server sends to the client
|
15
|
+
def to_a
|
16
|
+
Packer.pack_int(@kick_vote) +
|
17
|
+
Packer.pack_int(@kick_min) +
|
18
|
+
Packer.pack_int(@spec_vote) +
|
19
|
+
Packer.pack_int(@team_lock) +
|
20
|
+
Packer.pack_int(@team_balance) +
|
21
|
+
Packer.pack_int(@player_slots)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../packer'
|
4
|
+
|
5
|
+
# TODO: use this on the client side instead of the other hash
|
6
|
+
|
7
|
+
##
|
8
|
+
# StartInfo
|
9
|
+
#
|
10
|
+
# Client -> Server
|
11
|
+
class StartInfo
|
12
|
+
attr_accessor :name, :clan, :country, :body, :marking, :decoration, :hands, :feet, :eyes,
|
13
|
+
:custom_color_body, :custom_color_marking, :custom_color_decoration,
|
14
|
+
:custom_color_hands, :custom_color_feet, :custom_color_eyes,
|
15
|
+
:color_body, :color_marking, :color_decoration, :color_hands, :color_feet, :color_eyes
|
16
|
+
|
17
|
+
def initialize(hash_or_raw)
|
18
|
+
if hash_or_raw.instance_of?(Hash)
|
19
|
+
init_hash(hash_or_raw)
|
20
|
+
else
|
21
|
+
init_raw(hash_or_raw)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def init_raw(data)
|
26
|
+
u = Unpacker.new(data)
|
27
|
+
@name = u.get_string
|
28
|
+
@clan = u.get_string
|
29
|
+
@country = u.get_int
|
30
|
+
@body = u.get_string
|
31
|
+
@marking = u.get_string
|
32
|
+
@decoration = u.get_string
|
33
|
+
@hands = u.get_string
|
34
|
+
@feet = u.get_string
|
35
|
+
@eyes = u.get_string
|
36
|
+
@custom_color_body = u.get_int
|
37
|
+
@custom_color_marking = u.get_int
|
38
|
+
@custom_color_decoration = u.get_int
|
39
|
+
@custom_color_hands = u.get_int
|
40
|
+
@custom_color_feet = u.get_int
|
41
|
+
@custom_color_eyes = u.get_int
|
42
|
+
@color_body = u.get_int
|
43
|
+
@color_marking = u.get_int
|
44
|
+
@color_decoration = u.get_int
|
45
|
+
@color_hands = u.get_int
|
46
|
+
@color_feet = u.get_int
|
47
|
+
@color_eyes = u.get_int
|
48
|
+
end
|
49
|
+
|
50
|
+
def init_hash(attr)
|
51
|
+
@name = attr[:name] || 'ruby gamer'
|
52
|
+
@clan = attr[:clan] || ''
|
53
|
+
@country = attr[:country] || -1
|
54
|
+
@body = attr[:body] || 'spiky'
|
55
|
+
@marking = attr[:marking] || 'duodonny'
|
56
|
+
@decoration = attr[:decoration] || ''
|
57
|
+
@hands = attr[:hands] || 'standard'
|
58
|
+
@feet = attr[:feet] || 'standard'
|
59
|
+
@eyes = attr[:eyes] || 'standard'
|
60
|
+
@custom_color_body = attr[:custom_color_body] || 0
|
61
|
+
@custom_color_marking = attr[:custom_color_marking] || 0
|
62
|
+
@custom_color_decoration = attr[:custom_color_decoration] || 0
|
63
|
+
@custom_color_hands = attr[:custom_color_hands] || 0
|
64
|
+
@custom_color_feet = attr[:custom_color_feet] || 0
|
65
|
+
@custom_color_eyes = attr[:custom_color_eyes] || 0
|
66
|
+
@color_body = attr[:color_body] || 0
|
67
|
+
@color_marking = attr[:color_marking] || 0
|
68
|
+
@color_decoration = attr[:color_decoration] || 0
|
69
|
+
@color_hands = attr[:color_hands] || 0
|
70
|
+
@color_feet = attr[:color_feet] || 0
|
71
|
+
@color_eyes = attr[:color_eyes] || 0
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_h
|
75
|
+
{
|
76
|
+
name: @name,
|
77
|
+
clan: @clan,
|
78
|
+
country: @country,
|
79
|
+
body: @body,
|
80
|
+
marking: @marking,
|
81
|
+
decoration: @decoration,
|
82
|
+
hands: @hands,
|
83
|
+
feet: @feet,
|
84
|
+
eyes: @eyes,
|
85
|
+
custom_color_body: @custom_color_body,
|
86
|
+
custom_color_marking: @custom_color_marking,
|
87
|
+
custom_color_decoration: @custom_color_decoration,
|
88
|
+
custom_color_hands: @custom_color_hands,
|
89
|
+
custom_color_feet: @custom_color_feet,
|
90
|
+
custom_color_eyes: @custom_color_eyes,
|
91
|
+
color_body: @color_body,
|
92
|
+
color_marking: @color_marking,
|
93
|
+
color_decoration: @color_decoration,
|
94
|
+
color_hands: @color_hands,
|
95
|
+
color_feet: @color_feet,
|
96
|
+
color_eyes: @color_eyes
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
# basically to_network
|
101
|
+
# int array the client sends to the server
|
102
|
+
def to_a
|
103
|
+
Packer.pack_str(@name) +
|
104
|
+
Packer.pack_str(@clan) +
|
105
|
+
Packer.pack_int(@country) +
|
106
|
+
Packer.pack_str(@body) +
|
107
|
+
Packer.pack_str(@marking) +
|
108
|
+
Packer.pack_str(@decoration) +
|
109
|
+
Packer.pack_str(@hands) +
|
110
|
+
Packer.pack_str(@feet) +
|
111
|
+
Packer.pack_str(@eyes) +
|
112
|
+
Packer.pack_int(@custom_color_body) +
|
113
|
+
Packer.pack_int(@custom_color_marking) +
|
114
|
+
Packer.pack_int(@custom_color_decoration) +
|
115
|
+
Packer.pack_int(@custom_color_hands) +
|
116
|
+
Packer.pack_int(@custom_color_feet) +
|
117
|
+
Packer.pack_int(@custom_color_eyes) +
|
118
|
+
Packer.pack_int(@color_body) +
|
119
|
+
Packer.pack_int(@color_marking) +
|
120
|
+
Packer.pack_int(@color_decoration) +
|
121
|
+
Packer.pack_int(@color_hands) +
|
122
|
+
Packer.pack_int(@color_feet) +
|
123
|
+
Packer.pack_int(@color_eyes)
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_s
|
127
|
+
to_h
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../packer'
|
4
|
+
|
5
|
+
##
|
6
|
+
# SvClientDrop
|
7
|
+
#
|
8
|
+
# Server -> Client
|
9
|
+
class SvClientDrop
|
10
|
+
attr_accessor :client_id, :reason, :silent
|
11
|
+
|
12
|
+
def initialize(hash_or_raw)
|
13
|
+
if hash_or_raw.instance_of?(Hash)
|
14
|
+
init_hash(hash_or_raw)
|
15
|
+
else
|
16
|
+
init_raw(hash_or_raw)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def init_raw(data)
|
21
|
+
u = Unpacker.new(data)
|
22
|
+
@client_id = u.get_int
|
23
|
+
@reason = u.get_string
|
24
|
+
@reason = @reason == '' ? nil : @reason
|
25
|
+
@silent = u.get_int
|
26
|
+
end
|
27
|
+
|
28
|
+
def init_hash(attr)
|
29
|
+
@client_id = attr[:client_id] || 0
|
30
|
+
@reason = attr[:reason] == '' ? nil : attr[:reason]
|
31
|
+
@silent = attr[:silent] || false
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_h
|
35
|
+
{
|
36
|
+
client_id: @client_id,
|
37
|
+
reason: @reason,
|
38
|
+
silent: @silent
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def silent?
|
43
|
+
!@silent.zero?
|
44
|
+
end
|
45
|
+
|
46
|
+
# basically to_network
|
47
|
+
# int array the Server sends to the Client
|
48
|
+
def to_a
|
49
|
+
Packer.pack_int(@client_id) +
|
50
|
+
Packer.pack_str(@reason) +
|
51
|
+
Packer.pack_int(@silent)
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
to_h
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ChatMesage
|
4
|
+
attr_reader :mode, :client_id, :target_id, :message, :author
|
5
|
+
|
6
|
+
def initialize(data = {})
|
7
|
+
# @mode
|
8
|
+
# Type: Integer
|
9
|
+
@mode = data[:mode]
|
10
|
+
|
11
|
+
# @client_id
|
12
|
+
# Type: Integer
|
13
|
+
@client_id = data[:client_id]
|
14
|
+
|
15
|
+
# @target_id
|
16
|
+
# Type: Integer
|
17
|
+
@target_id = data[:target_id]
|
18
|
+
|
19
|
+
# @message
|
20
|
+
# Type: String
|
21
|
+
@message = data[:message]
|
22
|
+
|
23
|
+
# @author
|
24
|
+
# Type: Player see player.rb
|
25
|
+
@author = data[:author]
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
# server message
|
30
|
+
return "*** #{@message}" if @client_id == -1
|
31
|
+
|
32
|
+
# player message
|
33
|
+
# should never be from an invalid id
|
34
|
+
# but lets not crash if servers send weird stuff
|
35
|
+
name = ''
|
36
|
+
name = @author.name if @author
|
37
|
+
"#{name}: #{@message}"
|
38
|
+
end
|
39
|
+
end
|
data/lib/models/map.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../bytes'
|
4
|
+
|
5
|
+
class Map
|
6
|
+
attr_reader :name, :crc, :crc_str, :crc_arr, :size, :sha256, :sha256_str, :sha256_arr
|
7
|
+
|
8
|
+
def initialize(attr = {})
|
9
|
+
# map name as String
|
10
|
+
@name = attr[:name]
|
11
|
+
|
12
|
+
# crc hex encoded string (8 characters / 4 bytes)
|
13
|
+
@crc = attr[:crc]
|
14
|
+
raise "Error: map crc invalid type: #{@crc.class}" unless @crc.instance_of?(String)
|
15
|
+
|
16
|
+
unless @crc.match(/[a-fA-F0-9]{8}/) # str encoded hex
|
17
|
+
raise "Error: map crc raw string expects size 8 but got #{@crc.size}"
|
18
|
+
end
|
19
|
+
|
20
|
+
@crc_str = @crc
|
21
|
+
@crc_arr = str_bytes(@crc)
|
22
|
+
@crc = @crc_arr.pack('C*')
|
23
|
+
|
24
|
+
# size has to be a positive Integer
|
25
|
+
@size = attr[:size]
|
26
|
+
|
27
|
+
# sha256 can be:
|
28
|
+
# hex encoded string (64 characters / 32 bytes)
|
29
|
+
# '491af17a510214506270904f147a4c30ae0a85b91bb854395bef8c397fc078c3'
|
30
|
+
#
|
31
|
+
# raw string (32 characters)
|
32
|
+
# array of integers representing the bytes (32 elements)
|
33
|
+
@sha256 = attr[:sha256]
|
34
|
+
|
35
|
+
if @sha256.instance_of?(String)
|
36
|
+
if @sha256.match(/[a-fA-F0-9]{64}/) # str encoded hex
|
37
|
+
@sha256_str = @sha256
|
38
|
+
@sha256_arr = str_bytes(@sha256)
|
39
|
+
@sha256 = @sha256_arr.pack('C*')
|
40
|
+
elsif @sha256.length == 32 # raw byte string
|
41
|
+
@sha256_arr = @sha256
|
42
|
+
@sha256 = @sha256_arr.pack('C*')
|
43
|
+
@sha256_str = str_hex(@sha256).gsub(' ', '')
|
44
|
+
else
|
45
|
+
raise "Error: map sha256 raw string expects size 64 but got #{@sha256.size}"
|
46
|
+
end
|
47
|
+
elsif @sha256.instance_of?(Array) # int byte array
|
48
|
+
raise "Error: map sha256 array expects size 32 but got #{@sha256.size}" if @sha256.size != 32
|
49
|
+
|
50
|
+
@sha256_arr = @sha256
|
51
|
+
@sha256 = @sha256.pack('C*')
|
52
|
+
@sha256_str = @sha256.map { |b| b.to_s(16).rjust(2, '0') }.join
|
53
|
+
else
|
54
|
+
raise "Error: map sha256 invalid type: #{@sha256.class}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NetAddr
|
4
|
+
attr_accessor :ip, :port
|
5
|
+
|
6
|
+
def initialize(ip, port)
|
7
|
+
@ip = ip
|
8
|
+
@port = port
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#{@ip}:#{@port}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def eq(addr)
|
16
|
+
@ip == addr.ip && @port == addr.port
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PacketFlags
|
4
|
+
attr_reader :bits, :hash
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@hash = {}
|
8
|
+
@bits = ''
|
9
|
+
if data.instance_of?(Hash)
|
10
|
+
@bits = parse_hash(data)
|
11
|
+
@hash = data
|
12
|
+
elsif data.instance_of?(String)
|
13
|
+
@hash = parse_bits(data)
|
14
|
+
@bits = data
|
15
|
+
else
|
16
|
+
raise 'Flags have to be hash or string'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_hash(hash)
|
21
|
+
bits = ''
|
22
|
+
bits += hash[:connection] ? '1' : '0'
|
23
|
+
bits += hash[:compressed] ? '1' : '0'
|
24
|
+
bits += hash[:resend] ? '1' : '0'
|
25
|
+
bits += hash[:control] ? '1' : '0'
|
26
|
+
bits
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_bits(four_bit_str)
|
30
|
+
# takes a 4 character string
|
31
|
+
# representing the middle of the first byte sent
|
32
|
+
# in binary representation
|
33
|
+
#
|
34
|
+
# and creates a hash out of it
|
35
|
+
hash = {}
|
36
|
+
hash[:connection] = four_bit_str[0] == '1'
|
37
|
+
hash[:compressed] = four_bit_str[1] == '1'
|
38
|
+
hash[:resend] = four_bit_str[2] == '1'
|
39
|
+
hash[:control] = four_bit_str[3] == '1'
|
40
|
+
hash
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Player
|
4
|
+
attr_accessor :id, :local, :team, :name, :clan, :country, :skin_parts, :skin_custom_colors, :skin_colors, :score
|
5
|
+
|
6
|
+
def initialize(data = {})
|
7
|
+
@id = data[:id] || -1
|
8
|
+
@local = data[:local] == 1
|
9
|
+
@team = data[:team] || 0
|
10
|
+
@name = data[:name] || '(connecting..)'
|
11
|
+
@clan = data[:clan] || ''
|
12
|
+
@country = data[:country] || -1
|
13
|
+
@skin_parts = data[:skin_parts] || Array.new(6, 'standard')
|
14
|
+
@skin_custom_colors = data[:skin_custom_colors] || Array.new(6, 0)
|
15
|
+
@skin_colors = data[:skin_colors] || Array.new(6, 0)
|
16
|
+
|
17
|
+
@score = data[:score] || 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def local?
|
21
|
+
@local
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_start_info(start_info)
|
25
|
+
raise "expected: StartInfo got: #{start_info.class}" unless start_info.instance_of?(StartInfo)
|
26
|
+
|
27
|
+
start_info = start_info.to_h
|
28
|
+
@name = start_info[:name]
|
29
|
+
@clan = start_info[:clan]
|
30
|
+
@country = start_info[:country]
|
31
|
+
@skin_parts = [
|
32
|
+
start_info[:body],
|
33
|
+
start_info[:marking],
|
34
|
+
start_info[:decoration],
|
35
|
+
start_info[:hands],
|
36
|
+
start_info[:feet],
|
37
|
+
start_info[:eyes]
|
38
|
+
]
|
39
|
+
@skin_custom_colors = [
|
40
|
+
start_info[:custom_color_body],
|
41
|
+
start_info[:custom_color_marking],
|
42
|
+
start_info[:custom_color_decoration],
|
43
|
+
start_info[:custom_color_hands],
|
44
|
+
start_info[:custom_color_feet],
|
45
|
+
start_info[:custom_color_eyes]
|
46
|
+
]
|
47
|
+
@skin_colors = [
|
48
|
+
start_info[:color_body],
|
49
|
+
start_info[:color_marking],
|
50
|
+
start_info[:color_decoration],
|
51
|
+
start_info[:color_hands],
|
52
|
+
start_info[:color_feet],
|
53
|
+
start_info[:color_eyes]
|
54
|
+
]
|
55
|
+
end
|
56
|
+
end
|
data/lib/models/token.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../bytes'
|
4
|
+
|
5
|
+
class SecurityToken
|
6
|
+
def self.err_msg(msg, token)
|
7
|
+
hex = ''
|
8
|
+
hex = "hex: #{str_hex(token)}\n" if token.instance_of?(String)
|
9
|
+
"Invalid token! Token should be a human readable hex string!\n" \
|
10
|
+
" Good sample token: aabbccdd\n" \
|
11
|
+
" #{msg}\n" \
|
12
|
+
" token: #{token}:#{token.class}\n" \
|
13
|
+
" #{hex}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.validate(token)
|
17
|
+
raise err_msg("Expected type: String got: #{token.class}", token) unless token.instance_of?(String)
|
18
|
+
raise err_msg("Expected size: 8 got: #{token.size}", token) unless token.size == 8
|
19
|
+
end
|
20
|
+
end
|
data/lib/net_base.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'models/token'
|
4
|
+
|
5
|
+
##
|
6
|
+
# Turns int into network byte
|
7
|
+
#
|
8
|
+
# Takes a NETMSGTYPE_CL_* integer
|
9
|
+
# and returns a byte that can be send over
|
10
|
+
# the network
|
11
|
+
def pack_msg_id(msg_id, options = { system: false })
|
12
|
+
(msg_id << 1) | (options[:system] ? 1 : 0)
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# NetBase
|
17
|
+
#
|
18
|
+
# Lowest network layer logic. Sends packets via udp.
|
19
|
+
# Also adding the teeworlds protocol packet header.
|
20
|
+
class NetBase
|
21
|
+
attr_accessor :ack
|
22
|
+
attr_reader :peer_token
|
23
|
+
|
24
|
+
def initialize(opts = {})
|
25
|
+
@verbose = opts[:verbose] || false
|
26
|
+
@ip = nil
|
27
|
+
@port = nil
|
28
|
+
@s = nil
|
29
|
+
@ack = 0
|
30
|
+
@peer_token = [0xFF, 0xFF, 0xFF, 0xFF].map { |b| b.to_s(16).rjust(2, '0') }.join
|
31
|
+
end
|
32
|
+
|
33
|
+
def bind(socket)
|
34
|
+
@s = socket
|
35
|
+
end
|
36
|
+
|
37
|
+
def connect(socket, ip, port)
|
38
|
+
@s = socket
|
39
|
+
@ip = ip
|
40
|
+
@port = port
|
41
|
+
@ack = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_peer_token(token)
|
45
|
+
SecurityToken.validate(token)
|
46
|
+
@peer_token = token
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Sends a packing setting the proper header for you
|
51
|
+
#
|
52
|
+
# @param payload [Array] The Integer list representing the data after the header
|
53
|
+
# @param opts [Hash] :chunks, :client and packet header flags for more details check the class +PacketFlags+
|
54
|
+
def send_packet(payload, opts = { chunks: 1, client: nil, addr: nil })
|
55
|
+
# unsigned char flags_ack; // 6bit flags, 2bit ack
|
56
|
+
# unsigned char ack; // 8bit ack
|
57
|
+
# unsigned char numchunks; // 8bit chunks
|
58
|
+
# unsigned char token[4]; // 32bit token
|
59
|
+
# // ffffffaa
|
60
|
+
# // aaaaaaaa
|
61
|
+
# // NNNNNNNN
|
62
|
+
# // TTTTTTTT
|
63
|
+
# // TTTTTTTT
|
64
|
+
# // TTTTTTTT
|
65
|
+
# // TTTTTTTT
|
66
|
+
flags_bits = PacketFlags.new(opts).bits
|
67
|
+
ack = @ack
|
68
|
+
ip = @ip
|
69
|
+
port = @port
|
70
|
+
token = @peer_token
|
71
|
+
unless opts[:client].nil?
|
72
|
+
ack = opts[:client].ack
|
73
|
+
ip = opts[:client].addr.ip
|
74
|
+
port = opts[:client].addr.port
|
75
|
+
token = opts[:client].token
|
76
|
+
end
|
77
|
+
unless opts[:addr].nil?
|
78
|
+
ip = opts[:addr].ip
|
79
|
+
port = opts[:addr].port
|
80
|
+
end
|
81
|
+
# unused flags ack num chunks
|
82
|
+
# ff ffff aa aaaa aaaa NNNN NNNN
|
83
|
+
header_bits = "00#{flags_bits}#{ack.to_s(2).rjust(10, '0')}#{opts[:chunks].to_s(2).rjust(8, '0')}"
|
84
|
+
|
85
|
+
header = header_bits.chars.groups_of(8).map do |eight_bits|
|
86
|
+
eight_bits.join.to_i(2)
|
87
|
+
end
|
88
|
+
|
89
|
+
header += str_bytes(token)
|
90
|
+
data = (header + payload).pack('C*')
|
91
|
+
client = opts[:client]
|
92
|
+
if @verbose
|
93
|
+
if client
|
94
|
+
puts "send to #{ip}:#{port} " \
|
95
|
+
"client(id=#{client.id} " \
|
96
|
+
"token=#{client.token} " \
|
97
|
+
"name=#{client.player.name} port=#{client.addr.port})"
|
98
|
+
else
|
99
|
+
puts "send to #{ip}:#{port}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
@s.send(data, 0, ip, port)
|
103
|
+
|
104
|
+
puts Packet.new(data, '>').to_s if @verbose || opts[:test]
|
105
|
+
end
|
106
|
+
end
|