| Games-Nintendo-Wii-Mii documentation | Contained in the Games-Nintendo-Wii-Mii distribution. |
Games::Nintendo::Wii::Mii - Mii in Nintendo Wii data parser and builder.
version 0.02
use Games::Nintendo::Wii::Mii;
my $mii = Games::Nintendo::Wii::Mii->new;
$mii->parse_from_file('zigorou.mii');
$mii->profile->name("ZIGOROU");
$mii->profile->creator_name("TORU");
$mii->save_to_file("new_zigorou.mii");
print $mii->to_xml();
Constructor.
Parse mii data from mii binary file.
Parse mii data from mii binary.
Parse mii data from mii binary hexdump.
Parse mii data from xml file.
Parse mii data from xml string.
Parse mii data from xml document object (See XML::LibXML::Document)
Save mii binary to file.
Save mii xml to file.
To binary data.
To hexdump.
To xml.
To online editable url powered by MiiEditor http://www.miieditor.com/
To online viewable url powered by MiiEditor http://www.miieditor.com/
Toru Yamaguchi, <zigorou@cpan.org>
Describe mii data format, see below.
http://wiibrew.org/index.php?title=Wiimote/Mii_Data
Online mii data editor created by flash and php.
http://www.miieditor.com/
This module use DTD and editor, viewer created by miieditor.com. Thanks a lot.
Please report any bugs or feature requests to
bug-games-nintendo-wii-mii@rt.cpan.org, or through the web interface at
http://rt.cpan.org. I will be notified, and then you'll automatically be
notified of progress on your bug as I make changes.
Copyright 2007 Toru Yamaguchi, All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Games-Nintendo-Wii-Mii documentation | Contained in the Games-Nintendo-Wii-Mii distribution. |
package Games::Nintendo::Wii::Mii; use strict; use warnings; use utf8; use base qw(Class::Accessor::Fast); use Carp qw(croak); use Encode; use File::Slurp qw(slurp); use IO::File; use Readonly; use Tie::IxHash; use URI; use XML::LibXML; use Games::Nintendo::Wii::Mii::Data::BeardMustache; use Games::Nintendo::Wii::Mii::Data::Eye; use Games::Nintendo::Wii::Mii::Data::Eyebrow; use Games::Nintendo::Wii::Mii::Data::Face; use Games::Nintendo::Wii::Mii::Data::Figure; use Games::Nintendo::Wii::Mii::Data::Glasses; use Games::Nintendo::Wii::Mii::Data::Hair; use Games::Nintendo::Wii::Mii::Data::Mole; use Games::Nintendo::Wii::Mii::Data::Mouth; use Games::Nintendo::Wii::Mii::Data::Nose; use Games::Nintendo::Wii::Mii::Data::Profile; Readonly our @ACCESSORS => qw/ beard_mustache eye eyebrow face figure glasses hair mole mouth nose profile /; Readonly our $TYPE_UNKNOWN => 0; Readonly our $TYPE_INTEGER => 1; Readonly our $TYPE_STRING => 2; Readonly our $TYPE_HEXADECIMAL => 3; __PACKAGE__->mk_accessors(@ACCESSORS); tie( our %STRUCT, 'Tie::IxHash', ( invalid => { size => 1, type => $TYPE_INTEGER, accessor => 'profile' }, gender => { size => 1, type => $TYPE_INTEGER, accessor => 'profile', name => 'gender', min => 0, max => 1 }, birth_month => { size => 4, type => $TYPE_INTEGER, accessor => 'profile', name => 'birthMonth', min => 0, max => 12 }, birth_date => { size => 5, type => $TYPE_INTEGER, accessor => 'profile', name => 'birthDate', min => 0, max => 31 }, favorite_color => { size => 4, type => $TYPE_INTEGER, accessor => 'profile', name => 'favoriteColor', min => 0, max => 11 }, unknown_1 => { size => 1, type => $TYPE_UNKNOWN }, name => { size => 160, type => $TYPE_STRING, accessor => 'profile' }, height => { size => 8, type => $TYPE_INTEGER, accessor => 'figure', name => 'height', min => 0, max => 127 }, weight => { size => 8, type => $TYPE_INTEGER, accessor => 'figure', name => 'weight', min => 0, max => 127 }, mii_id => { size => 32, type => $TYPE_HEXADECIMAL, accessor => 'profile', name => 'id', min => '00-00-00-00', max => 'FF-FF-FF-FF' }, system_id_checksum8 => { size => 8, type => $TYPE_HEXADECIMAL, accessor => 'profile', min => '00', max => 'FF' }, system_id => { size => 24, type => $TYPE_HEXADECIMAL, accessor => 'profile', name => 'wii', min => '00-00-00', max => 'FF-FF-FF' }, face_shape => { size => 3, type => $TYPE_INTEGER, accessor => 'face', name => 'headType', min => 0, max => 7 }, skin_color => { size => 3, type => $TYPE_INTEGER, accessor => 'face', name => 'skinColor', min => 0, max => 5 }, facial_feature => { size => 4, type => $TYPE_INTEGER, accessor => 'face', name => 'facialFeaturesType', min => 0, max => 15 }, unknown_2 => { size => 3, type => $TYPE_UNKNOWN }, mingle => { size => 1, type => $TYPE_INTEGER, accessor => 'profile', name => 'mingles', min => 0, max => 1 }, unknown_3 => { size => 2, type => $TYPE_UNKNOWN }, hair_type => { size => 7, type => $TYPE_INTEGER, accessor => 'hair', name => 'hairType', min => 0, max => 71 }, hair_color => { size => 3, type => $TYPE_INTEGER, accessor => 'hair', name => 'hairColor', min => 0, max => 7 }, hair_part => { size => 1, type => $TYPE_INTEGER, accessor => 'hair', name => 'hairPart', min => 0, max => 1 }, unknown_4 => { size => 5, type => $TYPE_UNKNOWN }, eyebrow_type => { size => 5, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowType', min => 0, max => 23 }, unknown_5 => { size => 1, type => $TYPE_UNKNOWN }, eyebrow_rotation => { size => 4, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowRotation', min => 0, max => 11 }, unknown_6 => { size => 6, type => $TYPE_UNKNOWN }, eyebrow_color => { size => 3, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowColor', min => 0, max => 7 }, eyebrow_size => { size => 4, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowSize', min => 0, max => 8 }, eyebrow_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowY', min => 0, max => 15 }, eyebrow_horizon_spacing => { size => 4, type => $TYPE_INTEGER, accessor => 'eyebrow', name => 'eyebrowX', min => 0, max => 12 }, eye_type => { size => 6, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeType', min => 0, max => 47 }, unknown_7 => { size => 2, type => $TYPE_UNKNOWN }, eye_rotation => { size => 3, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeRotation', min => 0, max => 7 }, eye_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeY', min => 0, max => 18 }, eye_color => { size => 3, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeColor', min => 0, max => 5 }, unknown_8 => { size => 1, type => $TYPE_UNKNOWN }, eye_size => { size => 3, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeSize', min => 0, max => 7 }, eye_horizon_spacing => { size => 4, type => $TYPE_INTEGER, accessor => 'eye', name => 'eyeX', min => 0, max => 12 }, unknown_9 => { size => 5, type => $TYPE_UNKNOWN }, nose_type => { size => 4, type => $TYPE_INTEGER, accessor => 'nose', name => 'noseType', min => 0, max => 11 }, nose_size => { size => 4, type => $TYPE_INTEGER, accessor => 'nose', name => 'noseSize', min => 0, max => 8 }, nose_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'nose', name => 'noseY', min => 0, max => 18 }, unknown_10 => { size => 3, type => $TYPE_UNKNOWN }, mouth_type => { size => 5, type => $TYPE_INTEGER, accessor => 'mouth', name => 'mouthType', min => 0, max => 23 }, mouth_color => { size => 2, type => $TYPE_INTEGER, accessor => 'mouth', name => 'mouthColor', min => 0, max => 2 }, mouth_size => { size => 4, type => $TYPE_INTEGER, accessor => 'mouth', name => 'mouthSize', min => 0, max => 8 }, mouth_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'mouth', name => 'mouthY', min => 0, max => 18 }, glasses_type => { size => 4, type => $TYPE_INTEGER, accessor => 'glasses', name => 'glassesType', min => 0, max => 8 }, glasses_color => { size => 3, type => $TYPE_INTEGER, accessor => 'glasses', name => 'glassesColor', min => 0, max => 5 }, unknown_11 => { size => 1, type => $TYPE_UNKNOWN }, glasses_size => { size => 3, type => $TYPE_INTEGER, accessor => 'glasses', name => 'glassesSize', min => 0, max => 7 }, glasses_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'glasses', name => 'glassesY', min => 0, max => 20 }, mustache_type => { size => 2, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'mustacheType', min => 0, max => 3 }, beard_type => { size => 2, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'beardType', min => 0, max => 3 }, facial_hair_color => { size => 3, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'facialHairColor', min => 0, max => 7 }, mustache_size => { size => 4, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'mustacheSize', min => 0, max => 8 }, unknown_12 => { size => 1, type => $TYPE_UNKNOWN }, mustache_vertical_position => { size => 4, type => $TYPE_INTEGER, accessor => 'beard_mustache', name => 'mustacheY', min => 0, max => 16 }, mole_on => { size => 1, type => $TYPE_INTEGER, accessor => 'mole', name => 'moleType', min => 0, max => 1 }, mole_size => { size => 4, type => $TYPE_INTEGER, accessor => 'mole', name => 'moleSize', min => 0, max => 15 }, mole_vertical_position => { size => 5, type => $TYPE_INTEGER, accessor => 'mole', name => 'moleX', min => 0, max => 30 }, mole_horizon_position => { size => 5, type => $TYPE_INTEGER, accessor => 'mole', name => 'moleY', min => 0, max => 16 }, unknown_13 => { size => 1, type => $TYPE_UNKNOWN }, creator_name => { size => 160, type => $TYPE_STRING, accessor => 'profile' } ) );
our $VERSION = '0.02';
sub new { my $class = shift; my $prefix = "Games::Nintendo::Wii::Mii::Data::"; my $self = $class->SUPER::new(); for my $accessor (@ACCESSORS) { my $package = $prefix . join("", map { ucfirst } split(/_/, $accessor)); $self->$accessor($package->new); } return $self; }
sub parse_from_file { my ($self, $filename) = @_; ### TODO : validation $self->parse_from_binary(slurp($filename)); }
sub parse_from_binary { my ($self, $binary) = @_; my $bits = unpack("B*", $binary); my %data = (); my $index = 0; foreach my $key (keys %STRUCT) { $data{$key} = substr($bits, $index, $STRUCT{$key}->{size}); my $type = $STRUCT{$key}->{type}; if ($type == $TYPE_INTEGER) { $data{$key} = oct("0b$data{$key}"); } elsif ($type == $TYPE_STRING) { $data{$key} = decode("UTF16BE", pack("B*", $data{$key})); $data{$key} =~ s/\x00*$//; ### erase end of spaces and null bytes } elsif ($type == $TYPE_HEXADECIMAL) { $data{$key} = join("-", map { uc } unpack("H2" x ( $STRUCT{$key}->{size} / 8), pack("B*", $data{$key}))); } else { ### unknown data } $index += $STRUCT{$key}->{size}; } ### TODO : validation for my $part (@ACCESSORS) { no strict 'refs'; for my $accessor (@{ref($self->$part()) . "::ACCESSORS"}) { $self->$part->$accessor($data{$accessor}); } } return 1; }
sub parse_from_hex { my ($self, $hex) = @_; $self->parse_from_binary(pack("H*", $hex)); }
sub parse_from_xml_file { my ($self, $xml_file) = @_; my $parser = XML::LibXML->new; my $doc = $parser->parse_file($xml_file); $self->parse_from_xml($doc); }
sub parse_from_xml_string { my ($self, $xml_string) = @_; my $parser = XML::LibXML->new; my $doc = $parser->parse_string($xml_string); $self->parse_from_xml($doc); }
sub parse_from_xml { my ($self, $doc) = @_; my $xpc = XML::LibXML::XPathContext->new($doc); croak("Not valid mii's xml") unless ($xpc->find('//mii[@value]/@value')); $self->parse_from_hex($xpc->find('//mii[@value]/@value')); }
sub save_to_file { my ($self, $filename) = @_; my $fh = IO::File->new("> $filename") || croak(qq|Can't open file : | . $filename); syswrite($fh, $self->to_binary()); $fh->close; }
sub save_to_xml_file { my ($self, $filename) = @_; my $fh = IO::File->new("> $filename") || croak(qq|Can't open file : | . $filename); print $fh $self->to_xml; $fh->close; }
sub to_binary { my $self = shift; pack("H*", $self->to_hexdump); }
sub to_hexdump { my $self = shift; my $bits = ''; for my $key (keys %STRUCT) { my $type = $STRUCT{$key}->{type}; if ($type == $TYPE_UNKNOWN) { $bits .= '0' x $STRUCT{$key}->{size}; next; } my $accessor = $STRUCT{$key}->{accessor}; ### TODO : adhoc warn("$key is not defined accessor") unless ($accessor); my $value = $self->$accessor->$key(); my $size = $STRUCT{$key}->{size}; if ($type == $TYPE_INTEGER) { $bits .= substr(unpack("B8", pack("C", $value)), 8 - $size, $size); } elsif ($type == $TYPE_STRING) { my $strhex = unpack("H*", encode("UTF16BE", $value)); $strhex .= '0' x (($size / 8 * 2) - length $strhex); $bits .= unpack("B*", pack("H*", $strhex)); } else { ### $TYPE_HEXADECIMAL my @pieces = split(/-/, $value); $bits .= substr(unpack("B*", pack("H2" x (scalar @pieces), @pieces)), 8 * (scalar @pieces) - $size, $size); } } return unpack("H*", pack("B*", $bits)); }
sub to_xml { my $self = shift; my $doc = XML::LibXML::Document->new('1.0'); my $pinode = $doc->createProcessingInstruction('xml-stylesheet'); $pinode->setData(type => 'text/xsl', href => 'http://www.miieditor.com/xml/mii.xsl'); $doc->appendChild($pinode); my $root = $doc->createElement('mii-collection'); $doc->setDocumentElement($root); my $dtd = $doc->createInternalSubset($root->tagName, undef, 'http://www.miieditor.com/xml/mii.dtd'); $doc->setInternalSubset($dtd); my $mii_element = $doc->createElement('mii'); $mii_element->setAttribute('value', $self->to_hexdump); $root->appendChild($mii_element); my $name_element = $doc->createElement('name'); my $creator_element = $doc->createElement('creator'); $name_element->setAttribute('maxChars', 10); $creator_element->setAttribute('maxChars', 10); $name_element->appendText(encode('utf8', $self->profile->name)); $creator_element->appendText(encode('utf8', $self->profile->creator_name)); $mii_element->appendChild($name_element); $mii_element->appendChild($creator_element); my $data_element = $doc->createElement('data'); my %formats = ( $TYPE_INTEGER => 'integer', $TYPE_HEXADECIMAL => 'hexadecimal' ); { no strict 'refs'; for my $key (sort { $STRUCT{$a}->{name} cmp $STRUCT{$b}->{name} } grep {exists $STRUCT{$_}->{name}} keys %STRUCT) { my $data_clone = $data_element->cloneNode(); my $accessor = $STRUCT{$key}->{accessor}; $data_clone->setAttribute('name', $STRUCT{$key}->{name}); $data_clone->setAttribute('value', $self->$accessor->$key); $data_clone->setAttribute('format', $formats{$STRUCT{$key}->{type}}); $data_clone->setAttribute('min', $STRUCT{$key}->{min}); $data_clone->setAttribute('max', $STRUCT{$key}->{max}); $mii_element->appendChild($data_clone); } } return $doc->toString(1); }
sub to_edit_url { my $self = shift; my $uri = URI->new("http://miieditor.com/"); $uri->query_form( mii => $self->to_hexdump ); return $uri->as_string; }
sub to_view_url { my $self = shift; my $uri = URI->new("http://www.miieditor.com/"); $uri->path('view.php'); $uri->query_form( mii => $self->to_hexdump ); return $uri->as_string; }
1; # End of Games::Nintendo::Wii::Mii __END__