line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
3
|
|
|
3
|
|
149141
|
use strict; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
76
|
|
2
|
3
|
|
|
3
|
|
14
|
use warnings; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
69
|
|
3
|
3
|
|
|
3
|
|
13
|
no warnings 'uninitialized'; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
83
|
|
4
|
3
|
|
|
3
|
|
34
|
use v5.16.0; |
|
3
|
|
|
|
|
9
|
|
5
|
|
|
|
|
|
|
package Game::Tibia::Packet::Login; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
# ABSTRACT: Login packet support for the MMORPG Tibia |
8
|
|
|
|
|
|
|
our $VERSION = '0.006'; # VERSION |
9
|
|
|
|
|
|
|
|
10
|
3
|
|
|
3
|
|
20
|
use Carp; |
|
3
|
|
|
|
|
12
|
|
|
3
|
|
|
|
|
167
|
|
11
|
3
|
|
|
3
|
|
942
|
use File::ShareDir 'dist_file'; |
|
3
|
|
|
|
|
14010
|
|
|
3
|
|
|
|
|
172
|
|
12
|
3
|
|
|
3
|
|
842
|
use Crypt::OpenSSL::RSA; |
|
3
|
|
|
|
|
13875
|
|
|
3
|
|
|
|
|
90
|
|
13
|
3
|
|
|
3
|
|
727
|
use Digest::Adler32; |
|
3
|
|
|
|
|
887
|
|
|
3
|
|
|
|
|
74
|
|
14
|
3
|
|
|
3
|
|
782
|
use Game::Tibia::Packet; |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
97
|
|
15
|
3
|
|
|
3
|
|
19
|
use Scalar::Util qw(blessed); |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
2534
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=pod |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=encoding utf8 |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 NAME |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
Game::Tibia::Packet::Login - Login packet support for the MMORPG Tibia |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 SYNOPSIS |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
use Game::Tibia::Packet::Login; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 DESCRIPTION |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
Decodes Tibia Login packets into hashes and vice versa. By default uses the OTServ RSA key, but allows different RSA keys to be supplied. Version 9.80 and above is not supported. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=cut |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my $otserv = Crypt::OpenSSL::RSA->new_private_key( |
38
|
|
|
|
|
|
|
do { local $/; open my $rsa, dist_file('Game-Tibia-Packet', 'otserv.private') or die "Couldn't open private key $!"; <$rsa>; } |
39
|
|
|
|
|
|
|
); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 METHODS AND ARGUMENTS |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=over 4 |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=item new(version => $version, [$character => undef, packet => $packet, rsa => OTSERV]) |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
Constructs a new C instance of version C<$version>. If C is supplied, decryption using the supplied rsa private key is attempted. If no C is supplied, the OTServ RSA key is used. If a C<$character> name is supplied, it's assumed to be a game server login packet. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=cut |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub new { |
52
|
3
|
|
|
3
|
1
|
276
|
my $class = shift; |
53
|
|
|
|
|
|
|
|
54
|
3
|
|
|
|
|
18
|
my $self = { |
55
|
|
|
|
|
|
|
packet => undef, |
56
|
|
|
|
|
|
|
rsa => $otserv, |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
@_ |
59
|
|
|
|
|
|
|
}; |
60
|
|
|
|
|
|
|
|
61
|
3
|
|
33
|
|
|
12
|
$self->{version} //= $self->{versions}{client}{VERSION}; |
62
|
3
|
50
|
33
|
|
|
24
|
croak 'A protocol version < 9.80 must be supplied' if !defined $self->{version} || $self->{version} >= 980; |
63
|
|
|
|
|
|
|
|
64
|
3
|
|
|
|
|
28
|
$self->{versions}{client} = Game::Tibia::Packet::version($self->{version}); |
65
|
|
|
|
|
|
|
|
66
|
3
|
100
|
|
|
|
12
|
if ($self->{versions}{client}{rsa}) { |
67
|
2
|
50
|
33
|
|
|
19
|
if (defined $self->{rsa} and !blessed $self->{rsa}) { |
68
|
0
|
|
|
|
|
0
|
$self->{rsa} = Crypt::OpenSSL::RSA->new_private_key($self->{rsa}); |
69
|
|
|
|
|
|
|
} |
70
|
2
|
50
|
|
|
|
20
|
$self->{rsa}->use_no_padding if defined $self->{rsa}; |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
3
|
100
|
|
|
|
9
|
if (defined $self->{packet}) { |
74
|
|
|
|
|
|
|
(my $len, $self->{os}, $self->{versions}{client}{VERSION}, $self->{versions}{spr}, $self->{versions}{dat}, $self->{versions}{pic}, my $payload) |
75
|
2
|
|
|
|
|
23
|
= unpack 'v x(S S L3)< a*', $self->{packet}; |
76
|
|
|
|
|
|
|
|
77
|
2
|
100
|
|
|
|
7
|
if ($self->{versions}{client}{rsa}) { |
78
|
1
|
|
|
|
|
656
|
$payload = $self->{rsa}->decrypt($payload); |
79
|
1
|
50
|
|
|
|
7
|
croak q(Decoded RSA doesn't start with zero.) if $payload !~ /^\0/; |
80
|
1
|
|
|
|
|
17
|
$payload = substr $payload, 1; |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
2
|
100
|
|
|
|
9
|
if ($self->{versions}{client}{xtea}) { |
84
|
1
|
|
|
|
|
4
|
($self->{XTEA}, $payload) = unpack 'a16 a*', $payload; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
2
|
50
|
|
|
|
8
|
my $acc_data_pattern = $self->{versions}{client}{accname} ? '(S/a S/a)<' : '(V S/a)<'; |
88
|
|
|
|
|
|
|
($self->{account}, $self->{password}, $self->{hwinfo}, $self->{padding}) |
89
|
2
|
|
|
|
|
16
|
= unpack "$acc_data_pattern a47 a*", $payload; |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
3
|
|
|
|
|
7
|
bless $self, $class; |
93
|
3
|
|
|
|
|
9
|
return $self; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=item finalize([$rsa]) |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
Finalizes the packet. encrypts with RSA and prepends header |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=cut |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub finalize { |
104
|
2
|
|
|
2
|
1
|
9
|
my $self = shift; |
105
|
2
|
|
33
|
|
|
23
|
my $rsa = shift // $self->{rsa}; |
106
|
2
|
50
|
|
|
|
14
|
$self->{rsa}->use_no_padding if defined $self->{rsa}; |
107
|
2
|
50
|
|
|
|
19
|
$self->{versions}{client} = Game::Tibia::Packet::version $self->{versions}{client} unless ref $self->{versions}{client}; |
108
|
|
|
|
|
|
|
|
109
|
2
|
|
|
|
|
5
|
my $payload = ''; |
110
|
2
|
100
|
|
|
|
6
|
if ($self->{versions}{client}{rsa}) { |
111
|
1
|
50
|
|
|
|
4
|
$rsa = Crypt::OpenSSL::RSA->new_private_key($rsa) unless blessed $rsa; |
112
|
1
|
50
|
|
|
|
5
|
$rsa->size == 128 |
113
|
0
|
|
|
|
|
0
|
or croak "Protocol $self->{versions}{client}{VERSION} expects 128 bit RSA key, but ${\($rsa->size*8)} bit were provided"; |
114
|
1
|
|
|
|
|
3
|
$payload .= "\0"; |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
2
|
50
|
|
|
|
8
|
$self->{packet} = defined $self->{character} ? "\x0a" : "\x01"; |
118
|
2
|
|
|
|
|
13
|
$self->{packet} .= pack '(S2)<', $self->{os}, $self->{versions}{client}{VERSION}; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
$self->{packet} .= defined $self->{character} ? "\0" : |
121
|
2
|
50
|
|
|
|
12
|
pack '(L3)<', $self->{versions}{spr}, $self->{versions}{dat}, $self->{versions}{pic}; |
122
|
|
|
|
|
|
|
|
123
|
2
|
50
|
|
|
|
5
|
my $acc_pattern = $self->{versions}{client}{acc_name} ? '(S/a)<' : 'V'; |
124
|
|
|
|
|
|
|
|
125
|
2
|
100
|
|
|
|
7
|
$payload .= $self->{XTEA} if $self->{versions}{client}{xtea}; |
126
|
2
|
|
|
|
|
7
|
$payload .= pack $acc_pattern, $self->{account}; |
127
|
2
|
50
|
|
|
|
6
|
$payload .= pack '(S/a)<', $self->{character} if defined $self->{character}; |
128
|
2
|
|
|
|
|
9
|
$payload .= pack '(S/a)<', $self->{password}; |
129
|
2
|
100
|
66
|
|
|
14
|
$payload .= pack 'a47', $self->{hwinfo} if defined $self->{hwinfo} && $self->{hwinfo} ne ''; |
130
|
|
|
|
|
|
|
|
131
|
2
|
100
|
|
|
|
6
|
if ($self->{versions}{client}{rsa}) { |
132
|
1
|
|
|
|
|
3
|
my $padding_len = 128 - length($payload); |
133
|
1
|
|
50
|
|
|
3
|
$self->{padding} //= ''; |
134
|
1
|
|
|
|
|
4
|
$payload .= pack "a$padding_len", $self->{padding}; |
135
|
1
|
|
|
|
|
21
|
$payload = $self->{rsa}->encrypt($payload); |
136
|
|
|
|
|
|
|
} |
137
|
2
|
|
|
|
|
5
|
$self->{packet} .= $payload; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
|
140
|
2
|
50
|
|
|
|
7
|
if ($self->{versions}{client}{adler32}) { |
141
|
0
|
|
|
|
|
0
|
my $a32 = Digest::Adler32->new; |
142
|
0
|
|
|
|
|
0
|
$a32->add($self->{packet}); |
143
|
0
|
|
|
|
|
0
|
my $digest = unpack 'H*', pack 'N', unpack 'L', $a32->digest; |
144
|
0
|
|
|
|
|
0
|
$self->{packet} = $digest.$self->{packet}; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
2
|
|
|
|
|
8
|
$self->{packet} = pack("S/a", $self->{packet}); |
148
|
|
|
|
|
|
|
|
149
|
2
|
|
|
|
|
12
|
$self->{packet}; |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
1; |
153
|
|
|
|
|
|
|
__END__ |