Convert Minecraft server world files into MCserver world files.
#34
(10-30-2011, 07:48 AM)necavi Wrote: Hey, mind uploading the php as well? Even if he won't be able to get it running fast enough, he wants to give it a try, he hasn't been able to do as much PHP as he'd like recently and this one sounds fun.

Sure thing.

It's simple and I didn't really make any comments. It was meant to just be a proof of concept sort of thing. It needs a LOT of memory to run and is ridiculously slow and inefficient. (kind of like the official minecraftBig Grin )[/code]

denotch.php:
Code:
<?php
   $mtime = microtime();
   $mtime = explode(" ",$mtime);
   $mtime = $mtime[1] + $mtime[0];
   $starttime = $mtime;
include('nbtparse.php');

function coord_to_bin($coord) {
    if ($coord < 0 ) {
        $coord_bin = '';
        $abs_coord = abs($coord);

        if ($abs_coord < 256 ) {
                $Pos_value = -(pow(2, 8) - $coord);
                $coord_bin .= pack("C*", $Pos_value);
                $coord_bin .= pack("C*", 255);
                $coord_bin .= pack("C*", 255);
                $coord_bin .= pack("C*", 255);
            } elseif ($abs_coord < 65536 ) {
                $first  = floor($coord / 256);
                $second = $coord - ($first * 256);
                $first  = -(pow(2, 8) - $first);
                $second = -(pow(2, 8) - $second);
                $coord_bin .= pack("C*", $second);
                $coord_bin .= pack("C*", $first);
                $coord_bin .= pack("C*", 255);
                $coord_bin .= pack("C*", 255);
                //$false_header .= pack("C*", 0);
            } elseif ($abs_coord < 16777216 ) {
                $first  = floor($coord / (256*256));
                $second = floor( ( $coord - ( $first * ( 256*256 ) ) ) / 256 );
                $third  = $coord - ($second * 256) - ( $first * ( 256*256 ) );
                $first  = -(pow(2, 8) - $first);
                $second = -(pow(2, 8) - $second);
                $third  = -(pow(2, 8) - $third);
                $coord_bin .= pack("C*", $third);
                $coord_bin .= pack("C*", $second);
                $coord_bin .= pack("C*", $first);
                $coord_bin .= pack("C*", 255);
            } else {
                echo 'error 1a at: ' . $i . ' with coord: ' . $coord;
            }
    } elseif ($coord < 256 ) {
        $coord_bin .= pack("C*", $coord);
        $coord_bin .= pack("C*", 0);
        $coord_bin .= pack("C*", 0);
        $coord_bin .= pack("C*", 0);
    } elseif ($coord < 65536 ) {
        $first  = floor($coord / 256);
        $second = $coord - ($first * 256);
        $coord_bin .= pack("C*", $second);
        $coord_bin .= pack("C*", $first);
        $coord_bin .= pack("C*", 0);
        $coord_bin .= pack("C*", 0);
        //$false_header .= pack("C*", 0);
    } elseif ($coord < 16777216 ) {
        $first  = floor($coord / (256*256));
        $second = floor( ( $coord - ( $first * ( 256*256 ) ) ) / 256 );
        $third  = $coord - ($second * 256) - ( $first * ( 256*256 ) );

        $coord_bin .= pack("C*", $third);
        $coord_bin .= pack("C*", $second);
        $coord_bin .= pack("C*", $first);
        $coord_bin .= pack("C*", 0);
    } elseif ($coord < 4294967296 ) {
        $first  = floor($coord / (256*256*256));
        $second = floor( ( $coord - ( $first * ( 256*256*256 ) ) ) / (256 * 256) );
        $third  = floor( ( $coord - ( $first * ( 256*256*256 ) ) - ( $second * ( 256*256 ) ) ) / 256 );
        $fourth = $coord - ( $first * ( 256*256*256 ) ) - ( $second * ( 256*256 ) ) - ( $third * ( 256 ) );
        $coord_bin .= pack("C*", $fourth);
        $coord_bin .= pack("C*", $third);
        $coord_bin .= pack("C*", $second);
        $coord_bin .= pack("C*", $first);
    } else {
        echo 'error 1b at: ' . $i . ' with coord: ' . $coord;
    }
    return($coord_bin);
}



// open this directory
$myDirectory = opendir("C:\\xampp\\htdocs\\minecraft\\region");

// get each entry
while($entryName = readdir($myDirectory)) {
    $dirArray[] = $entryName;
    //echo $entryName;
}

// close directory
closedir($myDirectory);

// sort 'em
sort($dirArray);

foreach ($dirArray as $dir) {
    //echo "<br />" . $dir;
    
}


//$my_file = file_get_contents("C:\\xampp\\htdocs\\minecraft\\region\\r.0.0.mcr");
//echo $my_file;



$filename = "C:\\xampp\\htdocs\\minecraft\\region\\r.0.0.mcr";
$handle = fopen($filename, "rb");
$contents = fread($handle, filesize($filename));
fclose($handle);
//echo filesize($filename);
$number = unpack("C*", $contents);
//print_r($number);

$chunk_array = array();



$pak_file  = '';
$pak_file .= pack("C*", 1); // Pakversion 1
$pak_file .= pack("C*", 1); // Chunkversion 1




$i = 1;

while ($i < 4096) {

    $num1 = $number[$i+0];
    $num2 = $number[$i+1];
    $num3 = $number[$i+2];
    $num4 = $number[$i+3];

    $total_offset = ($num1*256*256) + ($num2*256) + $num3;


    

    $chunk_start_offset = $total_offset*4096 + 1;
    $chunk_end_offset     = $chunk_start_offest+($num4*4096);


    $chunk_data_length = ($number[$chunk_start_offset]*256*256*256) + ($number[$chunk_start_offset+1]*256*256) + ($number[$chunk_start_offset+2]*256) + $number[$chunk_start_offset+3];
    $chunk_data_comptp = $number[$chunk_start_offset+4];
    $chunk_data        = '';

    //echo $chunk_data_length;
    //echo $num1;
    //echo $num1;
    //echo $num1;
    
    //echo $total_offset;
    
    $cat = 0;
    $re = $chunk_start_offset+5;
    $ri = 1;
    while ($re < (($chunk_start_offset + $chunk_data_length)+4) ) {
        $chunk_data .= pack("C", $number[$re]);
        $re++;
        $ri++;
        $cat ++;
    }
    //echo $number[$re];
    //exit();
    $deflate_chunk_data = gzuncompress($chunk_data);
    //$alpha_nbt = gzencode($deflate_chunk_data);



    //$filename = "php://temp//tmp.nbt";
    $filename = tempnam(sys_get_temp_dir(), "temp");
    $handle = fopen($filename, "wb");
    fwrite($handle, $deflate_chunk_data);
    //fclose($handle);


    $nbt = new nbt();
    $nbt->verbose = false;
    //$nbt->loaddata($deflate_chunk_data);
    $nbt->loadFile($filename);
    //$nbt->loadFile("tmp.nbt");
    $nbt->writeFile($tmp = tempnam(sys_get_temp_dir(), "nbt"));
    $nbt_array = $nbt->root[0];
    echo '<pre>';
    print_r($nbt_array);
    echo '</pre>';
    exit();
    $nbt_Data_array = $nbt_array['value'][0]['value'][0]['value'];
    $nbt_Entities_array = $nbt_array['value'][0]['value'][1]['value'];
    $nbt_LastUpdate_value = $nbt_array['value'][0]['value'][2]['value'];
    $nbt_xPos_value = $nbt_array['value'][0]['value'][3]['value'];
    $nbt_zPos_value = $nbt_array['value'][0]['value'][4]['value'];
    $nbt_TileEntities_array = $nbt_array['value'][0]['value'][5]['value'];
    $nbt_TerrainPopulated_value = $nbt_array['value'][0]['value'][6]['value'];
    $nbt_SkyLight_array = $nbt_array['value'][0]['value'][7]['value'];
    $nbt_HeightMap_array = $nbt_array['value'][0]['value'][8]['value'];
    $nbt_BlockLight_array = $nbt_array['value'][0]['value'][9]['value'];
    $nbt_Blocks_array = $nbt_array['value'][0]['value'][10]['value'];

    $nbt_Block_bin = '';
    foreach ($nbt_Blocks_array as $nbt_Blocks) {
        $nbt_Block_bin .= pack("C*", $nbt_Blocks);
    }

    foreach ($nbt_Data_array as $nbt_Data) {
        $nbt_Block_bin .= pack("C*", $nbt_Data);
    }

    foreach ($nbt_BlockLight_array as $nbt_BlockLight) {
        $nbt_Block_bin .= pack("C*", $nbt_BlockLight);
    }

    foreach ($nbt_SkyLight_array as $nbt_SkyLight) {
        $nbt_Block_bin .= pack("C*", $nbt_SkyLight);
    }

    $uncompressed_chunk_size  = strlen($nbt_Block_bin);
    $Compressed_Block_bin    = gzcompress($nbt_Block_bin);
    $compressed_chunk_size    = strlen($Compressed_Block_bin);


    $false_header = '';



    $false_header = coord_to_bin($nbt_xPos_value) . coord_to_bin($nbt_zPos_value) . coord_to_bin($compressed_chunk_size) . coord_to_bin($uncompressed_chunk_size);


    //$chunk_array[$i]['compressed_chunk_size']   = $compressed_chunk_size;
    //$chunk_array[$i]['uncompressed_chunk_size'] = $uncompressed_chunk_size;
    $chunk_array[$i]['Compressed_Block_bin']    = $Compressed_Block_bin;
    //$chunk_array[$i]['xCoord']                  = $nbt_xPos_value;
    //$chunk_array[$i]['zCoord']                  = $nbt_zPos_value;
    $chunk_array[$i]['false_header']            = $false_header;



    $i = $i+4;
}


$numchunks = 0;
$pakheader = '';
$pakblocks = '';

foreach ($chunk_array as $chunk) {
    $numchunks++;
    $pakblocks .= $chunk['Compressed_Block_bin'];
    $pakheader .= $chunk['false_header'];
}
$chunk_array = array();


if ($numchunks < 256 ) {
    $pak_file .= pack("C*", $numchunks);
    $pak_file .= pack("C*", 0);
} elseif ($numchunks < 1025 ) {
    $first  = floor($numchunks / 256);
    $second = $numchunks - ($first * 256);
    $pak_file .= pack("C*", $second);
    $pak_file .= pack("C*", $first);
} else {
    echo 'error 12x';
    exit();
}


$pak_file .= $pakheader;
$pak_file .= $pakblocks;





$filename = "C:\\xampp\\htdocs\\minecraft\\world\\X0_Z0.pak";
$handle = fopen($filename, "wb");

fwrite($handle, $pak_file);

fclose($handle);




$mtime = microtime();
$mtime = explode(" ",$mtime);
$mtime = $mtime[1] + $mtime[0];
$endtime = $mtime;
$totaltime = ($endtime - $starttime);
echo "This page was created in ".$totaltime." seconds";


?>

nbtparse.php
Code:
<?php
/**
* Class for reading in NBT-format files.
* Based on the work of Justin Martin <frozenfire@thefrozenfire.com>.
* Altered to use BCMath rather than GMP, because BCMath comes by default with PHP >= 4.0.4, GMP never did.
*
* @author  Julian Rupp <mail@nuclearflux.net>
* @version 1.1
*
* Dependencies:
*  PHP 4.3+ (5.3+ recommended)
*  BCMath Extension
*/

extension_loaded("bcmath") or die("The NBT class requires the BCMath extension.");
class NBT {
    public $root = array();
    
    public $verbose = false;
    
    const TAG_END = 0;
    const TAG_BYTE = 1;
    const TAG_SHORT = 2;
    const TAG_INT = 3;
    const TAG_LONG = 4;
    const TAG_FLOAT = 5;
    const TAG_DOUBLE = 6;
    const TAG_BYTE_ARRAY = 7;
    const TAG_STRING = 8;
    const TAG_LIST = 9;
    const TAG_COMPOUND = 10;
    
    public function loadFile($filename) {
        //$fp = fopen("compress.zlib://{$filename}", "rb");
        $fp = fopen($filename, "rb");
        $this->traverseTag($fp, $this->root);
        return end($this->root);
    }
    
    public function writeFile($filename) {
        //$fp = fopen("compress.zlib://{$filename}", "wb");
        $fp = fopen($filename, "wb");
        foreach($this->root as $rootTag) if(!$this->writeTag($fp, $rootTag)) return false;
        return true;
    }
    
    public function purge() {
        $this->root = array();
    }
    
    protected function _signedlong2intstring($bin)
    {
        if(strlen($bin) != 8)
        {
            return(false);
        }
        list(,$firstHalf) = unpack("N", substr($bin,0,4));
        list(,$secondHalf) = unpack("N", substr($bin,4,4));
        $value = bcadd($secondHalf, bcmul($firstHalf, "4294967296"));
        if(bccomp($value, bcpow(2, 63)) >= 0) $value = bcsub($value, bcpow(2, 64));
        return($value);
    }
    
    protected function _intstring2signedlong($string)
    {
        #todo: input check

        $sbt = array();
        $sby = array();
        $ret = '';
        $c_remaining = $string;
        $c_mod = 0;

        for($i=0;$i<64;$i++)
        {
            $sbt[$i] = 0;
        }

        for($mp=63,$i=0;$mp>=0,$i<64;$mp--,$i++)
        {
            $c_mod = bcdiv($c_remaining,bcpow(2,$mp));
            $c_remaining = bcmod($c_remaining,bcpow(2,$mp));
            if(bccomp($c_mod,"1") >= 0)
            {
                $sbt[$i] = 1;
            }

            if(bccomp($c_remaining,"0") == 0)
            {
                break;
            }
        }

        for($i=0;$i<8;$i++)
        {
            $bin = '';
            for($j=0;$j<8;$j++)
            {
                $os = ($i*8)+$j;
                $bin .= $sbt[$os];
            }
            $sby[$i] = chr(bindec($bin));
        }
        $ret = implode($sby);
        if(bccomp($ret, bcpow(2, 63)) >= 0)
        {
            $ret = bcsub($ret, bcpow(2, 64));
        }

        return($ret);
    }
    
    protected function traverseTag($fp, &$tree) {
        if(feof($fp)) return false;
        $tagType = $this->readType($fp, self::TAG_BYTE); // Read type byte.
        if($tagType == self::TAG_END) {
            return false;
        } else {
            if($this->verbose) $position = ftell($fp);
            $tagName = $this->readType($fp, self::TAG_STRING);
            $tagData = $this->readType($fp, $tagType);
            if($this->verbose) echo "Reading tag \"{$tagName}\" at offset {$position}".PHP_EOL;
            $tree[] = array("type"=>$tagType, "name"=>$tagName, "value"=>$tagData);
            return true;
        }
    }
    
    protected function writeTag($fp, $tag) {
        if($this->verbose) echo "Writing tag \"{$tag["name"]}\" of type {$tag["type"]}".PHP_EOL;
        return $this->writeType($fp, self::TAG_BYTE, $tag["type"]) && $this->writeType($fp, self::TAG_STRING, $tag["name"]) && $this->writeType($fp, $tag["type"], $tag["value"]);
    }
    
    protected function readType($fp, $tagType) {
        switch($tagType) {
            case self::TAG_BYTE: // Signed byte (8 bit)
                list(,$unpacked) = unpack("C", fread($fp, 1));
                //return fread($fp, 1);
                return $unpacked;
            case self::TAG_SHORT: // Signed short (16 bit, big endian)
                list(,$unpacked) = unpack("n", fread($fp, 2));
                if($unpacked >= pow(2, 15)) $unpacked -= pow(2, 16); // Convert unsigned short to signed short.
                return $unpacked;
            case self::TAG_INT: // Signed integer (32 bit, big endian)
                list(,$unpacked) = unpack("N", fread($fp, 4));
                if($unpacked >= pow(2, 31)) $unpacked -= pow(2, 32); // Convert unsigned int to signed int
                return $unpacked;
            case self::TAG_LONG: // Signed long (64 bit, big endian)
                return($this->_signedlong2intstring(fread($fp, 8)));
            case self::TAG_FLOAT: // Floating point value (32 bit, big endian, IEEE 754-2008)
                list(,$value) = (pack('d', 1) == "\77\360\0\0\0\0\0\0")?unpack('f', fread($fp, 4)):unpack('f', strrev(fread($fp, 4)));
                return $value;
            case self::TAG_DOUBLE: // Double value (64 bit, big endian, IEEE 754-2008)
                list(,$value) = (pack('d', 1) == "\77\360\0\0\0\0\0\0")?unpack('d', fread($fp, 8)):unpack('d', strrev(fread($fp, 8)));
                return $value;
            case self::TAG_BYTE_ARRAY: // Byte array
                $arrayLength = $this->readType($fp, self::TAG_INT);
                $array = array();
                for($i = 0; $i < $arrayLength; $i++) $array[] = $this->readType($fp, self::TAG_BYTE);
                return $array;
            case self::TAG_STRING: // String
                if(!$stringLength = $this->readType($fp, self::TAG_SHORT)) return "";
                $string = utf8_decode(fread($fp, $stringLength)); // Read in number of bytes specified by string length, and decode from utf8.
                return $string;
            case self::TAG_LIST: // List
                $tagID = $this->readType($fp, self::TAG_BYTE);
                $listLength = $this->readType($fp, self::TAG_INT);
                if($this->verbose) echo "Reading in list of {$listLength} tags of type {$tagID}.".PHP_EOL;
                $list = array("type"=>$tagID, "value"=>array());
                for($i = 0; $i < $listLength; $i++) {
                    if(feof($fp)) break;
                    $list["value"][] = $this->readType($fp, $tagID);
                }
                return $list;
            case self::TAG_COMPOUND: // Compound
                $tree = array();
                while($this->traverseTag($fp, $tree));
                return $tree;
        }
    }
    
    protected function writeType($fp, $tagType, $value) {
        switch($tagType) {
            case self::TAG_BYTE: // Signed byte (8 bit)
                return fwrite($fp, pack("c", $value));
            case self::TAG_SHORT: // Signed short (16 bit, big endian)
                if($value < 0) $value += pow(2, 16); // Convert signed short to unsigned short
                return fwrite($fp, pack("n", $value));
            case self::TAG_INT: // Signed integer (32 bit, big endian)
                if($value < 0) $value += pow(2, 32); // Convert signed int to unsigned int
                return fwrite($fp, pack("N", $value));
            case self::TAG_LONG: // Signed long (64 bit, big endian)
                $data = $this->_intstring2signedlong($value);
                return fwrite($fp, $data);
            case self::TAG_FLOAT: // Floating point value (32 bit, big endian, IEEE 754-2008)
                return fwrite($fp, (pack('d', 1) == "\77\360\0\0\0\0\0\0")?pack('f', $value):strrev(pack('f', $value)));
            case self::TAG_DOUBLE: // Double value (64 bit, big endian, IEEE 754-2008)
                return fwrite($fp, (pack('d', 1) == "\77\360\0\0\0\0\0\0")?pack('d', $value):strrev(pack('d', $value)));
            case self::TAG_BYTE_ARRAY: // Byte array
                return $this->writeType($fp, self::TAG_INT, count($value)) && fwrite($fp, call_user_func_array("pack", array_merge(array("c".count($value)), $value)));
            case self::TAG_STRING: // String
                $value = utf8_encode($value);
                return $this->writeType($fp, self::TAG_SHORT, strlen($value)) && fwrite($fp, $value);
            case self::TAG_LIST: // List
                if($this->verbose) echo "Writing list of ".count($value["value"])." tags of type {$value["type"]}.".PHP_EOL;
                if(!($this->writeType($fp, self::TAG_BYTE, $value["type"]) && $this->writeType($fp, self::TAG_INT, count($value["value"])))) return false;
                foreach($value["value"] as $listItem) if(!$this->writeType($fp, $value["type"], $listItem)) return false;
                return true;
            case self::TAG_COMPOUND: // Compound
                foreach($value as $listItem) if(!$this->writeTag($fp, $listItem)) return false;
                if(!fwrite($fp, "\0")) return false;
                return true;
        }
    }
}
?>


You could use this to convert the world files (minus the entities), but it would take about 10 hours per 2000x2000 world to convert. I'm not even sure what happens if the file reads from an mcr that doesn't have 1024 chunks. The c++ program looks for that and works with it. The php program is dumb to it. There's also no organization of the chunk, it simple takes them from the mcr and puts them into the pak. I think FakeTruth's server reorders them when it reads the pak for the first time. I'm not sure though. There's definitely a small lag spike when the MCServer reads the created pak for the first time. Subsequent reads don't have the spike. I haven't even bothered to see if MCServer changes them after reading them. lol
Reply
Thanks given by:


Messages In This Thread
RE: Convert Minecraft server world files into MCserver world files. - by rs2k - 10-30-2011, 07:53 AM



Users browsing this thread: 1 Guest(s)