Pr4w

NBT Parser

Aug 24th, 2011
342
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <?php
  2. /**
  3.  * Class for reading in NBT-format files.
  4.  * Based on the work of Justin Martin <frozenfire@thefrozenfire.com>.
  5.  * Altered to use BCMath rather than GMP, because BCMath comes by default with PHP >= 4.0.4, GMP never did.
  6.  *
  7.  * @author  Julian Rupp <mail@nuclearflux.net>
  8.  * @version 1.1
  9.  *
  10.  * Dependencies:
  11.  *  PHP 4.3+ (5.3+ recommended)
  12.  *  BCMath Extension
  13.  */
  14.  
  15. extension_loaded("bcmath") or die("The NBT class requires the BCMath extension.");
  16. class NBT {
  17.     public $root = array();
  18.    
  19.     public $verbose = false;
  20.    
  21.     const TAG_END = 0;
  22.     const TAG_BYTE = 1;
  23.     const TAG_SHORT = 2;
  24.     const TAG_INT = 3;
  25.     const TAG_LONG = 4;
  26.     const TAG_FLOAT = 5;
  27.     const TAG_DOUBLE = 6;
  28.     const TAG_BYTE_ARRAY = 7;
  29.     const TAG_STRING = 8;
  30.     const TAG_LIST = 9;
  31.     const TAG_COMPOUND = 10;
  32.    
  33.     public function loadFile($filename) {
  34.         $fp = fopen("compress.zlib://{$filename}", "rb");
  35.         $this->traverseTag($fp, $this->root);
  36.         return end($this->root);
  37.     }
  38.    
  39.     public function writeFile($filename) {
  40.         $fp = fopen("compress.zlib://{$filename}", "wb");
  41.         foreach($this->root as $rootTag) if(!$this->writeTag($fp, $rootTag)) return false;
  42.         return true;
  43.     }
  44.    
  45.     public function purge() {
  46.         $this->root = array();
  47.     }
  48.    
  49.     protected function _signedlong2intstring($bin)
  50.     {
  51.         if(strlen($bin) != 8)
  52.         {
  53.             return(false);
  54.         }
  55.         list(,$firstHalf) = unpack("N", substr($bin,0,4));
  56.         list(,$secondHalf) = unpack("N", substr($bin,4,4));
  57.         $value = bcadd($secondHalf, bcmul($firstHalf, "4294967296"));
  58.         if(bccomp($value, bcpow(2, 63)) >= 0) $value = bcsub($value, bcpow(2, 64));
  59.         return($value);
  60.     }
  61.    
  62.     protected function _intstring2signedlong($string)
  63.     {
  64.         #todo: input check
  65.  
  66.         $sbt = array();
  67.         $sby = array();
  68.         $ret = '';
  69.         $c_remaining = $string;
  70.         $c_mod = 0;
  71.  
  72.         for($i=0;$i<64;$i++)
  73.         {
  74.             $sbt[$i] = 0;
  75.         }
  76.  
  77.         for($mp=63,$i=0;$mp>=0,$i<64;$mp--,$i++)
  78.         {
  79.             $c_mod = bcdiv($c_remaining,bcpow(2,$mp));
  80.             $c_remaining = bcmod($c_remaining,bcpow(2,$mp));
  81.             if(bccomp($c_mod,"1") >= 0)
  82.             {
  83.                 $sbt[$i] = 1;
  84.             }
  85.  
  86.             if(bccomp($c_remaining,"0") == 0)
  87.             {
  88.                 break;
  89.             }
  90.         }
  91.  
  92.         for($i=0;$i<8;$i++)
  93.         {
  94.             $bin = '';
  95.             for($j=0;$j<8;$j++)
  96.             {
  97.                 $os = ($i*8)+$j;
  98.                 $bin .= $sbt[$os];
  99.             }
  100.             $sby[$i] = chr(bindec($bin));
  101.         }
  102.         $ret = implode($sby);
  103.         if(bccomp($ret, bcpow(2, 63)) >= 0)
  104.         {
  105.             $ret = bcsub($ret, bcpow(2, 64));
  106.         }
  107.  
  108.         return($ret);
  109.     }
  110.    
  111.     protected function traverseTag($fp, &$tree) {
  112.         if(feof($fp)) return false;
  113.         $tagType = $this->readType($fp, self::TAG_BYTE); // Read type byte.
  114.         if($tagType == self::TAG_END) {
  115.             return false;
  116.         } else {
  117.             if($this->verbose) $position = ftell($fp);
  118.             $tagName = $this->readType($fp, self::TAG_STRING);
  119.             $tagData = $this->readType($fp, $tagType);
  120.             if($this->verbose) echo "Reading tag \"{$tagName}\" at offset {$position}".PHP_EOL;
  121.             $tree[] = array("type"=>$tagType, "name"=>$tagName, "value"=>$tagData);
  122.             return true;
  123.         }
  124.     }
  125.    
  126.     protected function writeTag($fp, $tag) {
  127.         if($this->verbose) echo "Writing tag \"{$tag["name"]}\" of type {$tag["type"]}".PHP_EOL;
  128.         return $this->writeType($fp, self::TAG_BYTE, $tag["type"]) && $this->writeType($fp, self::TAG_STRING, $tag["name"]) && $this->writeType($fp, $tag["type"], $tag["value"]);
  129.     }
  130.    
  131.     protected function readType($fp, $tagType) {
  132.         switch($tagType) {
  133.             case self::TAG_BYTE: // Signed byte (8 bit)
  134.                 list(,$unpacked) = unpack("c", fread($fp, 1));
  135.                 return $unpacked;
  136.             case self::TAG_SHORT: // Signed short (16 bit, big endian)
  137.                 list(,$unpacked) = unpack("n", fread($fp, 2));
  138.                 if($unpacked >= pow(2, 15)) $unpacked -= pow(2, 16); // Convert unsigned short to signed short.
  139.                 return $unpacked;
  140.             case self::TAG_INT: // Signed integer (32 bit, big endian)
  141.                 list(,$unpacked) = unpack("N", fread($fp, 4));
  142.                 if($unpacked >= pow(2, 31)) $unpacked -= pow(2, 32); // Convert unsigned int to signed int
  143.                 return $unpacked;
  144.             case self::TAG_LONG: // Signed long (64 bit, big endian)
  145.                 return($this->_signedlong2intstring(fread($fp, 8)));
  146.             case self::TAG_FLOAT: // Floating point value (32 bit, big endian, IEEE 754-2008)
  147.                 list(,$value) = (pack('d', 1) == "\77\360\0\0\0\0\0\0")?unpack('f', fread($fp, 4)):unpack('f', strrev(fread($fp, 4)));
  148.                 return $value;
  149.             case self::TAG_DOUBLE: // Double value (64 bit, big endian, IEEE 754-2008)
  150.                 list(,$value) = (pack('d', 1) == "\77\360\0\0\0\0\0\0")?unpack('d', fread($fp, 8)):unpack('d', strrev(fread($fp, 8)));
  151.                 return $value;
  152.             case self::TAG_BYTE_ARRAY: // Byte array
  153.                 $arrayLength = $this->readType($fp, self::TAG_INT);
  154.                 $array = array();
  155.                 for($i = 0; $i < $arrayLength; $i++) $array[] = $this->readType($fp, self::TAG_BYTE);
  156.                 return $array;
  157.             case self::TAG_STRING: // String
  158.                 if(!$stringLength = $this->readType($fp, self::TAG_SHORT)) return "";
  159.                 $string = utf8_decode(fread($fp, $stringLength)); // Read in number of bytes specified by string length, and decode from utf8.
  160.                 return $string;
  161.             case self::TAG_LIST: // List
  162.                 $tagID = $this->readType($fp, self::TAG_BYTE);
  163.                 $listLength = $this->readType($fp, self::TAG_INT);
  164.                 if($this->verbose) echo "Reading in list of {$listLength} tags of type {$tagID}.".PHP_EOL;
  165.                 $list = array("type"=>$tagID, "value"=>array());
  166.                 for($i = 0; $i < $listLength; $i++) {
  167.                     if(feof($fp)) break;
  168.                     $list["value"][] = $this->readType($fp, $tagID);
  169.                 }
  170.                 return $list;
  171.             case self::TAG_COMPOUND: // Compound
  172.                 $tree = array();
  173.                 while($this->traverseTag($fp, $tree));
  174.                 return $tree;
  175.         }
  176.     }
  177.    
  178.     protected function writeType($fp, $tagType, $value) {
  179.         switch($tagType) {
  180.             case self::TAG_BYTE: // Signed byte (8 bit)
  181.                 return fwrite($fp, pack("c", $value));
  182.             case self::TAG_SHORT: // Signed short (16 bit, big endian)
  183.                 if($value < 0) $value += pow(2, 16); // Convert signed short to unsigned short
  184.                 return fwrite($fp, pack("n", $value));
  185.             case self::TAG_INT: // Signed integer (32 bit, big endian)
  186.                 if($value < 0) $value += pow(2, 32); // Convert signed int to unsigned int
  187.                 return fwrite($fp, pack("N", $value));
  188.             case self::TAG_LONG: // Signed long (64 bit, big endian)
  189.                 $data = $this->_intstring2signedlong($value);
  190.                 return fwrite($fp, $data);
  191.             case self::TAG_FLOAT: // Floating point value (32 bit, big endian, IEEE 754-2008)
  192.                 return fwrite($fp, (pack('d', 1) == "\77\360\0\0\0\0\0\0")?pack('f', $value):strrev(pack('f', $value)));
  193.             case self::TAG_DOUBLE: // Double value (64 bit, big endian, IEEE 754-2008)
  194.                 return fwrite($fp, (pack('d', 1) == "\77\360\0\0\0\0\0\0")?pack('d', $value):strrev(pack('d', $value)));
  195.             case self::TAG_BYTE_ARRAY: // Byte array
  196.                 return $this->writeType($fp, self::TAG_INT, count($value)) && fwrite($fp, call_user_func_array("pack", array_merge(array("c".count($value)), $value)));
  197.             case self::TAG_STRING: // String
  198.                 $value = utf8_encode($value);
  199.                 return $this->writeType($fp, self::TAG_SHORT, strlen($value)) && fwrite($fp, $value);
  200.             case self::TAG_LIST: // List
  201.                 if($this->verbose) echo "Writing list of ".count($value["value"])." tags of type {$value["type"]}.".PHP_EOL;
  202.                 if(!($this->writeType($fp, self::TAG_BYTE, $value["type"]) && $this->writeType($fp, self::TAG_INT, count($value["value"])))) return false;
  203.                 foreach($value["value"] as $listItem) if(!$this->writeType($fp, $value["type"], $listItem)) return false;
  204.                 return true;
  205.             case self::TAG_COMPOUND: // Compound
  206.                 foreach($value as $listItem) if(!$this->writeTag($fp, $listItem)) return false;
  207.                 if(!fwrite($fp, "\0")) return false;
  208.                 return true;
  209.         }
  210.     }
  211. }
  212. ?>
RAW Paste Data Copied