File Coverage

blib/lib/Game/Tibia/Packet/Login.pm
Criterion Covered Total %
statement 56 62 90.3
branch 11 20 55.0
condition 4 11 36.3
subroutine 11 11 100.0
pod 2 2 100.0
total 84 106 79.2


line stmt bran cond sub pod time code
1 2     2   50321 use strict;
  2         3  
  2         59  
2 2     2   7 use warnings;
  2         2  
  2         46  
3 2     2   32 use v5.16.0;
  2         8  
4             package Game::Tibia::Packet::Login;
5              
6             # ABSTRACT: Login packet support for the MMORPG Tibia
7             our $VERSION = '0.005'; # VERSION
8              
9 2     2   8 use Carp;
  2         3  
  2         138  
10 2     2   1076 use File::ShareDir 'dist_file';
  2         11574  
  2         132  
11 2     2   1090 use Crypt::OpenSSL::RSA;
  2         13772  
  2         77  
12 2     2   900 use Digest::Adler32;
  2         643  
  2         67  
13 2     2   836 use Game::Tibia::Packet;
  2         4  
  2         70  
14 2     2   14 use Scalar::Util qw(blessed);
  2         2  
  2         1418  
15              
16             =pod
17              
18             =encoding utf8
19              
20             =head1 NAME
21              
22             Game::Tibia::Packet::Login - Login packet support for the MMORPG Tibia
23              
24              
25             =head1 SYNOPSIS
26              
27             use Game::Tibia::Packet::Login;
28              
29              
30             =head1 DESCRIPTION
31              
32             Decodes Tibia Charlist packets into hashes and vice versa. By default uses the OTServ RSA key, but allows different RSA keys to be supplied.
33              
34             =cut
35              
36             my $otserv = Crypt::OpenSSL::RSA->new_private_key(
37             do { local $/; open my $rsa, dist_file('Game-Tibia-Packet', 'otserv.private') or die "Couldn't open private key $!"; <$rsa>; }
38             );
39              
40             =head1 METHODS AND ARGUMENTS
41              
42             =over 4
43              
44             =item new([packet => $packet, rsa => OTSERV])
45              
46             Constructs a new C instance. If C is supplied, decryption using the supplied rsa private key is attempted. If no C is supplied, the OTServ RSA key is used.
47              
48             =cut
49              
50             sub new {
51 2     2 1 37 my $class = shift;
52            
53 2         11 my $self = {
54             packet => undef,
55             rsa => $otserv,
56              
57             @_
58             };
59              
60 2 50       43 unless (blessed $self->{rsa}) {
61 0         0 $self->{rsa} = Crypt::OpenSSL::RSA->new_private_key($self->{rsa});
62             }
63 2         27 $self->{rsa}->use_no_padding;
64              
65 2 100       8 if (defined $self->{packet}) {
66             (my $len, $self->{os}, $self->{version}{client}, $self->{version}{spr}, $self->{version}{dat}, $self->{version}{pic}, my $payload)
67 1         16 = unpack 'v x(S S L3)< a*', $self->{packet};
68              
69 1         929 my $decr = $self->{rsa}->decrypt($payload);
70 1 50       11 croak q(Decoded RSA doesn't start with zero.) if $decr !~ /^\0/;
71              
72             my $acc_data_pattern =
73             Game::Tibia::Packet::version($self->{version}{client})->{ACCNUM}
74 1 50       7 ? '(V S/a)' : '(S/a S/a)<';
75             ($self->{XTEA}, $self->{account}, $self->{password}, $self->{hwinfo}, $self->{padding})
76 1         13 = unpack "(x a16 $acc_data_pattern)< a47 a*", $decr;
77             }
78              
79 2         5 bless $self, $class;
80 2         6 return $self;
81             }
82              
83             =item finalize([$rsa])
84              
85             Finalizes the packet. encrypts with RSA and prepends header
86              
87             =cut
88              
89              
90             sub finalize {
91 1     1 1 6 my $self = shift;
92 1   33     11 my $rsa = shift // $self->{rsa};
93 1         4 $self->{rsa}->use_no_padding;
94 1 50       7 $self->{version}{client} = Game::Tibia::Packet::version $self->{version}{client} unless ref $self->{version}{client};
95 1 50 33     9 $rsa = Crypt::OpenSSL::RSA->new_private_key($rsa) unless blessed $rsa || !$self->{version}{client}{RSA};
96 1         2 my $expected_rsa_size = $self->{version}{client}{RSA};
97             !defined $expected_rsa_size || $rsa->size == $expected_rsa_size
98             or croak "Protocol $self->{version}{client}{VERSION} expects "
99 1 50 33     12 . ($self->{version}{client}{RSA} * 8) . " bit RSA key, but ${\($rsa->size*8)} bit were provided";
  0         0  
100              
101 1         11 $self->{packet} = pack 'C (S S L3)<', 0x01, $self->{os}, $self->{version}{client}{VERSION}, $self->{version}{spr}, $self->{version}{dat}, $self->{version}{pic};
102              
103             my $acc_data_pattern =
104             Game::Tibia::Packet::version($self->{version}{client})->{ACCNUM}
105 1 50       3 ? '(V S/a)' : '(S/a S/a)<' ;
106 1         10 my $payload = pack "(C a16 $acc_data_pattern a47)<", 0x00, $self->{XTEA}, $self->{account}, $self->{password}, $self->{hwinfo};
107              
108 1         4 my $padding_len = $self->{version}{client}{RSA} - length($payload);
109 1   50     4 $self->{padding} //= '';
110 1         6 $payload .= pack "a$padding_len", $self->{padding};
111 1 50       40 $payload = $self->{rsa}->encrypt($payload) if $self->{version}{client}{RSA};
112 1         4 $self->{packet} .= $payload;
113              
114              
115 1 50       4 if ($self->{version}{client}{ADLER32}) {
116 0         0 my $a32 = Digest::Adler32->new;
117 0         0 $a32->add($self->{packet});
118 0         0 my $digest = unpack 'H*', pack 'N', unpack 'L', $a32->digest;
119 0         0 $self->{packet} = $digest.$self->{packet};
120             }
121              
122 1         5 $self->{packet} = pack("S/a", $self->{packet});
123              
124 1         8 $self->{packet};
125             }
126              
127             1;
128             __END__