File Coverage

blib/lib/Game/Tibia/Packet/Login.pm
Criterion Covered Total %
statement 76 82 92.6
branch 30 44 68.1
condition 7 17 41.1
subroutine 12 12 100.0
pod 2 2 100.0
total 127 157 80.8


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__