ET 框架的一个常用通信通道KService
,用来游戏和服务器进行rpc通信
本质是一个或多个kcp包拼接起来,并再追加一个会话识别编号
抓包并自动提取protobuf rpc内容
ET 框架的一个常用通信通道KService
,用来游戏和服务器进行rpc通信
本质是一个或多个kcp包拼接起来,并再追加一个会话识别编号
抓包并自动提取protobuf rpc内容
if NOT [%1]==[] GOTO single | |
start boot.bat 11002 | |
start boot.bat 11003 | |
start boot.bat 11004 | |
exit | |
:single | |
php proxy.php %1 | cat |
<?php | |
chdir(__DIR__); | |
require 'UnityBundle.php'; | |
use Workerman\Connection\UdpConnection; | |
use Workerman\Connection\AsyncUdpConnection; | |
use Workerman\Worker; | |
use Workerman\Timer; | |
require_once __DIR__ . '/vendor/autoload.php'; | |
$linkPool = []; | |
$port = 11004; | |
if (isset($argv[1])) { | |
$port = $argv[1]; | |
} | |
define('PORT', $port); | |
define('ID_MAP', json_decode(file_get_contents('../idMap.json'), true)); | |
$proxy = new Worker('udp://0.0.0.0:'.PORT); | |
$proxy->onWorkerStart = function ($worker) { | |
}; | |
$prevConnection = null; | |
$upstream = new AsyncUdpConnection('udp://alpha.anxiens.com:'.PORT); | |
$upstream->onMessage = function ($c, $data) { | |
global $prevConnection; | |
if (!$prevConnection) return; | |
$data = str_replace(['106.75.223.112:11003','106.75.223.112:11004'], ['192.168.2.198:11003 ','192.168.2.198:11004 '], $data); | |
$prevConnection->send($data); | |
echo PORT." s->c\t" . strlen($data) . "\n"; | |
try { | |
readKServiceBody($data); | |
} catch (Exception $_) {} | |
}; | |
$proxy->onMessage = function ($connection, $data) { | |
echo PORT." c->s\t" . strlen($data) . "\n"; | |
global $prevConnection; | |
global $upstream; | |
// if ($prevConnection) { | |
// $prevConnection->close(); | |
// } | |
$prevConnection = $connection; | |
$upstream->connect(); | |
$upstream->send($data); | |
try { | |
readKServiceBody($data); | |
} catch (Exception $_) {} | |
}; | |
$fragments = []; | |
function getFullDataStart($conv, $sn, $frg) { | |
global $fragments; | |
$convFrag = &$fragments[$conv]; | |
if ($frg === 0) { | |
if ($sn === 0) return 0; | |
// 往前找到上一个frg为0的分片 | |
for ($i=$sn-1; $i>=0; $i--) { | |
if (!isset($convFrag[$i])) return -1; | |
if ($convFrag[$i] === true) return $i+1; | |
if ($convFrag[$i]['frg'] === 0) return $i+1; | |
} | |
return -1; | |
} | |
if (isset($convFrag[$sn]) && $convFrag[$sn] === true) return -1; | |
if (!isset($convFrag[$sn + $frg]) || $convFrag[$sn + $frg] === true) return -1; | |
return getFullDataStart($conv, $sn + $frg, 0); | |
} | |
function receivedBody($info, $body) { | |
global $fragments; | |
$conv = $info['conv']; | |
$sn = $info['sn']; | |
$frg = $info['frg']; | |
if (!isset($fragments[$conv])) $fragments[$conv] = []; | |
$fragments[$conv][$sn] = ['body' => $body, 'frg' => $frg]; | |
if (($start = getFullDataStart($conv, $sn, $frg)) !== -1) { | |
$count = $fragments[$conv][$start]['frg']; | |
$body = ''; | |
for ($i=$start; $i<=$start+$count; $i++) { | |
$body .= $fragments[$conv][$i]['body']; | |
$fragments[$conv][$i] = true; | |
} | |
$size = strlen($body); | |
echo "$sn\t$frg\tfound\t$start count $count\tsize $size\n"; | |
return $body; | |
} | |
return null; | |
} | |
function readKServiceBody($data) { | |
$type = ord($data[0]); | |
switch ($type) { | |
case 1: { | |
echo "\t\t\tConnect\n"; | |
break; | |
} | |
case 2: { | |
echo "\t\t\tConnect Ack\n"; | |
break; | |
} | |
case 3: { | |
echo "\t\t\tKService Recv Fin\n"; | |
break; | |
} | |
case 4: { | |
// echo "\t\t\tKService Data\n"; | |
$kcpStream = new MemoryStream($data); | |
$kcpStream->littleEndian = true; | |
$kcpStream->position = 5; | |
while ($kcpStream->position < $kcpStream->size) { | |
// echo "kcp\n"; | |
$info = [ | |
'conv' => $kcpStream->long, | |
'cmd' => ord($kcpStream->byte), | |
'frg' => ord($kcpStream->byte), | |
'wnd' => $kcpStream->short, | |
'ts' => $kcpStream->long, | |
'sn' => $kcpStream->long, | |
'una' => $kcpStream->long, | |
'len' => $kcpStream->long, | |
]; | |
$len = $info['len']; | |
if ($len <= 2) { | |
$kcpStream->position += $len; | |
continue; | |
} | |
$body = $kcpStream->readData($len); | |
$body = receivedBody($info, $body); | |
if ($body === null) continue; | |
$payload = new MemoryStream($body); | |
$payload->littleEndian = true; | |
$messageId = $payload->short; | |
if (!isset(ID_MAP[$messageId])) { | |
echo "Unknown message id: $messageId\n"; | |
continue; | |
} | |
$message = ID_MAP[$messageId]; | |
if (in_array($message, ['C2G_Ping', 'G2C_Ping'])) { | |
// skip | |
continue; | |
} | |
echo "$message: "; | |
$proto = parseProto('../'.$message.'_gen.proto'); | |
$data = parseProtoBuf($message, $payload->size - 2, $payload, $proto); | |
// print_r($data); | |
processDict($data); | |
$output = prettifyJSON($data); | |
echo $output."\n"; | |
@mkdir("log/".PORT, 0777, true); | |
file_put_contents("log/".PORT."/".date('Ymd-His').'-'.$info['sn'].'-'.$message.".json", $output); | |
} | |
echo "\n"; | |
break; | |
} | |
} | |
} | |
function parseProto($file) { | |
$proto = file_get_contents($file); | |
$proto = preg_replace('(//.+\n)', "\n", $proto); | |
preg_match_all('(message (\w+) \{([\w\W]+?)\})', $proto, $messages); | |
$output = []; | |
for ($i=0; $i<count($messages[0]); $i++) { | |
$messageName = $messages[1][$i]; | |
$lines = explode(';', trim($messages[2][$i], "; \n")); | |
$output[$messageName] = []; | |
foreach ($lines as $line) { | |
$fields = explode('=', trim($line)); | |
$tag = trim($fields[1]); | |
$fields = explode(' ', trim($fields[0])); | |
$output[$messageName][$tag] = array( | |
'repeated' => $fields[0] == 'repeated', | |
'type' => $fields[1], | |
'name' => $fields[2] | |
); | |
} | |
} | |
return $output; | |
} | |
function readVarInt32($pb) { | |
$b = ord($pb->readData(1)); | |
$result = $b & 0x7f; | |
$shift = 0; | |
while ($b & 0x80) { | |
$shift += 7; | |
if ($shift > 64) throw new Exception('Too many bytes for varint'); | |
$b=ord($pb->readData(1)); | |
$result |= ($b & 0x7f) << $shift; | |
} | |
return $result; | |
} | |
function setValue(&$arr, $key, &$val, $isArr) { | |
if ($isArr) { | |
if (!isset($arr[$key])) $arr[$key] = []; | |
$arr[$key][] = $val; | |
} else { | |
$arr[$key] = $val; | |
} | |
} | |
function parseProtoBuf($messageName, $end, &$pb, &$proto, $messageCallback = null, $level = 1) { | |
$message = []; | |
while ($pb->position() < $end){ | |
$id = readVarInt32($pb); | |
$type = $id & 7; | |
$id = $id >> 3; | |
if (isset($proto[$messageName][$id])) { | |
$def = $proto[$messageName][$id]; | |
} else { | |
$def = array('type'=>'byte', 'name'=>'UNKNOWN_'.$id, 'repeated'=>false); | |
} | |
$typeName = $def['type']; | |
$name = $def['name']; | |
$isRepeated = $def['repeated']; | |
switch($type){ | |
case 0: | |
$data = readVarInt32($pb); | |
setValue($message, $name, $data, $isRepeated); | |
break; | |
case 1: | |
$data = $pb->readData(8); | |
if ($typeName == 'double') { | |
$data = unpack('d', $data)[1]; | |
} else { | |
$data = '<fix64> '.bin2hex($data); | |
} | |
setValue($message, $name, $data, $isRepeated); | |
break; | |
case 2: | |
$length = readVarInt32($pb); | |
if (isset($proto[$typeName])) { | |
$sub = parseProtoBuf($typeName, $pb->position()+$length, $pb, $proto, $messageCallback, $level + 1); | |
if ($messageCallback === null || call_user_func($messageCallback, $name, $sub, $level)) { | |
setValue($message, $name, $sub, $isRepeated); | |
} | |
} else if ($length > 0) { | |
$data = $pb->readData($length); | |
if ($typeName == 'string') { | |
} else { | |
if (mb_detect_encoding($data, 'UTF-8', true)) { | |
$data = '<guessed string> '.$data; | |
} else { | |
$data = '<byte> '.bin2hex($data); | |
} | |
} | |
setValue($message, $name, $data, $isRepeated); | |
} | |
break; | |
case 5: | |
$data = $pb->readData(4); | |
if ($typeName == 'float') { | |
$data = unpack('f', $data)[1]; | |
} else { | |
$data = '<fix32> '.bin2hex($data); | |
} | |
setValue($message, $name, $data, $isRepeated); | |
break; | |
default: | |
throw new Exception('unsupported type '.$type); | |
} | |
} | |
return $message; | |
} | |
function processDict(&$arr) { | |
foreach ($arr as $key=>&$val) { | |
if (gettype($val) == 'array') { | |
if (isset($val[0]) && gettype($val[0]) == 'array') { | |
$keys = array_keys($val[0]); | |
if (count($keys) == 2 | |
&& substr($keys[0], -4, 4) == '_key' | |
&& substr($keys[1], -6, 6) == '_value') { | |
$newDict = []; | |
foreach ($val as &$item) { | |
$newDict[ $item[$keys[0]] ] = $item[$keys[1]]; | |
} | |
unset($arr[$key]); | |
$arr[$key] = $newDict; | |
$val = &$arr[$key]; | |
} | |
} | |
processDict($val); | |
$keys = array_keys($val); | |
if (count($keys) == 1 && $keys[0] === 'entries') { | |
$arr[$key] = $val['entries']; | |
} | |
} | |
} | |
} | |
function prettifyJSON($in, Stream $out = NULL, $returnData = true) { | |
if ($out == NULL) $out = new MemoryStream(''); | |
$a = $in; | |
if (gettype($a) == 'string') $a = json_decode($a); | |
$a = json_encode($a, JSON_UNESCAPED_UNICODE+JSON_UNESCAPED_SLASHES+JSON_PRETTY_PRINT); | |
$a = preg_replace("/([^\]\}]),\n +/", "$1, ", $a); | |
$a = preg_replace('/("[^"]+?":) /', '$1', $a); | |
$a = preg_replace_callback("/\n +/", function ($m) { return "\n".str_repeat(' ', (strlen($m[0])-1) / 2); }, $a); | |
$out->write($a); | |
if (!$returnData) return; | |
$out->seek(0); | |
$output = $out->readData($out->size); | |
unset($out); | |
return $output; | |
} | |
Worker::runAll(); |