Skip to content

Instantly share code, notes, and snippets.

@jaames
Created December 12, 2016 15:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaames/9b8e290661d5ac1620d75692831380dc to your computer and use it in GitHub Desktop.
Save jaames/9b8e290661d5ac1620d75692831380dc to your computer and use it in GitHub Desktop.
PHP class for parsing Mii data from the Nintendo Wii
<?php
// ====== Mii Data Parser ======
//
// Based on the documentation found here:
// http://wiibrew.org/wiki/Mii_Data#Mii_format
//
// Written by James Daniel
// github.com/jaames | rakujira.jp
// ====== Usage ======
//
// instanciate:
// $mii = new miiParser();
//
// open buffer:
// $mii->loadFromBuffer(fopen("path/to/example.mii", "r"));
//
// get data:
// $mii->getData()
class miiParser {
var $stream = null;
// load a stream from an open buffer object
function loadFromBuffer($buffer){
$this->stream = $buffer;
}
// load a stream from a string
function loadFromString($data){
$this->stream = fopen("php://memory", "r+");
fwrite($this->stream, $data);
}
// close the stream
function close(){
fclose($this->stream);
$this->stream = null;
}
// should, hopefully, auto-close the stream when finished
function __destruct() {
if ($this->stream){
$this->close();
}
}
function _getFormatString($spec){
$formatString = [];
foreach ($spec as $var => $format) {
array_push($formatString, $format . $var);
}
return join("/", $formatString);
}
function _unpackSmallInts($source, $sourceBitlength, $items){
$offset = $sourceBitlength;
$bitMasks = [0x0, 0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF];
$ret = [];
foreach ($items as $name => $length) {
$offset -= $length;
$ret[$name] = (($source >> $offset) & $bitMasks[$length]);
}
return $ret;
}
function getData(){
if (!$this->stream) {
return null;
}
$spec = [
// flags 1 - [1bit - isValid][1bit - isFemale][4bit - birthMonth][5bit - birthDay][4bit - favColor][1bit - isFavorite]
"flags1" => "n",
// miiname - UTF-16 big endian
"name" => "a20",
"height" => "C",
"weight" => "C",
// mii IDs
"miiId1" => "C",
"miiId2" => "C",
"miiId3" => "C",
"miiId4" => "C",
// system IDs
"systemId0" => "C",
"systemId1" => "C",
"systemId2" => "C",
"systemId3" => "C",
// flags2 - [3bit - faceshape][3bit - skincolor][4bit - facialFeature][3bit - unknown][1bit - mingle on/off][1bit - unknown][1bit - downloaded]
"flags2" => "n",
// hair - [7bit - type][3bit - color][1bit - parting][5bit - unknown]
"hair" => "n",
// eyebrows - [5bit - type][1bit - unknown][4bit - rotation][6bit - unknown][3bit - color][4bit - size][5bit - yPosition][4bit - xSpacing]
"eyebrows" => "N",
// eyes - [6bit - type][2bit - unknown][3bit - rotation][5bit - yPosition][3bit - color][1bit - unknown][3bit - size][4bit - xSpacing][5bit - unknown]
"eyes" => "N",
// nose - [4bit - type][4bit - size][5bit - yPosition][3bit - unknown]
"nose" => "n",
// mouth - [5bit - type][2bit - color][4bit - size][5bit - yPosition]
"mouth" => "n",
// glasses - [4bit - type][3bit - color][1bit - unknown][3bit - size][5bit - yPosition]
"glasses" => "n",
// facial hair - [2bit - mustacheType][2bit - beardType][3bit - color][4bit - mustacheSize][5bit - mustacheYPosition]
"facialHair" => "n",
// mole - [1bit - on/off][4bit - size][5bit - yPosition][5bit - xPosition][1bit - unknown]
"mole" => "n",
// creator name - UTF-16 big endian
"creator" => "a20",
];
// seek back to the beginning of the file
fseek($this->stream, 0);
$data = unpack($this->_getFormatString($spec), fread($this->stream, 0x50));
$flags1 = $this->_unpackSmallInts($data["flags1"], 16, [
"isValid" => 1,
"isFemale" => 1,
"birthMonth" => 4,
"birthDay" => 5,
"favoriteColor" => 4,
"isFavorite" => 1
]);
$flags2 = $this->_unpackSmallInts($data["flags1"], 16, [
"headShape" => 3,
"skinColor" => 3,
"facialFeature" => 4,
"unknown" => 3,
"mingle" => 1,
"unknown2" => 1,
"downloaded" => 1,
]);
$hair = $this->_unpackSmallInts($data["hair"], 16, [
"type" => 7,
"color" => 3,
"parting" => 1,
"unknown" => 5
]);
$eyebrows = $this->_unpackSmallInts($data["eyebrows"], 32, [
"type" => 5,
"unknown" => 1,
"rotation" => 4,
"unknown2" => 6,
"color" => 3,
"size" => 4,
"y" => 5,
"xSpacing" => 4
]);
$eyes = $this->_unpackSmallInts($data["eyes"], 32, [
"type" => 6,
"unknown" => 2,
"rotation" => 3,
"y" => 5,
"color" => 3,
"unknown2" => 1,
"size" => 3,
"xSpacing" => 4,
"unknown3" => 5,
]);
$nose = $this->_unpackSmallInts($data["nose"], 16, [
"type" => 4,
"size" => 4,
"y" => 5,
"unknown" => 3,
]);
$mouth = $this->_unpackSmallInts($data["mouth"], 16, [
"type" => 4,
"color" => 3,
"size" => 4,
"y" => 5,
]);
$glasses = $this->_unpackSmallInts($data["glasses"], 16, [
"type" => 4,
"color" => 3,
"unknown" => 1,
"size" => 3,
"y" => 5,
]);
$facialHair = $this->_unpackSmallInts($data["facialHair"], 16, [
"mustacheType" => 2,
"beardType" => 2,
"color" => 3,
"mustacheSize" => 4,
"mustacheY" => 5,
]);
$mole = $this->_unpackSmallInts($data["mole"], 16, [
"isUsed" => 1,
"size" => 4,
"y" => 5,
"x" => 5,
"unknown" => 1
]);
return [
"isValid" => $flags1["isValid"],
"name" => trim(mb_convert_encoding($data["name"], "UTF-8", "UTF-16BE"), "\0"),
"creator" => trim(mb_convert_encoding($data["creator"], "UTF-8", "UTF-16BE"), "\0"),
"gender" => $flags1["isFemale"] == 1 ? "female" : "male",
"birthday" => [
"month" => $flags1["birthMonth"],
"day" => $flags1["birthDay"],
],
"color" => $flags1["favoriteColor"],
"body" => [
"height" => $data["height"],
"weight" => $data["weight"],
"skinColor" => $flags2["skinColor"]
],
"head" => [
"shape" => $flags2["headShape"],
"feature" => $flags2["facialFeature"],
],
"hair" => [
"type" => $hair["type"],
"color" => $hair["color"],
"parting" => $hair["parting"],
],
"eyebrows" => [
"type" => $eyebrows["type"],
"rotation" => $eyebrows["rotation"],
"rotation" => $eyebrows["rotation"],
"color" => $eyebrows["color"],
"size" => $eyebrows["size"],
"y" => $eyebrows["y"],
"xSpacing" => $eyebrows["xSpacing"],
],
"eyes" => [
"type" => $eyes["type"],
"color" => $eyes["color"],
"rotation" => $eyes["rotation"],
"size" => $eyes["size"],
"y" => $eyes["y"],
"xSpacing" => $eyes["xSpacing"],
],
"nose" => [
"type" => $nose["type"],
"size" => $nose["size"],
"y" => $nose["y"],
],
"mouth" => [
"type" => $mouth["type"],
"size" => $mouth["size"],
"color" => $mouth["color"],
"y" => $mouth["y"],
],
"glasses" => [
"type" => $glasses["type"],
"color" => $glasses["color"],
"size" => $glasses["size"],
"y" => $glasses["y"],
],
"mustache" => [
"type" => $facialHair["mustacheType"],
"size" => $facialHair["mustacheSize"],
"color" => $facialHair["color"],
"y" => $facialHair["mustacheY"],
],
"beard" => [
"type" => $facialHair["beardType"],
"color" => $facialHair["color"],
],
"mole" => [
"isUsed" => $mole["isUsed"],
"size" => $mole["size"],
"x" => $mole["x"],
"y" => $mole["y"],
],
"settings" => [
"mingle" => $flags2["mingle"],
"isFavorite" => $flags1["isFavorite"],
],
"downloaded" => $flags2["downloaded"],
"miiID" => [
1 => $data["miiId1"],
2 => $data["miiId2"],
3 => $data["miiId3"],
4 => $data["miiId4"],
],
"systemID" => [
0 => $data["systemId0"],
1 => $data["systemId1"],
2 => $data["systemId2"],
3 => $data["systemId3"],
],
];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment