File Coverage

blib/lib/Game/Tibia/Packet.pm
Criterion Covered Total %
statement 58 68 85.2
branch 12 20 60.0
condition 3 6 50.0
subroutine 11 12 91.6
pod 5 5 100.0
total 89 111 80.1


line stmt bran cond sub pod time code
1 6     6   47558 use strict;
  6         11  
  6         187  
2 6     6   29 use warnings;
  6         8  
  6         178  
3 6     6   63 use v5.16.0;
  6         16  
4             package Game::Tibia::Packet;
5              
6             # ABSTRACT: Minimal session layer support for the MMORPG Tibia
7             our $VERSION = '0.005'; # VERSION
8              
9 6     6   1743 use Digest::Adler32 qw(adler32);
  6         1256  
  6         157  
10 6     6   2710 use Crypt::XTEA 0.0108;
  6         16869  
  6         1053  
11 6     6   3127 use Crypt::ECB 2.0.0;
  6         13164  
  6         300  
12 6     6   36 use Carp;
  6         12  
  6         4063  
13              
14             sub version;
15             my $default = Game::Tibia::Packet::version(860);
16              
17             =pod
18              
19             =encoding utf8
20              
21             =head1 NAME
22              
23             Game::Tibia::Packet - Session layer support for the MMORPG Tibia
24              
25             =head1 SYNOPSIS
26              
27             use Game::Tibia::Packet;
28              
29             # decrypt Tibia packet
30             my $read; my $ret = $sock->recv($read, 1024);
31             my $res = Game::Tibia::Packet->new(packet => $read, xtea => $xtea_key);
32             $packet_type = unpack('C', $res->payload);
33              
34              
35             # encrypt a Tibia speech packet
36             my $p = Game::Tibia::Packet->new;
37             $p->payload .= pack("C S S S/A S C SSC S/A",
38             0xAA, 0x1, 0x0, "Perl", 0, 1, 1, 1, 8,
39             "Game::Tibia::Packet says Hi!\n:-)");
40             $sock->send($p->finalize($xtea_key}))
41              
42             =begin HTML
43              
44            

Screenshot

45              
46             =end HTML
47              
48              
49             =head1 DESCRIPTION
50              
51             Methods for constructing Tibia Gameserver (XTEA) packets. Handles checksum calculation and symmetric encryption depending on the requested Tibia version.
52              
53             Tested working with Tibia 8.1, but will probably work with other protocol versions too.
54              
55             =head1 METHODS AND ARGUMENTS
56              
57             =over 4
58              
59             =item new([packet => $payload, xtea => $xtea, version => 860])
60              
61             Constructs a new Game::Tibia::Packet instance. If payload and XTEA are given, the payload will be decrypted and trimmed to correct size. version argument defaults to 860.
62              
63             =cut
64              
65             sub new {
66 6     6 1 40 my $type = shift;
67 6         35 my $self = {
68             payload => '',
69             packet => '',
70             xtea => undef,
71             version => $default,
72             padding => '',
73             @_
74             };
75 6 100       28 $self->{version} = version $self->{version} unless ref $self->{version};
76 6 100       21 if ($self->{packet} ne '')
77             {
78             #return undef unless isValid($self->{packet});
79             my $ecb = Crypt::ECB->new(
80 3         26 -cipher => Crypt::XTEA->new($self->{xtea}, 32, little_endian => 1)
81             );
82 3         454 $ecb->padding('null');
83            
84 3         19 my $digest_size = $self->{version}{ADLER32};
85 3         12 $self->{payload} = $ecb->decrypt(substr($self->{packet}, 2 + $digest_size));
86 3         906 $self->{payload} .= "\0" x ((8 - length($self->{payload})% 8)%8);
87 3         12 $self->{padding} = substr $self->{payload}, 2 + unpack('v', $self->{payload});
88 3         31 $self->{payload} = substr $self->{payload}, 2, unpack('v', $self->{payload});
89             }
90              
91 6         10 bless $self, $type;
92 6         16 return $self;
93             }
94              
95             =item isValid($packet)
96              
97             Checks if packet's adler32 digest matches (A totally unnecessary thing on Cipsoft's part, as we already have TCP checksum. Why hash again?)
98              
99             =cut
100              
101             sub isValid {
102 0     0 1 0 my $packet = shift;
103              
104 0         0 my ($len, $adler) = unpack('(S a4)<', $packet);
105 0 0       0 return 0 if $len + 2 != length $packet;
106              
107 0         0 my $a32 = Digest::Adler32->new;
108 0         0 $a32->add(substr($packet, 6));
109 0 0       0 return 0 if $a32->digest ne reverse $adler;
110 0         0 1;
111             #TODO: set errno to checksum failed or length doesnt match
112             }
113              
114             =item payload() : lvalue
115              
116             returns the payload as lvalue (so you can concat on it)
117              
118             =cut
119              
120             sub payload : lvalue {
121 12     12 1 10 my $self = shift;
122 12         57 return $self->{payload};
123             }
124              
125             =item finalize([$XTEA_KEY])
126              
127             Finalizes the packet. XTEA encrypts, prepends checksum and length.
128              
129             =cut
130              
131              
132             sub finalize {
133 3     3 1 8 my $self = shift;
134 3   66     15 my $XTEA = $self->{xtea} // shift;
135              
136 3         5 my $packet = $self->{payload};
137 3 50 33     21 if ($self->{version}{XTEA} and defined $XTEA) {
138 3         21 $packet = pack('v', length $packet) . $packet;
139            
140 3         16 my $ecb = Crypt::ECB->new(
141             -cipher => Crypt::XTEA->new($XTEA, 32, little_endian => 1)
142             );
143 3         229 $ecb->padding('null');
144            
145             # $packet .= "\0" x ((8 - length($packet)% 8)%8);
146 3         19 my $padding_len = (8 - length($packet)% 8)%8;
147 3         13 $packet .= pack("a$padding_len", unpack('a*', $self->{padding}));
148 3         5 my $orig_len = length $packet;
149 3         9 $packet = $ecb->encrypt($packet);
150 3         860 substr($packet, $orig_len) = '';
151             }
152              
153 3         5 my $digest = '';
154 3 50       12 if ($self->{version}{ADLER32}) {
155 0         0 my $a32 = Digest::Adler32->new;
156 0         0 $a32->add($packet);
157 0         0 $digest = unpack 'H*', pack 'N', unpack 'L', $a32->digest;
158             }
159              
160 3         35 $packet = CORE::pack("S/a", $digest.$packet);
161              
162 3         19 $packet;
163             }
164              
165              
166             =item version($version)
167              
168             Returns a hash reference with size information about a protocol version.
169             For example, for 860 it returns:
170              
171             {XTEA => 16, RSA => 128, ADLER32 => 4, ACCNUM => 0, GET_CHARLIST => 147, LOGIN_CHAR => 135}
172              
173             Sizes are in bytes.
174              
175             =cut
176              
177             sub version {
178 14     14 1 23 my $ver = shift;
179 14 100       44 $ver = $ver->{VERSION} if ref $ver;
180 14         60 $ver =~ s/^v|[ .]//g;
181 14 50       53 $ver =~ /^\d+/ or croak 'Version format invalid';
182              
183 14 50       110 my $sizes = $ver >= 830 ? {XTEA => 16, RSA => 128, ADLER32 => 4, ACCNUM => 0,
    100          
184             GET_CHARLIST => 147, LOGIN_CHAR => 135}
185              
186             : $ver >= 761 ? {XTEA => 16, RSA => 128, ADLER32 => 0, ACCNUM => 4,
187             GET_CHARLIST => 143, LOGIN_CHAR => 131}
188              
189             : {XTEA => 0, RSA => 0, ADLER32 => 0, ACCNUM => 4,
190             GET_CHARLIST => undef, LOGIN_CHAR => undef};
191 14         29 $sizes->{VERSION} = $ver;
192 14         36 return $sizes;
193             }
194              
195             1;
196             __END__