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   29470 use 5.018001;
  2         7  
  2         61  
4 2     2   10 use strict;
  2         2  
  2         75  
5 2     2   7 use warnings;
  2         9  
  2         143  
6              
7             require Exporter;
8              
9             our @ISA = qw(Exporter);
10             our $VERSION = '0.5';
11              
12 2     2   9 use Config;
  2         3  
  2         98  
13 2     2   2435 use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
  2         80075  
  2         2483  
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 990 my ($class) = @_;
22 2         7 my $self = bless {}, $class;
23            
24 2 50       1219 if($Config{byteorder} =~ /^1/) {
25 2         3846 $self->{needswap} = 1;
26             } else {
27 0         0 $self->{needswap} = 0;
28             }
29            
30 2         8 return $self;
31             }
32              
33             sub readFile {
34 2     2 0 692 my ($self, $filename) = @_;
35            
36 2         5 $self->{unnamedcount} = 0;
37            
38 2         4 my %data;
39            
40 2         10 my $filetype = $self->checkFileType($filename);
41            
42 2         3 my $newfname = $filename;
43            
44 2 100       10 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         5 print "File looks like an NBT file\n";
51             }
52            
53 2 50       34 open(my $ifh, '<', $newfname) or die($!);
54 2         5 binmode($ifh);
55            
56 2         10 $self->parseFile(\*$ifh, \%data);
57            
58 2         15 close $ifh;
59            
60 2 100       7 if($filename ne $newfname) {
61 1         103 unlink $newfname;
62             }
63            
64 2         21 return %data;
65             }
66              
67             sub checkFileType {
68 3     3 0 5 my ($self, $filename) = @_;
69            
70 3 50       76 open(my $ifh, '<', $filename) or die($!);
71 3         4 my $buf;
72 3 50       46 read($ifh, $buf, 1) or die($!);
73 3         6 my $type = ord($buf);
74 3         18 close $ifh;
75            
76 3 100       12 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 3 my ($self, $fname, $newfname) = @_;
87            
88 1         16 unlink $newfname;
89            
90 1         5 gunzip $fname => $newfname;
91            
92 1 50 33     2017 if(!-f $newfname || $self->checkFileType($newfname) ne 'plain') {
93 0         0 die("Gunzip failed!");
94             }
95            
96 1         2 return;
97             }
98              
99             sub parseFile {
100 9     9 0 9 my ($self, $fh, $data) = @_;
101            
102 9         24 while(!eof($fh)) {
103 31         17 my $buf;
104 31 50       58 read($fh, $buf, 1) or die($!);
105 31         21 my $type = ord($buf);
106 31 100 66     145 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         40 my $name = $self->readTagName($fh);
112 16         25 my $val = $self->readValByType($fh, $type);
113 16         40 $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         1 my @vals;
119 1         4 for(my $i = 0; $i < $count; $i++) {
120 1000         910 my $val = $self->readByte($fh);
121 1000         1347 push @vals, $val;
122             }
123 1         8 $data->{$name} = \@vals;
124             } elsif($type == 9) {
125             # TAG_List
126 2         3 my $name = $self->readTagName($fh);
127 2 50       5 read($fh, $buf, 1) or die($!);
128 2         1 my $listtype = ord($buf);
129 2         3 my $count = $self->readInt($fh);
130 2         2 my @vals;
131 2         5 for(my $i = 0; $i < $count; $i++) {
132 7 100 66     31 if(($listtype >= 1 && $listtype <= 6) || $listtype == 8) {
    50 66        
133             # simmple data types
134 5         11 my $val = $self->readValByType($fh, $listtype);
135 5         12 push @vals, $val;
136             } elsif($listtype == 10) {
137             # unnamed compound
138 2         2 my %subdata;
139 2         4 $self->parseFile($fh, \%subdata);
140 2         4 push @vals, \%subdata;
141             } else {
142 0         0 die("Unsupported type $listtype for TAG_List");
143             }
144             }
145 2         6 $data->{$name} = \@vals;
146             } elsif($type == 10) {
147             # TAG_compound
148 5         12 my $name = $self->readTagName($fh);
149 5         5 my %tmp;
150 5         26 $self->parseFile($fh, \%tmp);
151 5         27 $data->{$name} = \%tmp;
152             } else {
153 0         0 die("Unknown type $type");
154             }
155             }
156            
157 9         11 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 21 my ($self, $fh) = @_;
174            
175 24         30 my $len = $self->readStringLength($fh, 1);
176            
177 24 50       31 if(!$len) {
178 0         0 return $self->getNextPseudoName();
179             }
180            
181 24         13 my $name;
182 24 50       41 read($fh, $name, $len) or die($!);
183 24         30 return $name;
184             }
185              
186             sub readStringLength {
187 30     30 0 25 my ($self, $fh, $allowzerolength) = @_;
188            
189 30 100       43 if(!defined($allowzerolength)) {
190 6         5 $allowzerolength = 0;
191             }
192            
193 30         20 my $buf;
194 30 50       45 read($fh, $buf, 2) or die($!);
195            
196 30         17 my $len;
197 30 50       36 if($self->{needswap}) {
198 30         40 $len = unpack('S>', $buf);
199             } else {
200 0         0 $len = unpack('S', $buf);
201             }
202            
203 30 50 66     53 die("The Fuck?") if(!$allowzerolength && !$len);
204            
205 30         34 return $len;
206             }
207              
208             sub readValByType {
209 21     21 0 21 my ($self, $fh, $type) = @_;
210            
211 21 100       69 if($type == 1) {
    100          
    100          
    100          
    100          
    100          
    50          
212 1         4 return $self->readByte($fh);
213             } elsif($type == 2) {
214 1         3 return $self->readShort($fh);
215             } elsif($type == 3) {
216 1         3 return $self->readInt($fh);
217             } elsif($type == 4) {
218 8         11 return $self->readLong($fh);
219             } elsif($type == 5) {
220 3         6 return $self->readFloat($fh);
221             } elsif($type == 6) {
222 1         4 return $self->readDouble($fh);
223             } elsif($type == 8) {
224 6         13 return $self->readString($fh);
225             }
226            
227 0         0 return;
228             }
229              
230             sub readByte {
231 1001     1001 0 682 my ($self, $fh) = @_;
232            
233 1001         557 my $buf;
234 1001 50       1241 read($fh, $buf, 1) or die($!);
235            
236 1001         783 my $val = unpack('c', $buf);
237            
238 1001         789 return $val;
239             }
240              
241             sub readShort {
242 1     1 0 2 my ($self, $fh) = @_;
243            
244 1         1 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         3 $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         4 my $buf;
261 4 50       10 read($fh, $buf, 4) or die($!);
262            
263 4         3 my $val;
264 4 50       5 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 7 my ($self, $fh) = @_;
275            
276 8         7 my $buf;
277 8 50       11 read($fh, $buf, 8) or die($!);
278            
279 8         5 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         11 return $val;
287             }
288              
289             sub readFloat {
290 3     3 0 4 my ($self, $fh) = @_;
291            
292 3         1 my $buf;
293 3 50       7 read($fh, $buf, 4) or die($!);
294            
295 3         1 my $val;
296 3 50       6 if($self->{needswap}) {
297 3         7 $val = unpack('f>', $buf);
298             } else {
299 0         0 $val = unpack('f', $buf);
300             }
301            
302 3         4 return $val;
303             }
304              
305             sub readDouble {
306 1     1 0 2 my ($self, $fh) = @_;
307            
308 1         1 my $buf;
309 1 50       4 read($fh, $buf, 8) or die($!);
310            
311 1         2 my $val;
312 1 50       3 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         10 my $len = $self->readStringLength($fh);
326            
327 6 50       13 read($fh, $val, $len) or die($!);
328            
329 6         11 return $val;
330             }
331              
332             1;
333             __END__