File Coverage

blib/lib/Games/Freelancer/UTF.pm
Criterion Covered Total %
statement 105 114 92.1
branch 25 36 69.4
condition 4 6 66.6
subroutine 19 19 100.0
pod 11 11 100.0
total 164 186 88.1


line stmt bran cond sub pod time code
1             ###############################################################################
2             #UTF.pm
3             #Last Change: 2008-12-08
4             #Copyright (c) 2008 Marc-Seabstian "Maluku" Lucksch
5             #Version 0.21
6             ####################
7             #This file is part of the Plasma project, a parser library for an all-purpose
8             #ASCII file format. More information can be found on the project web site
9             #at http://plasma.sf.net/ .
10             #
11             #UTF.pm is published under the terms of the MIT license, which basically
12             #means "Do with it whatever you want". For more information, see the license.txt
13             #file that should be enclosed with plasma distributions. A copy of the license
14             #is (at the time of this writing) also available at
15             #http://www.opensource.org/licenses/mit-license.php .
16             ###############################################################################
17            
18             package Games::Freelancer::UTF;
19 1     1   19566 use Exporter;
  1         2  
  1         46  
20             {
21 1     1   5 no warnings qw/portable/; #Why is this so hard... :(
  1         1  
  1         42  
22             local $^W=0; #Grrr @ Tie::InsertOrderHash;
23             require Tie::InsertOrderHash;
24             }
25 1     1   4 use strict;
  1         5  
  1         25  
26 1     1   4 use warnings;
  1         1  
  1         43  
27 1     1   10 use warnings::register;
  1         1  
  1         149  
28            
29 1     1   5 use Carp;
  1         1  
  1         520  
