File Coverage

blib/lib/Games/Nintendo/Wii/Mii.pm
Criterion Covered Total %
statement 34 36 94.4
branch n/a
condition n/a
subroutine 12 12 100.0
pod n/a
total 46 48 95.8


line stmt bran cond sub pod time code
1             package Games::Nintendo::Wii::Mii;
2              
3 3     3   1896 use strict;
  3         7  
  3         282  
4 3     3   18 use warnings;
  3         7  
  3         94  
5 3     3   3703 use utf8;
  3         29  
  3         15  
6              
7 3     3   119 use base qw(Class::Accessor::Fast);
  3         6  
  3         2994  
8              
9 3     3   12785 use Carp qw(croak);
  3         6  
  3         227  
10 3     3   3613 use Encode;
  3         43965  
  3         313  
11 3     3   3572 use File::Slurp qw(slurp);
  3         71823  
  3         329  
12 3     3   3179 use IO::File;
  3         53603  
  3         615  
13 3     3   3619 use Readonly;
  3         9981  
  3         171  
14 3     3   2912 use Tie::IxHash;
  3         11209  
  3         97  
15 3     3   3832 use URI;
  3         23154  
  3         94  
16 3     3   1370 use XML::LibXML;
  0            
  0            
17              
18             use Games::Nintendo::Wii::Mii::Data::BeardMustache;
19             use Games::Nintendo::Wii::Mii::Data::Eye;
20             use Games::Nintendo::Wii::Mii::Data::Eyebrow;
21             use Games::Nintendo::Wii::Mii::Data::Face;
22             use Games::Nintendo::Wii::Mii::Data::Figure;
23             use Games::Nintendo::Wii::Mii::Data::Glasses;
24             use Games::Nintendo::Wii::Mii::Data::Hair;
25             use Games::Nintendo::Wii::Mii::Data::Mole;
26             use Games::Nintendo::Wii::Mii::Data::Mouth;
27             use Games::Nintendo::Wii::Mii::Data::Nose;
28             use Games::Nintendo::Wii::Mii::Data::Profile;
29              
30             Readonly our @ACCESSORS => qw/
31             beard_mustache
32             eye
33             eyebrow
34             face
35             figure
36             glasses
37             hair
38             mole
39             mouth
40             nose
41             profile
42             /;
43              
44             Readonly our $TYPE_UNKNOWN => 0;
45             Readonly our $TYPE_INTEGER => 1;
46             Readonly our $TYPE_STRING => 2;
47             Readonly our $TYPE_HEXADECIMAL => 3;
48              
49             __PACKAGE__->mk_accessors(@ACCESSORS);
50              
51             tie(
52             our %STRUCT,
53             'Tie::IxHash',
54             (
55             invalid => { size => 1, type => $TYPE_INTEGER, accessor => 'profile' },
56             gender => { size => 1, type => $TYPE_INTEGER, accessor => 'profile', name => 'gender', min => 0, max => 1 },
57             birth_month => { size => 4, type => $TYPE_INTEGER, accessor => 'profile', name => 'birthMonth', min => 0, max => 12 },
58             birth_date => { size => 5, type => $TYPE_INTEGER, accessor => 'profile', name => 'birthDate', min => 0, max => 31 },
59             favorite_color => { size => 4, type => $TYPE_INTEGER, accessor => 'profile', name => 'favoriteColor', min => 0, max => 11 },
60             unknown_1 => { size => 1, type => $TYPE_UNKNOWN },
61              
62             name => { size => 160, type => $TYPE_STRING, accessor => 'profile' },
63             height => { size => 8, type => $TYPE_INTEGER, accessor => 'figure', name => 'height', min => 0, max => 127 },
64             weight => { size => 8, type => $TYPE_INTEGER, accessor => 'figure', name => 'weight', min => 0, max => 127 },
65             mii_id => { size => 32, type => $TYPE_HEXADECIMAL, accessor => 'profile', name => 'id', min => '00-00-00-00', max => 'FF-FF-FF-FF' },
66             system_id_checksum8 => { size => 8, type => $TYPE_HEXADECIMAL, accessor => 'profile', min => '00', max => 'FF' },
67             system_id => { size => 24, type => $TYPE_HEXADECIMAL, accessor => 'profile', name => 'wii', min => '00-00-00', max => 'FF-FF-FF' },
68             face_shape => { size => 3, type => $TYPE_INTEGER, accessor => 'face', name => 'headType', min => 0, max => 7 },
69             skin_color => { size => 3, type => $TYPE_INTEGER, accessor => 'face', name => 'skinColor', min => 0, max => 5 },
70             facial_feature => { size => 4, type => $TYPE_INTEGER, accessor => 'face', name => 'facialFeaturesType', min => 0, max => 15 },
71             unknown_2 => { size => 3, type => $TYPE_UNKNOWN },
72             mingle => { size => 1, type => $TYPE_INTEGER, accessor => 'profile', name => 'mingles', min => 0, max => 1 },
73             unknown_3 => { size => 2, type => $TYPE_UNKNOWN },
74            
75             hair_type => { size => 7, type => $TYPE_INTEGER, accessor => 'hair', name => 'hairType', min => 0, max => 71 },
76             hair_color => { size => 3, type => $TYPE_INTEGER, accessor => 'hair', name => 'hairColor', min => 0, max => 7 },
77             hair_part => { size => 1, type => $TYPE_INTEGER, accessor => 'hair', name => 'hairPart', min => 0, max => 1 },
78             unknown_4 => { size => 5, type => $TYPE_UNKNOWN },
79            
80             eyebrow_type => { size => 5, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowType', min => 0, max => 23 },
81             unknown_5 => { size => 1, type => $TYPE_UNKNOWN },
82             eyebrow_rotation => { size => 4, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowRotation', min => 0, max => 11 },
83             unknown_6 => { size => 6, type => $TYPE_UNKNOWN },
84             eyebrow_color => { size => 3, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowColor', min => 0, max => 7 },
85             eyebrow_size => { size => 4, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowSize', min => 0, max => 8 },
86             eyebrow_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowY', min => 0, max => 15 },
87             eyebrow_horizon_spacing => { size => 4, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowX', min => 0, max => 12 },
88            
89             eye_type => { size => 6, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeType', min => 0, max => 47 },
90             unknown_7 => { size => 2, type => $TYPE_UNKNOWN },
91             eye_rotation => { size => 3, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeRotation', min => 0, max => 7 },
92             eye_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeY', min => 0, max => 18 },
93             eye_color => { size => 3, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeColor', min => 0, max => 5 },
94             unknown_8 => { size => 1, type => $TYPE_UNKNOWN },
95             eye_size => { size => 3, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeSize', min => 0, max => 7 },
96             eye_horizon_spacing => { size => 4, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeX', min => 0, max => 12 },
97             unknown_9 => { size => 5, type => $TYPE_UNKNOWN },
98            
99             nose_type => { size => 4, type => $TYPE_INTEGER, accessor => 'nose', name => 'noseType', min => 0, max => 11 },
100             nose_size => { size => 4, type => $TYPE_INTEGER, accessor => 'nose', name => 'noseSize', min => 0, max => 8 },
101             nose_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'nose', name => 'noseY', min => 0, max => 18 },
102             unknown_10 => { size => 3, type => $TYPE_UNKNOWN },
103            
104             mouth_type => { size => 5, type => $TYPE_INTEGER, accessor => 'mouth', name => 'mouthType', min => 0, max => 23 },
105             mouth_color => { size => 2, type => $TYPE_INTEGER, accessor => 'mouth', name => 'mouthColor', min => 0, max => 2 },
106             mouth_size => { size => 4, type => $TYPE_INTEGER, accessor => 'mouth', name => 'mouthSize', min => 0, max => 8 },
107             mouth_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'mouth', name => 'mouthY', min => 0, max => 18 },
108            
109             glasses_type => { size => 4, type => $TYPE_INTEGER, accessor => 'glasses', name => 'glassesType', min => 0, max => 8 },
110             glasses_color => { size => 3, type => $TYPE_INTEGER, accessor => 'glasses', name => 'glassesColor', min => 0, max => 5 },
111             unknown_11 => { size => 1, type => $TYPE_UNKNOWN },
112             glasses_size => { size => 3, type => $TYPE_INTEGER, accessor => 'glasses', name => 'glassesSize', min => 0, max => 7 },
113             glasses_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'glasses', name => 'glassesY', min => 0, max => 20 },
114            
115             mustache_type => { size => 2, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'mustacheType', min => 0, max => 3 },
116             beard_type => { size => 2, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'beardType', min => 0, max => 3 },
117             facial_hair_color => { size => 3, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'facialHairColor', min => 0, max => 7 },
118             mustache_size => { size => 4, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'mustacheSize', min => 0, max => 8 },
119             unknown_12 => { size => 1, type => $TYPE_UNKNOWN },
120             mustache_vertical_position => { size => 4, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'mustacheY', min => 0, max => 16 },
121            
122             mole_on => { size => 1, type => $TYPE_INTEGER, accessor => 'mole', name => 'moleType', min => 0, max => 1 },
123             mole_size => { size => 4, type => $TYPE_INTEGER, accessor => 'mole', name => 'moleSize', min => 0, max => 15 },
124             mole_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'mole', name => 'moleX', min => 0, max => 30 },
125             mole_horizon_position => { size => 5, type => $TYPE_INTEGER, accessor => 'mole', name => 'moleY', min => 0, max => 16 },
126             unknown_13 => { size => 1, type => $TYPE_UNKNOWN },
127            
128             creator_name => { size => 160, type => $TYPE_STRING, accessor => 'profile' }
129             )
130             );
131              
132             =head1 NAME
133              
134             Games::Nintendo::Wii::Mii - Mii in Nintendo Wii data parser and builder.
135              
136             =head1 VERSION
137              
138             version 0.02
139              
140             =cut
141              
142             our $VERSION = '0.02';
143              
144             =head1 SYNOPSIS
145              
146             use Games::Nintendo::Wii::Mii;
147            
148             my $mii = Games::Nintendo::Wii::Mii->new;
149            
150             $mii->parse_from_file('zigorou.mii');
151             $mii->profile->name("ZIGOROU");
152             $mii->profile->creator_name("TORU");
153             $mii->save_to_file("new_zigorou.mii");
154             print $mii->to_xml();
155              
156             =head1 METHODS
157              
158             =head2 new()
159              
160             Constructor.
161              
162             =cut
163              
164             sub new {
165             my $class = shift;
166              
167             my $prefix = "Games::Nintendo::Wii::Mii::Data::";
168             my $self = $class->SUPER::new();
169            
170             for my $accessor (@ACCESSORS) {
171             my $package = $prefix . join("", map { ucfirst } split(/_/, $accessor));
172             $self->$accessor($package->new);
173             }
174              
175             return $self;
176             }
177              
178             =head2 parse_from_file($filename)
179              
180             Parse mii data from mii binary file.
181              
182             =cut
183              
184             sub parse_from_file {
185             my ($self, $filename) = @_;
186              
187             ### TODO : validation
188              
189             $self->parse_from_binary(slurp($filename));
190             }
191              
192             =head2 parse_from_binary($binary)
193              
194             Parse mii data from mii binary.
195              
196             =cut
197              
198             sub parse_from_binary {
199             my ($self, $binary) = @_;
200              
201             my $bits = unpack("B*", $binary);
202             my %data = ();
203             my $index = 0;
204              
205             foreach my $key (keys %STRUCT) {
206             $data{$key} = substr($bits, $index, $STRUCT{$key}->{size});
207              
208             my $type = $STRUCT{$key}->{type};
209              
210             if ($type == $TYPE_INTEGER) {
211             $data{$key} = oct("0b$data{$key}");
212             }
213             elsif ($type == $TYPE_STRING) {
214             $data{$key} = decode("UTF16BE", pack("B*", $data{$key}));
215             $data{$key} =~ s/\x00*$//; ### erase end of spaces and null bytes
216             }
217             elsif ($type == $TYPE_HEXADECIMAL) {
218             $data{$key} = join("-", map { uc } unpack("H2" x ( $STRUCT{$key}->{size} / 8), pack("B*", $data{$key})));
219             }
220             else {
221             ### unknown data
222             }
223              
224             $index += $STRUCT{$key}->{size};
225             }
226              
227             ### TODO : validation
228              
229             for my $part (@ACCESSORS) {
230             no strict 'refs';
231             for my $accessor (@{ref($self->$part()) . "::ACCESSORS"}) {
232             $self->$part->$accessor($data{$accessor});
233             }
234             }
235              
236             return 1;
237             }
238              
239             =head2 parse_from_hex($hex)
240              
241             Parse mii data from mii binary hexdump.
242              
243             =cut
244              
245             sub parse_from_hex {
246             my ($self, $hex) = @_;
247              
248             $self->parse_from_binary(pack("H*", $hex));
249             }
250              
251             =head2 parse_from_xml_file($xml_file)
252              
253             Parse mii data from xml file.
254              
255             =cut
256              
257             sub parse_from_xml_file {
258             my ($self, $xml_file) = @_;
259              
260             my $parser = XML::LibXML->new;
261             my $doc = $parser->parse_file($xml_file);
262              
263             $self->parse_from_xml($doc);
264             }
265              
266             =head2 parse_from_xml_string($xml_string)
267              
268             Parse mii data from xml string.
269              
270             =cut
271              
272             sub parse_from_xml_string {
273             my ($self, $xml_string) = @_;
274              
275             my $parser = XML::LibXML->new;
276             my $doc = $parser->parse_string($xml_string);
277              
278             $self->parse_from_xml($doc);
279             }
280              
281             =head2 parse_from_xml($doc)
282              
283             Parse mii data from xml document object (See L)
284              
285             =cut
286              
287             sub parse_from_xml {
288             my ($self, $doc) = @_;
289              
290             my $xpc = XML::LibXML::XPathContext->new($doc);
291             croak("Not valid mii's xml") unless ($xpc->find('//mii[@value]/@value'));
292              
293             $self->parse_from_hex($xpc->find('//mii[@value]/@value'));
294             }
295              
296             =head2 save_to_file($filename)
297              
298             Save mii binary to file.
299              
300             =cut
301              
302             sub save_to_file {
303             my ($self, $filename) = @_;
304              
305             my $fh = IO::File->new("> $filename") || croak(qq|Can't open file : | . $filename);
306             syswrite($fh, $self->to_binary());
307             $fh->close;
308             }
309              
310             =head2 save_to_xml_file($filename)
311              
312             Save mii xml to file.
313              
314             =cut
315              
316             sub save_to_xml_file {
317             my ($self, $filename) = @_;
318              
319             my $fh = IO::File->new("> $filename") || croak(qq|Can't open file : | . $filename);
320             print $fh $self->to_xml;
321             $fh->close;
322             }
323              
324             =head2 to_binary()
325              
326             To binary data.
327              
328             =cut
329              
330             sub to_binary {
331             my $self = shift;
332              
333             pack("H*", $self->to_hexdump);
334             }
335              
336             =head2 to_hexdump()
337              
338             To hexdump.
339              
340             =cut
341              
342             sub to_hexdump {
343             my $self = shift;
344             my $bits = '';
345              
346             for my $key (keys %STRUCT) {
347             my $type = $STRUCT{$key}->{type};
348              
349             if ($type == $TYPE_UNKNOWN) {
350             $bits .= '0' x $STRUCT{$key}->{size};
351             next;
352             }
353              
354             my $accessor = $STRUCT{$key}->{accessor};
355              
356             ### TODO : adhoc
357             warn("$key is not defined accessor") unless ($accessor);
358              
359             my $value = $self->$accessor->$key();
360             my $size = $STRUCT{$key}->{size};
361              
362             if ($type == $TYPE_INTEGER) {
363             $bits .= substr(unpack("B8", pack("C", $value)), 8 - $size, $size);
364             }
365             elsif ($type == $TYPE_STRING) {
366             my $strhex = unpack("H*", encode("UTF16BE", $value));
367             $strhex .= '0' x (($size / 8 * 2) - length $strhex);
368             $bits .= unpack("B*", pack("H*", $strhex));
369             }
370             else { ### $TYPE_HEXADECIMAL
371             my @pieces = split(/-/, $value);
372             $bits .= substr(unpack("B*", pack("H2" x (scalar @pieces), @pieces)), 8 * (scalar @pieces) - $size, $size);
373             }
374             }
375              
376             return unpack("H*", pack("B*", $bits));
377             }
378              
379             =head2 to_xml()
380              
381             To xml.
382              
383             =cut
384              
385             sub to_xml {
386             my $self = shift;
387              
388             my $doc = XML::LibXML::Document->new('1.0');
389             my $pinode = $doc->createProcessingInstruction('xml-stylesheet');
390             $pinode->setData(type => 'text/xsl', href => 'http://www.miieditor.com/xml/mii.xsl');
391             $doc->appendChild($pinode);
392              
393             my $root = $doc->createElement('mii-collection');
394             $doc->setDocumentElement($root);
395              
396             my $dtd = $doc->createInternalSubset($root->tagName, undef, 'http://www.miieditor.com/xml/mii.dtd');
397             $doc->setInternalSubset($dtd);
398              
399             my $mii_element = $doc->createElement('mii');
400             $mii_element->setAttribute('value', $self->to_hexdump);
401             $root->appendChild($mii_element);
402              
403             my $name_element = $doc->createElement('name');
404             my $creator_element = $doc->createElement('creator');
405              
406             $name_element->setAttribute('maxChars', 10);
407             $creator_element->setAttribute('maxChars', 10);
408              
409             $name_element->appendText(encode('utf8', $self->profile->name));
410             $creator_element->appendText(encode('utf8', $self->profile->creator_name));
411              
412             $mii_element->appendChild($name_element);
413             $mii_element->appendChild($creator_element);
414              
415             my $data_element = $doc->createElement('data');
416              
417             my %formats = (
418             $TYPE_INTEGER => 'integer',
419             $TYPE_HEXADECIMAL => 'hexadecimal'
420             );
421              
422             {
423             no strict 'refs';
424              
425             for my $key (sort { $STRUCT{$a}->{name} cmp $STRUCT{$b}->{name} } grep {exists $STRUCT{$_}->{name}} keys %STRUCT) {
426             my $data_clone = $data_element->cloneNode();
427             my $accessor = $STRUCT{$key}->{accessor};
428              
429             $data_clone->setAttribute('name', $STRUCT{$key}->{name});
430             $data_clone->setAttribute('value', $self->$accessor->$key);
431             $data_clone->setAttribute('format', $formats{$STRUCT{$key}->{type}});
432              
433             $data_clone->setAttribute('min', $STRUCT{$key}->{min});
434             $data_clone->setAttribute('max', $STRUCT{$key}->{max});
435              
436             $mii_element->appendChild($data_clone);
437             }
438             }
439              
440             return $doc->toString(1);
441             }
442              
443             =head2 to_edit_url()
444              
445             To online editable url powered by MiiEditor http://www.miieditor.com/
446              
447             =cut
448              
449             sub to_edit_url {
450             my $self = shift;
451              
452             my $uri = URI->new("http://miieditor.com/");
453             $uri->query_form(
454             mii => $self->to_hexdump
455             );
456              
457             return $uri->as_string;
458             }
459              
460             =head2 to_view_url()
461              
462             To online viewable url powered by MiiEditor http://www.miieditor.com/
463              
464             =cut
465              
466             sub to_view_url {
467             my $self = shift;
468              
469             my $uri = URI->new("http://www.miieditor.com/");
470             $uri->path('view.php');
471             $uri->query_form(
472             mii => $self->to_hexdump
473             );
474              
475             return $uri->as_string;
476             }
477              
478             =head1 AUTHOR
479              
480             Toru Yamaguchi, C<< >>
481              
482             =head1 SEE ALSO
483              
484             =over 4
485              
486             =item Mii Data Structure
487              
488             Describe mii data format, see below.
489              
490             http://wiibrew.org/index.php?title=Wiimote/Mii_Data
491              
492             =item Mii Editor
493              
494             Online mii data editor created by flash and php.
495              
496             http://www.miieditor.com/
497              
498             This module use DTD and editor, viewer created by miieditor.com.
499             Thanks a lot.
500              
501             =item L
502              
503             =item L
504              
505             =item L
506              
507             =item L
508              
509             =item L
510              
511             =item L
512              
513             =item L
514              
515             =item L
516              
517             =back
518              
519             =head1 BUGS
520              
521             Please report any bugs or feature requests to
522             C, or through the web interface at
523             L. I will be notified, and then you'll automatically be
524             notified of progress on your bug as I make changes.
525              
526             =head1 COPYRIGHT & LICENSE
527              
528             Copyright 2007 Toru Yamaguchi, All Rights Reserved.
529              
530             This program is free software; you can redistribute it and/or modify it
531             under the same terms as Perl itself.
532              
533             =cut
534              
535             1; # End of Games::Nintendo::Wii::Mii
536              
537             __END__