File Coverage

blib/lib/Minecraft/NBTReader.pm
Criterion Covered Total %
statement 155 175 88.5
branch 60 90 66.6
condition 12 18 66.6
subroutine 20 21 95.2
pod 0 16 0.0
total 247 320 77.1


line stmt bran cond sub pod time code
1             package Minecraft::NBTReader;
2              
3 2     2   31820 use 5.016;
  2         6  
  2         55  
4 2     2   7 use strict;
  2         3  
  2         63  
5 2     2   10 use warnings;
  2         8  
  2         132  
6              
7             require Exporter;
8              
9             our @ISA = qw(Exporter);
10             our $VERSION = '0.6';
11              
12 2     2   8 use Config;
  2         3  
  2         86  
13 2     2   2841 use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
  2         85217  
  2         2323  
14              
15             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
16              
17             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
18             our @EXPORT = qw();
19              
20             sub new {
21 2     2 0 1117 my ($class) = @_;
22 2         6 my $self = bless {}, $class;
23            
24 2 50       1175 if($Config{byteorder} =~ /^1/) {
25 2         4253 $self->{needswap} = 1;
26             } else {
27 0         0 $self->{needswap} = 0;
28             }
29            
30 2         9 return $self;
31             }
32              
33             sub readFile {
34 2     2 0 471 my ($self, $filename) = @_;
35            
36 2         6 $self->{unnamedcount} = 0;
37            
38 2         3 my %data;
39            
40 2         10 my $filetype = $self->checkFileType($filename);
41            
42 2         5 my $newfname = $filename;
43            
44 2 100       11 if($filetype eq 'gzip') {
    50          
    50          
45 1         1 $newfname = 'temp.dat';
46 1         4 $self->DeZip($filename, $newfname);
47             } elsif($filetype eq 'unknown') {
48 0         0 die("File is of unknown type");
49             } elsif($filetype eq 'plain') {
50 1         6 print "File looks like an NBT file\n";
51             }
52            
53 2 50       39 open(my $ifh, '<', $newfname) or die($!);
54 2         5 binmode($ifh);
55            
56 2         10 $self->parseFile(\*$ifh, \%data);
57            
58 2         14 close $ifh;
59            
60 2 100       9 if($filename ne $newfname) {
61 1         112 unlink $newfname;
62             }
63            
64 2         19 return %data;
65             }
66              
67             sub checkFileType {
68 3     3 0 6 my ($self, $filename) = @_;
69            
70 3 50       70 open(my $ifh, '<', $filename) or die($!);
71 3         4 my $buf;
72 3 50       47 read($ifh, $buf, 1) or die($!);
73 3         5 my $type = ord($buf);
74 3         16 close $ifh;
75            
76 3 100       13 if($type == 10) {
    50          
77 2         13 return 'plain';
78             } elsif($type == 31) {
79 1         4 return 'gzip';
80             }
81            
82 0         0 return 'unknown';
83             }
84              
85             sub DeZip {
86 1     1 0 2 my ($self, $fname, $newfname) = @_;
87            
88 1         19 unlink $newfname;
89            
90 1         4 gunzip $fname => $newfname;
91            
92 1 50 33     1716 if(!-f $newfname || $self->checkFileType($newfname) ne 'plain') {
93 0         0 die("Gunzip failed!");
94             }
95            
96 1         3 return;
97             }
98              
99             sub parseFile {
100 9     9 0 9 my ($self, $fh, $data) = @_;
101            
102 9         23 while(!eof($fh)) {
103 31         17 my $buf;
104 31 50       51 read($fh, $buf, 1) or die($!);
105 31         21 my $type = ord($buf);
106 31 100 66     158 if($type == 0) {
    100 100        
    100          
    100          
    50          
107             # TAG_end
108 7         8 last;
109             } elsif(($type >= 1 && $type <= 6) || $type == 8) {
110             # TAG_byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_String
111 16         36 my $name = $self->readTagName($fh);
112 16         24 my $val = $self->readValByType($fh, $type);
113 16         35 $data->{$name} = $val;
114             } elsif($type == 7) {
115             # TAG_Byte_Array
116 1         2 my $name = $self->readTagName($fh);
117 1         3 my $count = $self->readInt($fh);
118 1         2 my @vals;
119 1         3 for(my $i = 0; $i < $count; $i++) {
120 1000         761 my $val = $self->readByte($fh);
121 1000         1194 push @vals, $val;
122             }
123 1         7 $data->{$name} = \@vals;
124             } elsif($type == 9) {
125             # TAG_List
126 2         3 my $name = $self->readTagName($fh);
127 2 50       4 read($fh, $buf, 1) or die($!);
128 2         2 my $listtype = ord($buf);
129 2         3 my $count = $self->readInt($fh);
130 2         3 my @vals;
131 2         4 for(my $i = 0; $i < $count; $i++) {
132 7 100 66     33 if(($listtype >= 1 && $listtype <= 6) || $listtype == 8) {
    50 66        
133             # simmple data types
134 5         5 my $val = $self->readValByType($fh, $listtype);
135 5         9 push @vals, $val;
136             } elsif($listtype == 10) {
137             # unnamed compound
138 2         0 my %subdata;
139 2         3 $self->parseFile($fh, \%subdata);
140 2         5 push @vals, \%subdata;
141             } else {
142 0         0 die("Unsupported type $listtype for TAG_List");
143             }
144             }
145 2         5 $data->{$name} = \@vals;
146             } elsif($type == 10) {
147             # TAG_compound
148 5         11 my $name = $self->readTagName($fh);
149 5         4 my %tmp;
150 5         26 $self->parseFile($fh, \%tmp);
151 5         25 $data->{$name} = \%tmp;
152             } else {
153 0         0 die("Unknown type $type");
154             }
155             }
156            
157 9         7 return;
158             }
159              
160             sub getNextPseudoName {
161 0     0 0 0 my ($self) = @_;
162            
163 0         0 $self->{unnamedcount}++;
164            
165 0         0 my $val = '' . $self->{unnamedcount};
166 0         0 while(length($val) < 7) {
167 0         0 $val = '0' . $val;
168             }
169 0         0 return 'unnamed_' . $val;
170             }
171              
172             sub readTagName {
173 24     24 0 18 my ($self, $fh) = @_;
174            
175 24         28 my $len = $self->readStringLength($fh, 1);
176            
177 24 50       29 if(!$len) {
178 0         0 return $self->getNextPseudoName();
179             }
180            
181 24         15 my $name;
182 24 50       36 read($fh, $name, $len) or die($!);
183 24         35 return $name;
184             }
185              
186             sub readStringLength {
187 30     30 0 25 my ($self, $fh, $allowzerolength) = @_;
188            
189 30 100       37 if(!defined($allowzerolength)) {
190 6         7 $allowzerolength = 0;
191             }
192            
193 30         18 my $buf;
194 30 50       44 read($fh, $buf, 2) or die($!);
195            
196 30         17 my $len;
197 30 50       38 if($self->{needswap}) {
198 30         35 $len = unpack('S>', $buf);
199             } else {
200 0         0 $len = unpack('S', $buf);
201             }
202            
203 30 50 66     46 die("The Fuck?") if(!$allowzerolength && !$len);
204            
205 30         31 return $len;
206             }
207              
208             sub readValByType {
209 21     21 0 17 my ($self, $fh, $type) = @_;
210            
211 21 100       66 if($type == 1) {
    100          
    100          
    100          
    100          
    100          
    50          
212 1         2 return $self->readByte($fh);
213             } elsif($type == 2) {
214 1         3 return $self->readShort($fh);
215             } elsif($type == 3) {
216 1         2 return $self->readInt($fh);
217             } elsif($type == 4) {
218 8         8 return $self->readLong($fh);
219             } elsif($type == 5) {
220 3         5 return $self->readFloat($fh);
221             } elsif($type == 6) {
222 1         3 return $self->readDouble($fh);
223             } elsif($type == 8) {
224 6         11 return $self->readString($fh);
225             }
226            
227 0         0 return;
228             }
229              
230             sub readByte {
231 1001     1001 0 604 my ($self, $fh) = @_;
232            
233 1001         492 my $buf;
234 1001 50       1161 read($fh, $buf, 1) or die($!);
235            
236 1001         686 my $val = unpack('c', $buf);
237            
238 1001         731 return $val;
239             }
240              
241             sub readShort {
242 1     1 0 1 my ($self, $fh) = @_;
243            
244 1         2 my $buf;
245 1 50       3 read($fh, $buf, 2) or die($!);
246            
247 1         1 my $val;
248 1 50       3 if($self->{needswap}) {
249 1         2 $val = unpack('s>', $buf);
250             } else {
251 0         0 $val = unpack('s', $buf);
252             }
253            
254 1         2 return $val;
255             }
256              
257             sub readInt {
258 4     4 0 3 my ($self, $fh) = @_;
259            
260 4         3 my $buf;
261 4 50       7 read($fh, $buf, 4) or die($!);
262            
263 4         4 my $val;
264 4 50       4 if($self->{needswap}) {
265 4         6 $val = unpack('l>', $buf);
266             } else {
267 0         0 $val = unpack('l', $buf);
268             }
269            
270 4         5 return $val;
271             }
272              
273             sub readLong {
274 8     8 0 4 my ($self, $fh) = @_;
275            
276 8         6 my $buf;
277 8 50       11 read($fh, $buf, 8) or die($!);
278            
279 8         4 my $val;
280 8 50       8 if($self->{needswap}) {
281 8         9 $val = unpack('q>', $buf);
282             } else {
283 0         0 $val = unpack('q', $buf);
284             }
285            
286 8         10 return $val;
287             }
288              
289             sub readFloat {
290 3     3 0 2 my ($self, $fh) = @_;
291            
292 3         3 my $buf;
293 3 50       4 read($fh, $buf, 4) or die($!);
294            
295 3         2 my $val;
296 3 50       4 if($self->{needswap}) {
297 3         3 $val = unpack('f>', $buf);
298             } else {
299 0         0 $val = unpack('f', $buf);
300             }
301            
302 3         5 return $val;
303             }
304              
305             sub readDouble {
306 1     1 0 1 my ($self, $fh) = @_;
307            
308 1         2 my $buf;
309 1 50       2 read($fh, $buf, 8) or die($!);
310            
311 1         2 my $val;
312 1 50       4 if($self->{needswap}) {
313 1         2 $val = unpack('d>', $buf);
314             } else {
315 0         0 $val = unpack('d', $buf);
316             }
317            
318 1         4 return $val;
319             }
320              
321             sub readString {
322 6     6 0 5 my ($self, $fh) = @_;
323            
324 6         5 my $val;
325 6         8 my $len = $self->readStringLength($fh);
326            
327 6 50       14 read($fh, $val, $len) or die($!);
328            
329 6         11 return $val;
330             }
331              
332             1;
333             __END__