30            
31             our $VERSION = 1.001;
32            
33             =head1 NAME
34            
35             Games::Freelancer::UTF - Perl extension for working with Microsoft UTF Files used in the Game Freelancer.
36            
37             =head1 Synopsis
38            
39             use Games::Freelancer::UTF;
40             open FILE,"model.cmp"; #or .utf, .3db, .txm, .mat, .ale, .vms, .dfm or maybe some more
41             binmode FILE;
42             my $content = do {local $/; };
43             close FILE;
44             my $tree=UTFread($content);
45             $code = UTFwriteUTF($tree);
46             open FILE, ">out.cmp"
47             binmode FILE;
48             print FILE, $code;
49             close FILE;
50            
51            
52             =head1 DESCRIPTION
53            
54             This Module provides the ability to decode UTF files for the Mircrosoft game "Freelancer"
55            
56             Those are named UTF files because of their header.
57            
58             They are just trees that are encoded in binary, there might be a possibility that these files are used somewhere else, too.
59            
60             In "Freelancer" they are used for models, meshes, materials, textures, effects and a lot more.
61            
62             You can even use this to save hashes of hashes, but I highly recommend using something else, like L for this.
63            
64             =head1 WARNING
65            
66             The read routines return a tied InsertOrderHash, so just keep the reference you got and work with it instead of using
67            
68             my %mysuperhash = %{UTFread($crypted)}
69             %mysuperhash{NewEntry} = "Data"
70             # Now the order is destroyed
71             # This is better:
72             my $tree=UTFread($crypted);
73             $tree->{NewEntry} = "Data"
74            
75            
76             I have no idea how important the order of the elements is for Freelancer, but better keep it this way.
77             Of course all subhashes are also tied
78             #Bad code example
79             $tree->{copyme}={%{$tree}};
80             #Good code:
81             tie my %newhash,'Tie::InsertOrderHash';
82             %newhash=(%{$tree}) #Copy tree, preserves order, at least last time I tested
83             $tree->{copyme}=\%newhash;
84             #not this:
85             $tree->{copyme}={%newhash}; #Looses tiedness too.
86            
87             =head1 FUNCTIONS
88            
89             =cut
90            
91             # Perl-Port by Maluku (fl@maluku.de)
92             our @ISA=qw/Exporter/;
93             our @EXPORT=qw/UTFread UTFwrite/;
94             our @EXPORT_OK=qw/UTFread UTFwrite/;
95            
96             my $datas;
97             my $strings;
98             my $pointer;
99             my %strings;
100             my %datas;
101             my %offsets;
102            
103            
104             =head2 $tree = UTFread ($data);
105            
106             Reads an UTF file content into a tied tree:
107            
108             use Games::Freelancer::UTF;
109             use Data::Dumper;
110             open FILE,"model.cmp";
111             binmode FILE;
112             my $content = do {local $/; };
113             close FILE;
114             my $tree=UTFread($content);
115             print Data::Dumper->Dump([$tree]);
116            
117             =cut
118            
119             sub UTFread {
120 1     1 1 298 my $d = UTFreadUTF(@_);
121 1         40 return $d;
122             }
123            
124            
125             =head2 $data = UTFwrite ($tree);
126            
127             Reads an UTF file content into a tied tree:
128            
129             use Games::Freelancer::UTF;
130             open FILE,"model.cmp";
131             binmode FILE;
132             my $content = do {local $/; };
133             close FILE;
134             my $tree=UTFread($content);
135             #... Do something with $tree ..., for example moving a hardpoint:
136             foreach my $entr (grep /\.3db/,keys %{$tree->{"\\"}}) {
137             foreach (keys %{$tree->{"\\"}->{$entr}->{Hardpoints}->{"Fixed"}}) {
138             #moves all fixed hardpoints along (0.2,0.2,0.2):
139             $tree->{"\\"}->{$entr}->{Hardpoints}->{"Fixed"}->{$_}->{Position}=pack("f*",map {$_+0.2} unpack("f*",$tree->{"\\"}->{$entr}->{Hardpoints}->{"Fixed"}->{$_}->{Position}));
140             }
141             }
142             # Now write it down.
143             open FILE,">model2.cmp";
144             binmode FILE;
145             print FILE UTFwrite($tree);
146             close FILE;
147             =cut
148            
149             sub UTFwrite {
150 1     1 1 713 return UTFwriteUTF(@_);
151             }
152            
153             =head1 INTERNAL FUNCTIONS (use with care)
154            
155             =head2 get (SOURCE, OFFSET, LENTGH)
156            
157             Extracts a string of LENGTH at OFFSET of the SOURCE.
158            
159             Used for general file reading.
160            
161             =cut
162            
163             #Internal functions:
164            
165             #$chars = get ($string,$offset,$amount);
166             #returns $amount (or less) chars of $string starting from $offset.
167             #Also increases $offset.
168            
169            
170             sub get {
171 1     1 1 2 my $off=$_[1];
172 1         2 $_[1]+=$_[2];
173 1         7 return substr($_[0],$off,$_[2]);
174             }
175            
176             =head2 string (OFFSET)
177            
178             Extracts a string \0 delimited string at OFFSET out of the stringlibrary of the file.
179            
180             Used for key names
181            
182             =cut
183            
184             #my $string = string($offset)
185             #Returns a string of stringlib, which is a compilation of \0 strings starting a $offset.
186             #Returns string without the trailing \0.
187            
188             sub string {
189 32 50   32 1 64 if ($_[0] >= length $strings) { #THIS IS AN ERROR IN THE UTF FILE
190 0         0 warnings::warnif("Requested a string from outside of the string lib, this is an error in the file or something the parser doesn't know about.");
191 0         0 return "" ;
192             }
193 32         48 my $shift=index($strings,"\0",$_[0]) - $_[0];
194 32         150 return substr($strings,$_[0],$shift);
195             }
196            
197             =head2 data (OFFSET, LENGTH)
198            
199             Extracts data from an OFFSET with specific LENGTH out of the datalibrary of the file.
200            
201             Used for data nodes
202            
203             =cut
204            
205             #my $data=data($start,$length);
206             #Returns a string of the datalib, which is just a bunch of data, starting at $start with a length of $length.
207            
208             sub data {
209 17 50   17 1 33 if ($_[0] >= length $datas) {#THIS IS AN ERROR IN THE UTF FILE
210 0         0 warnings::warnif("Requested data from outside of the data lib, this is an error in the file or something the parser doesn't know about.");
211 0         0 return "" ;
212             }
213 17         42 return substr($datas,$_[0],$_[1]);
214             }
215            
216            
217             =head2 UTFreadUTFrek( TREE, NODEID )
218            
219             Parses a node with NODEID out of the binary TREE, then calls itself with all the childnodes and siblingnodes
220            
221             =cut
222            
223             #Parses a UTF node recursive:
224             #node
225             #{
226             # dword sibling_offset
227             # dword string_offset
228             # dword flags
229             # dword zero (seems to be always zero, meaning unknown)
230             # dword child_offset
231             # dword allocated_size
232             # dword size1
233             # dword size2
234             # dword time1
235             # dword time2
236             # dword time3
237             #} = 44 bytes
238            
239            
240            
241             sub UTFreadUTFrek {
242 1     1   5 no warnings 'recursion';
  1         2  
  1         442  
243 32     32 1 30 local $_;
244 32         36 my $tree=shift;
245 32         31 my $i=shift;
246 32 50       98 if ($offsets{$i}++) {
247 0         0 warnings::warnif("Somehow the file managed to request the same node again, this is ignored");
248 0         0 return {};
249             }
250 32 50       66 if ($i > length($tree)-44) {
251 0         0 warnings::warnif("Requested a node from outside of the TREE, this is an error in the file or something the parser doesn't know about.");
252 0         0 return {};
253             }
254 32         108 tie my %data => 'Tie::InsertOrderHash';
255 32         301 my ($silb, $name, $flags, $z, $childoffset, $alloc, $size, $size2, $time1, $time2, $time3) = unpack("VVVVVVVVVVV", substr($tree,$i));
256             # Now we must use all the stuff here or warning will annoy us:
257 32         49 ($time1, $time2, $time3) = ($time1, $time2, $time3);
258 32 50       61 $size=$size2 if ($size2 < $size);
259 32 100 66     101 if ($flags & 0x10 and not $flags & 0x80) {
260 15         46 $data{string($name)}=UTFreadUTFrek($tree,$childoffset);
261             }
262             else {
263 17         27 $data{string($name)}=data($childoffset,$size);
264             }
265 32 100       356 if ($silb) {
266 16         42 my $data=UTFreadUTFrek($tree,$silb);
267             #print $data,"->$i-->$silb\n";
268 16         54 %data=(%data,%{$data});
  16         236  
269             }
270 32         1094 return \%data;
271            
272             }
273            
274             =head2 packString (STRING)
275            
276             Saves a STRING to the stringlib and returns an offset. Tests if the string already exists.
277            
278             Used for writing Nodenames (keys)
279            
280             =cut
281            
282             #$offset = packString($string)
283            
284             #Packs a string and returns the offset.
285             #Also adds the string to the stringlib
286             sub packString {
287 32     32 1 39 my $string = shift;
288 32 100       54 if (exists $strings{$string}) {
289 7         20 return $strings{$string};
290             }
291             else {
292 25         49 $strings{$string} = length($strings);
293 25         37 $strings.=$string."\0";
294 25         173 return $strings{$string};
295             }
296            
297             }
298            
299             =head2 packData (DATA)
300            
301             Saves a string with DATA to the datalib and returns an offset. Tests if the data already exists.
302            
303             Used for writing nodedata (values)
304            
305             =cut
306            
307             #$offset = packData($data)
308            
309             #Packs a piece of data and returns the offset.
310             #Also adds the data to the datalib
311             sub packData {
312 17     17 1 22 my $data = shift;
313 17 100       33 if (exists $datas{$data}) {
314 3         26 return $datas{$data};
315             }
316             else {
317 14         577 $datas{$data} = length($datas);
318             #$datas.=$data."\0";
319 14         23 $datas.=$data;
320 14         83 return $datas{$data};
321             }
322            
323             }
324            
325             #Writes UTF nodes recursive.
326            
327             =head2 UTFwriteUTFrek (DATA, NAME, SIBLING)
328            
329             Writes hashref or a scalar into the tree.
330            
331             SIBLING is true if there is a next sibling node (different output on nodes without a next one)
332            
333             Calls itself again for each entry of a hashref. (Writes the children)
334            
335             =cut
336            
337             sub UTFwriteUTFrek {
338 1     1   5 no warnings 'recursion';
  1         2  
  1         667  
339 32     32 1 35 local $_;
340 32         42 my $tree=shift;
341 32         74 my $name=shift;
342 32         40 my $silb=shift;
343 32         37 $pointer+=44;
344 32         29 my $start=$pointer;
345 32 50 66     107 croak "Can't pack other then scalar or Hashrefs" if ref $tree and ref $tree ne "HASH";
346 32 100       88 return pack ("VVVVVVVVVVV",$silb?$pointer:0, packString($name), 0x80, 0, packData($tree), length($tree), length($tree), length($tree), 0, 0, 0) unless ref $tree;
    100          
347 15         22 my $code="";
348 15         50 my @list = keys(%$tree);
349             #print "name = $name, pointer = $pointer, data = ".(ref ($tree) || "scalar")."\n";
350 15         35 foreach (0 .. $#list) {
351 31         172 $code.=UTFwriteUTFrek($tree->{$list[$_]},$list[$_],($_!=$#list));
352             }
353             #print "name = $name, pointer = $pointer, data = ".(ref ($tree) || "scalar")."\n";
354 15 100       43 return pack ("VVVVVVVVVVV",$silb?$pointer:0, packString($name), 0x10, 0, $start, 0,0,0,0,0,0).$code;
355             }
356            
357            
358             #header
359             #{
360             # dword "UTF "
361             # dword 0x101
362             # dword tree_segment_offset
363             # dword size_of_tree_segment
364             # dword header_offset? (0) ##I think its here also a First element of the treeoffset
365             # dword size_of_header (44) ##I think it is more a size of entry
366             # dword string_segment_offset
367             # dword space_allocated_for_string_segment
368             # dword size_of_string_segment_actually_used
369             # dword data_segment_offset
370             # dword unknown (seems to be zero most of the times) #Possible first entry of data segment (after deletion of an entry)
371             #} = 44 bytes
372            
373             #Reads the UTF header, extracts data and string libraries and starts parsing the nodes
374            
375             =head2 UTFreadUTF ( DATA )
376            
377             Extracts and parses an UTF header from the scalar DATA.
378            
379             Splits the file in TREE, STRINGLIB and DATALIB according to the header and then calls UTFreadUTFrek on the TREE.
380            
381             =cut
382            
383             sub UTFreadUTF{
384 1     1 1 2 my $code=shift;
385 1         2 my $i=0;
386 1         2 %offsets = ();
387 1 50       5 if (substr($code,$i,4) eq "UTF ") {
388 1         2 $i+=4;
389 1         4 my ($ver,$treeoffset,$treesize,$treefirst,$treeelemsize,$stringoffset,$stringspace,$stringsize,$dataoffset,$datafirst)=unpack("VVVVVVVVVV",get($code,$i,40));
390             # We don't use those now, not sure what they are for anyway
391 1 50       3 $datafirst = 0 unless $datafirst;
392 1 50       3 $treefirst = 0 unless $treefirst;
393 1 50       2 $treeelemsize = 0 unless $treeelemsize;
394             # We don't need this one either, do we?
395 1 50       2 $stringsize = 0 unless $stringsize;
396             # Splitting the parts
397 1         2 $strings=substr($code,$stringoffset,$stringspace);
398 1         3 $datas=substr($code,$dataoffset);
399 1         3 my $tree=substr($code,$treeoffset,$treesize);
400 1         4 return UTFreadUTFrek($tree,0);
401             }
402             else {
403 0         0 croak "NOT a UTF File";
404             }
405            
406             }
407            
408             #Writes an UTF file with header and nodes.
409            
410             =head2 UTFwriteUTF (TREE(HASHREF) )
411            
412             Calls UTFwriteUTFrek and then return the header, TREE, STRINGLIB and DATALIB
413            
414             =cut
415            
416             sub UTFwriteUTF{
417 1     1 1 3 my $tree=shift;
418 1         3 my $i=0;
419 1         2 $strings="";
420 1         1 $datas="";
421 1         2 my $code = "";
422 1         3 %strings = ();
423 1         2 %datas = ();
424 1         2 $pointer=0;
425 1         5 my @list = keys(%$tree);
426 1         4 foreach (0 .. $#list) {
427 1         7 $code.=UTFwriteUTFrek($tree->{$list[$_]},$list[$_],($_!=$#list));
428             }
429 1         15 my $string=$strings;
430 1         11 $string.="\0" for(length($strings) .. (int(length($strings)/32)+1)*32); #Just some fun stuff.
431 1         10 return "UTF ".pack("VVVVVVVVVV",0x101,44+12,length($code),0,44,44+12+length($code),length($string),length($strings),44+12+length($code)+length($string),0)."000000000000".$code.$string.$datas;
432            
433             }
434            
435             1;
436            
437             __END__