Skip to content

Instantly share code, notes, and snippets.

@shoghicp
Last active July 26, 2020 21:36
Show Gist options
  • Save shoghicp/4678643 to your computer and use it in GitHub Desktop.
Save shoghicp/4678643 to your computer and use it in GitHub Desktop.
Minecraft PE .lst Information Extractor to obtain Protocol Information and Compare Versions
<?php
define("COMPARATOR_VERSION", "0.1");
ini_set("memory_limit", "512M");
$node1 = json_decode(@file_get_contents($argv[1]), true);
if($node1 == ""){
echo "Provide a valid json file produced by the Extractor".PHP_EOL;
exit(1);
}
$node2 = json_decode(@file_get_contents($argv[2]), true);
if($node2 == ""){
echo "Provide a valid json file produced by the Extractor".PHP_EOL;
exit(1);
}
$info = array();
if($node1["protocol"] > $node2["protocol"]){
$new = $node1;
$old = $node2;
}elseif($node1["protocol"] < $node2["protocol"]){
$new = $node2;
$old = $node1;
}else{
echo "Same protocol".PHP_EOL;
exit(1);
}
$info["protocol"] = array($old["protocol"], $new["protocol"]);
$info["version"] = array($old["version"], $new["version"]);
$packets = array();
foreach($old["packets"] as $p){
if(!isset($packets[$p["name"]])){
$packets[$p["name"]] = array();
}
$packets[$p["name"]][0] = $p;
}
foreach($new["packets"] as $p){
if(!isset($packets[$p["name"]])){
$packets[$p["name"]] = array();
}
$packets[$p["name"]][1] = $p;
}
$info["packets"] = array();
foreach($packets as $name => $p){
$changes = array();
$changes["type"] = "none";
if(!isset($p[0]) and isset($p[1])){ //New packet
$changes["type"] = "added";
$changes["id"] = array("", $p[1]["id"]);
$changes["structure"] = array(array(), $p[1]["structure"]);
}elseif(!isset($p[1]) and isset($p[0])){ //removed packet
$changes["type"] = "removed";
$changes["id"] = array($p[0]["id"], "");
}else{ //Compare
if($p[0]["id"] !== $p[1]["id"]){ //ID Moved
$changes["type"] = "changed";
$changes["id"] = array($p[0]["id"], $p[1]["id"]);
}
if($p[0]["direction"] !== $p[1]["direction"]){ //Direction changed
$changes["type"] = "changed";
$changes["direction"] = array($p[0]["direction"], $p[1]["direction"]);
}
$changed = array_diff_assoc($p[0]["structure"], $p[1]["structure"]);
if(count($changed) > 0){
$changes["structure"] = array($p[0]["structure"], $p[1]["structure"]);
}
}
if($changes["type"] !== "none"){
$info["packets"][$name] = $changes;
}
}
echo json_encode($info, JSON_PRETTY_PRINT);
<?php
/*
Produces html output
*/
define("VIEWER_VERSION", "0.1");
ini_set("memory_limit", "512M");
$result = json_decode(@file_get_contents($argv[1]), true);
if($result == ""){
echo "Provide a valid json file produced by the Comparator".PHP_EOL;
exit(1);
}
echo '<html>
<head>
<title>Comparison of MCPE '.$result["version"][0].' #'.$result["protocol"][0].' vs '.$result["version"][1].' #'.$result["protocol"][1].'</title>
</head>
<body>
<style>
</style>
<table width="800">
<tr><th style="text-align:center;border:1px solid black;background:silver;">MCPE '.$result["version"][0].' Protocol #'.$result["protocol"][0].' (Old)</th><td width="10"></td><th style="text-align:center;border:1px solid black;background:silver;">MCPE '.$result["version"][1].' Protocol #'.$result["protocol"][1].' (New)</th></tr>
';
foreach($result["packets"] as $name => $p){
echo '<tr><th style="border:1px solid black;border-bottom:0px;">'.$name.' '.(@$p["data"][0]["id"]).'</th><td></td><th style="border:1px solid black;border-bottom:0px;">'.$name.' '.(@$p["data"][1]["id"]).'</th></tr>';
switch($p["type"]){
case "added":
echo '<tr><td style="border:1px solid black;border-bottom:0px;border-top:0px;"></td><td></td><th style="border:1px solid black;border-bottom:0px;border-top:0px;background:lightgreen;">New Packet</th></tr>';
break;
case "removed":
echo '<tr><td style="border:1px solid black;border-bottom:0px;border-top:0px;"></td><td></td><th style="border:1px solid black;border-bottom:0px;border-top:0px;background:red;">Removed</th></tr>';
break;
}
if(isset($p["id"])){
echo '<tr><th style="border:1px solid black;border-bottom:0px;border-top:0px;">ID '.$p["id"][0].'</th><td></td><th style="border:1px solid black;border-bottom:0px;border-top:0px;">ID '.$p["id"][1].'</th></tr>';
}
if(isset($p["direction"])){
$dire = array(
0 => "Unknown",
1 => "Server => Client",
2 => "Client => Server",
3 => "Two-Way",
);
echo '<tr><th style="border:1px solid black;border-bottom:0px;border-top:0px;">Direction: '.$dire[$p["direction"][0]].'</th><td></td><th style="border:1px solid black;border-bottom:0px;border-top:0px;">Direction: '.$dire[$p["direction"][1]].'</th></tr>';
}
if(isset($p["structure"])){
echo '<tr><th style="border:1px solid black;border-top:0px;">Structure<br/>'.implode("<br/>", $p["structure"][0]).'</th><td></td><th style="border:1px solid black;border-top:0px;">Structure<br/>'.implode("<br/>", $p["structure"][1]).'</th></tr>';
}
echo '<tr><td colspan="3">&nbsp;</td></tr>';
}
echo '</table></body></html>';
<?php
/*
LST Information extractor
*/
define("EXTRACTOR_VERSION", "0.6");
function getFunction($str, $name, &$offset = null, &$end = null){
preg_match('#[\r\n]{1,2}; '.$name.'\(.*\)(const|)[\r\n]{1,2}#', $str, $off, PREG_OFFSET_CAPTURE);
$offset = $off[0][1];
preg_match('/;[\t ]{1,}End[\t ]{1,}of[\t ]{1,}function[\t ]{1,}'.$name.'/', $str, $end, PREG_OFFSET_CAPTURE, $offset);
$end = $end[0][1];
return substr($str, $offset, $end - $offset);
}
ini_set("memory_limit", "2048M");
$lst = trim(@file_get_contents($argv[1]));
if($lst == ""){
echo "Provide a valid *.lst file generated by IDA from the libminecraftpe.so".PHP_EOL;
exit(1);
}
$lst = preg_replace('#[a-z\.]*\:[0-9A-F]{8}[ ]?#', '', $lst); //Remove offsets
//get version directly
preg_match_all('#DCB[\t ]{1,}"([ a-zA-Z0-9\.]*)",0[\t ]{1,};[\t ]{1,}DATA XREF\:[\t ]{1,}Common\:\:getGameVersionString#', $lst, $ver, PREG_SET_ORDER);
$version = "";
foreach($ver as $v){
$version .= $v[1];
}
if(trim($version) == ""){ //Different method to get version
preg_match('#[\r\n]{1,2}; Common\:\:getGameVersionString\(std\:\:string[\t ]{1,}const\&\)[\r\n]{1,2}#', $lst, $off, PREG_OFFSET_CAPTURE);
$offset = $off[0][1];
preg_match('/;[\t ]{1,}(End[\t ]{1,}of[\t ]{1,}function[\t ]{1,}Common\:\:getGameVersionString)|([\-]{9,})/', $lst, $end, PREG_OFFSET_CAPTURE, $offset);
$end = $end[0][1];
$tg = substr($lst, $offset, $end - $offset);
preg_match_all('#ADD[\t ]{1,}R[0-9],[\t ]{1,}PC[\t ]{1,};[\t ]{1,}"([ a-zA-Z0-9\.]*)"#', $tg, $ver, PREG_SET_ORDER);
$version = "";
foreach($ver as $v){
$version .= $v[1];
}
}
//Get protocol
preg_match('/MOVS[\t ]{1,}R[0-9],[\t ]{1,}#([0-9A-F]*)/', getFunction($lst, "ClientSideNetworkHandler\:\:onConnect"), $proto);
$protocol = (int) $proto[1];
//Get packet list
preg_match_all('#[\r\n]{1,2}; ([A-Za-z_]*)\:\:write\(RakNet::BitStream[\t ]{1,}\*\)[\r\n]{1,2}#', $lst, $packets, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
//Get packet side
preg_match_all('#(Server|Client)SideNetworkHandler\:\:handle\(RakNet\:\:RakNetGUID[\t ]{1,}const\&\,[\t ]{0,}([A-Za-z_]*) \*\)#', $lst, $side, PREG_SET_ORDER);
$isServer = array();
$isClient = array();
foreach($side as $s){
if($s[1] == "Server"){
$isServer[$s[2]] = true;
}else{
$isClient[$s[2]] = true;
}
}
$packetlist = array();
foreach($packets as $p){
$name = $p[1][0];
$offset = $p[1][1];
preg_match('/;[\t ]{1,}End[\t ]{1,}of[\t ]{1,}function[\t ]{1,}'.$name.'\:\:write/', $lst, $end, PREG_OFFSET_CAPTURE, $offset);
$end = $end[0][1];
$search = substr($lst, $offset, $end - $offset);
//Bitfield => 0x01 => Server, 0x02 => client
if(isset($isServer[$name]) and isset($isClient[$name])){
$dir = 3;//$dir = "[C <=> S]";
}elseif(isset($isServer[$name])){
$dir = 2;//$dir = "[C ==> S]";
}elseif(isset($isClient[$name])){
$dir = 1;//$dir = "[C <== S]";
}else{
$dir = 0;//$dir = "[C ??? S]";
}
preg_match('/(MOVS|MOV\.W)[\t ]{1,}(R[23456]|LR)\,[\t ]{1,}\#0x([0-9A-F]{2})[\r\n]{1,2}/', $search, $d, PREG_OFFSET_CAPTURE);
$funcs = array();
$id = $d[3][0];
$start = $d[3][1];
preg_match_all('/BL[\t ]{1,}[a-zA-Z0-9_]*[\t ]{1,};[\t ]{1,}.*\:\:([A-Za-z0-9_\<\> ]*)\(/', $search, $fun, PREG_SET_ORDER | PREG_OFFSET_CAPTURE, $start);
$cnt = 0;
$o = 0;
$head = "";
foreach($fun as $fn){
$f = $fn[1][0];
switch(strtolower($f)){
case "writebits":
preg_match('/(MOVS|MOV\.W)[\t ]{1,}R[2]\,[\t ]{1,}\#([x0-9A-F]{1,4})[\r\n]{1,2}/', $search, $bits, 0, $o);
$f = "bits[".hexdec(str_replace("0x", "", $bits[2]))."]";
break;
case "write<uchar>":
$f = "ubyte";
break;
case "write<ushort>":
$f = "ushort";
break;
case "write<short>":
$f = "short";
break;
case "write<int>":
$f = "int";
break;
case "write<float>":
$f = "float";
break;
case "write<long>":
$f = "long";
break;
case "write<char>":
case "write<signed char>":
$f = "byte";
break;
case "packall":
case "pack":
$f = "Metadata";
break;
case "writeiteminstance":
$f = "Item";
break;
case "serialize":
case "writestring":
$f = "String";
break;
case "raknetguid>":
$f = "GUID";
break;
case "isnetworkorder":
case "isnetworkorderinternal":
case "rakstring":
case "reset":
case "reversebytes":
case "doendianswap":
case "clamp":
case "rot_degreestochar":
$f = false;
break;
}
$o = $fn[1][1];
if($f === false){
continue;
}
if($cnt === 0){
$head = $f;
++$cnt;
continue;
}
$funcs[] = $f;
}
$packetlist[hexdec($id)] = array(
"id" => "0x".$id,
"name" => $name,
"direction" => $dir,
"head" => $head,
"structure" => $funcs,
);
}
ksort($packetlist);
//Get mobs !!
$moblist = array();
$mobs = getFunction($lst, "MobFactory\:\:CreateMob");
preg_match_all('/BL[\t ]{1,}[a-zA-Z0-9_]*[\t ]{1,};[\t ]{1,}([A-Za-z0-9_\<\> \:]*)\(Level[\t ]{1,}\*\)/', $mobs, $names, PREG_SET_ORDER);
foreach($names as $mob){
$name = explode("::", $mob[1]);
$name = $name[1];
$fn = getFunction($lst, $name.'\:\:'.$name);
preg_match('/BL[\t ]{1,}[a-zA-Z0-9_]*[\t ]{1,};[\t ]{1,}([A-Za-z0-9_\<\> \:]*)\(Level[\t ]{1,}\*\)/', $fn, $type);
$type = explode("::", $type[1]);
$type = $type[1];
$fn = getFunction($lst, $name.'\:\:getEntityTypeId');
preg_match('/(MOVS|MOV\.W)[\t ]{1,}(R[0123456]|LR)\,[\t ]{1,}\#0x([0-9A-F]{1,2})[\r\n]{1,2}/', $fn, $id);
$id = hexdec($id[3]);
$moblist[$id] = array(
"id" => "0x".str_pad(dechex($id),2,"0", STR_PAD_LEFT),
"name" => $name,
"type" => $type,
);
}
ksort($moblist);
//Entities!!
$entitylist = array();
$ents = getFunction($lst, "EntityFactory\:\:CreateEntity");
preg_match_all('/BL[\t ]{1,}[a-zA-Z0-9_]*[\t ]{1,};[\t ]{1,}([A-Za-z0-9_\<\> \:]*)\(Level[\t ]{1,}\*\)/', $ents, $names, PREG_SET_ORDER);
foreach($names as $ent){
$name = explode("::", $ent[1]);
$name = $name[1];
if($name === "Throwable"){
continue; // :(
}
$fn = getFunction($lst, $name.'\:\:'.$name);
preg_match('/BL[\t ]{1,}[a-zA-Z0-9_]*[\t ]{1,};[\t ]{1,}([A-Za-z0-9_\<\> \:]*)\(Level[\t ]{1,}\*\)/', $fn, $type);
$type = explode("::", $type[1]);
$type = $type[1];
$fn = getFunction($lst, $name.'\:\:getEntityTypeId');
preg_match('/(MOVS|MOV\.W)[\t ]{1,}(R[0123456]|LR)\,[\t ]{1,}\#0x([0-9A-F]{1,2})[\r\n]{1,2}/', $fn, $id);
$id = hexdec($id[3]);
$entitylist[$id] = array(
"id" => "0x".str_pad(dechex($id),2,"0", STR_PAD_LEFT),
"name" => $name,
"type" => $type,
);
}
ksort($entitylist);
$tilelist = array();
$tiles = getFunction($lst, "TileEntityFactory\:\:createTileEntity");
preg_match_all('/BL[\t ]{1,}[a-zA-Z0-9_]*[\t ]{1,};[\t ]{1,}([A-Za-z0-9_\<\> \:]*)\(void\)/', $tiles, $names, PREG_SET_ORDER);
foreach($names as $tile){
$name = explode("::", $tile[1]);
$name = $name[1];
$tilelist[] = $name;
}
$blocklist = array();
$blocks = explode("\n", getFunction($lst, "Tile\:\:initTiles"));
//Thanks @zhuowei, https://gist.github.com/zhuowei/4416929
foreach($blocks as $line){
$line = trim(str_replace("\t", " ", $line));
$isConstructor = false;
if(strpos($line, "BL") !== false and strpos($line, "::") !== false and strpos($line, "Tile") !== false){
$start = strpos($line, ";") + 1;
$funcname = explode("::", trim(substr($line, $start, strpos($line, "(") - $start)));
if($funcname[0] == $funcname[1]){
$isConstructor = true;
}
}
if($isConstructor){
preg_match('/[0x#]{1,3}([0-9A-F]*)/', $lastMov, $matches);
$block = hexdec($matches[1]);
if(!isset($blocklist[$block])){
$blocklist[$block] = array();
}
}elseif(strpos($line, "MOV") !== false and strpos($line, "MOV.W") === false and strpos($line, "R1, #") !== false){
$lastMov = $line;
}
}
ksort($blocklist);
$info = array(
"extractor" => EXTRACTOR_VERSION,
"protocol" => $protocol,
"version" => $version,
"packets" => $packetlist,
"mobs" => $moblist,
"entities" => $entitylist,
"tiles" => $tilelist,
"blocks" => $blocklist,
);
echo json_encode($info, JSON_PRETTY_PRINT);
<?php
ini_set("memory_limit", "512M");
$info = json_decode(@file_get_contents($argv[1]), true);
if($info == ""){
echo "Provide a valid json file produced by the Extractor".PHP_EOL;
exit(1);
}
echo "# Minecraft PE ".$info["version"]." Protocol #".$info["protocol"].PHP_EOL.PHP_EOL;
echo "# ".count($info["packets"])." identified Packets".PHP_EOL;
$last = 0;
foreach($info["packets"] as $id => $pk){
if($last > 0 and ($id - 1) > $last){
echo PHP_EOL;
}
$last = $id;
$dire = array(
0 => "[C ??? S]",
1 => "[C <== S]",
2 => "[C ==> S]",
3 => "[C <=> S]",
);
echo $dire[$pk["direction"]]." ".$pk["id"]." ".$pk["name"]." (";
echo implode(", ", $pk["structure"]).")".PHP_EOL;
}
echo PHP_EOL.PHP_EOL;
echo "# ".count($info["mobs"])." identified Mobs".PHP_EOL;
$last = 0;
foreach($info["mobs"] as $id => $mob){
if($last > 0 and ($id - 1) > $last){
echo PHP_EOL;
}
$last = $id;
echo $mob["id"]." ".$mob["name"]." (".$mob["type"].")".PHP_EOL;
}
echo PHP_EOL.PHP_EOL;
echo "# ".count($info["entities"])." identified Entities".PHP_EOL;
$last = 0;
foreach($info["entities"] as $id => $ent){
if($last > 0 and ($id - 1) > $last){
echo PHP_EOL;
}
$last = $id;
echo $ent["id"]." ".$ent["name"]." (".$ent["type"].")".PHP_EOL;
}
echo PHP_EOL.PHP_EOL;
echo "# ".count($info["tiles"])." identified TileEntities".PHP_EOL;
foreach($info["tiles"] as $id => $t){
echo $t.PHP_EOL;
}
echo PHP_EOL.PHP_EOL;
echo "# ".count($info["blocks"])." identified Blocks".PHP_EOL;
foreach($info["blocks"] as $id => $t){
echo $id. " (0x".dechex($id).")".PHP_EOL;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment