打包逻辑重构

master
ELF 5 years ago
parent 69a6f9612f
commit bcc34cbd0c

@ -54,8 +54,8 @@ class GameSourceController extends ThinkController
$extend = substr($_POST['file_name'], strlen($_POST['file_name']) - 3, 3);
if ($_POST['file_type'] == 1 && $extend != 'apk') {
$this -> error('游戏原包格式不正确!');
} else if ($_POST['file_type'] == 2 && $extend != 'ipa' || $_POST['file_type'] == 2 && empty($_POST['bao_name'])) {
$this -> error('游戏原包格式不正确/包名不能为空');
} else if ($_POST['file_type'] == 2 && $extend != 'ipa') {
$this -> error('游戏原包格式不正确');
}
}
$map['game_id'] = $_POST['game_id'];
@ -136,17 +136,17 @@ class GameSourceController extends ThinkController
$extend = substr($_POST['file_name'], strlen($str) - 3, 3);
if ($_POST['file_type'] == 1 && $extend != 'apk') {
$this -> error('游戏原包格式不正确!');
} else if ($_POST['file_type'] == 2 && $extend != 'ipa' || $_POST['file_type'] == 2 && empty($_POST['bao_name'])) {
$this -> error('游戏原包格式不正确/包名不能为空');
} else if ($_POST['file_type'] == 2 && $extend != 'ipa') {
$this -> error('游戏原包格式不正确');
}
}
$map['file_type'] = $_POST['file_type'];
$d = D('Game_source') -> where($map) -> find();
$source = A('Source', 'Event');
if (empty($d)) {
$source -> add_source();
$source->add_source();
} else {
$source -> update_source($d['id'], $d['file_name']);
$source->update_source($d['id'], $d['file_name']);
}
} else {
$d = M('GameSource', "tab_") -> where($map) -> find();

@ -10,6 +10,8 @@ namespace Admin\Event;
use Think\Controller;
use Base\Service\GameSourceService;
use Base\Tool\PlistParser;
use Base\Tool\ApkParser;
/**
* 后台首页控制器
@ -26,12 +28,27 @@ class SourceEvent extends Controller
$data['file_url'] = $data['file_url'] . "/" . $data['file_name'];
$data['sdk_version'] = $data['file_type'];
$data['op_id'] = UID;
$data['version'] = $_POST['version'];
$data['op_account'] = session("user_auth.username");
$data['create_time'] = NOW_TIME;
$remark = str_replace(array("\r\n", "\r", "\n"), "@@@", $_POST['remark']);
$data['remark'] = json_encode(explode('@@@', $remark));
$data['source_version'] = 0;
$version = '';
$packageName = '';
if ($data['file_type'] == 1) {
$parser = new ApkParser();
$parser->open($data['file_url']);
$version = $parser->getVersionName();
$packageName = $parser->getPackage();
} elseif($data['file_type'] == 2) {
$parser = new PlistParser();
$parser->openFromIpa($data['file_url'], PlistParser::PREG_INFO_PLIST);
$result = $parser->getResult();
$version = $result['CFBundleShortVersionString'];
$packageName = $result['CFBundleIdentifier'];
}
$data['version'] = $version;
$data['bao_name'] = $packageName;
$game = M('game', 'tab_')->where(['id' => $data['game_id']])->find();
$gameSourceService = new GameSourceService();
$result = $gameSourceService->sourcePack($data, $game);
@ -85,6 +102,7 @@ class SourceEvent extends Controller
$data['source_version'] = $game_source['source_version'] + 1;
$game = M('game', 'tab_')->where(['id' => $data['game_id']])->find();
$gameSourceService = new GameSourceService();
$result = $gameSourceService->sourcePack($data, $game);
if (!$result['status']) {
if ($from == "dev") {

@ -123,13 +123,13 @@
<tr>
<td class="l"><i class="mustmark" style="margin-left:-7px;display:none" id="apply_bz">*</i>包名:</td>
<td class="r">
<input type="text" class="txt" name="bao_name" value="">
<input type="text" class="txt" name="bao_name" style="background:#E7E7E7" value="无需填写,自动生成" disabled="disabled">
</td>
</tr>
<tr>
<td class="l">原包版本:</td>
<td class="r">
<input type="text" class="txt" name="version" value="">
<input type="text" class="txt" name="version" style="background:#E7E7E7" value="无需填写,自动生成" disabled="disabled">
</td>
</tr>

@ -110,14 +110,13 @@
</eq>
包名:</td>
<td class="r">
<input type="text" class="txt" name="bao_name" value="{$data['bao_name']}">
<input type="text" class="txt" name="bao_name" style="background:#E8E8E8" value="{$data['bao_name']}" disabled="disabled" readonly="true">
</td>
</tr>
<tr>
<td class="l">原包版本:</td>
<td class="r">
<input type="text" class="txt table_btn_input" name="version" value="{$data['version']}" readonly="true">
<a href="javascript:" id='version_click' class="table_click">点击修改</a>
<input type="text" class="txt table_btn_input" style="background:#E8E8E8" name="version" value="{$data['version']}" disabled="disabled" readonly="true">
</td>
</tr>

@ -0,0 +1,450 @@
<?php
namespace Base\Tool;
use \ZipArchive;
/**
* 用于读取apk信息
* @author elf<360197197@qq.com>
*/
class ApkParser
{
const AXML_FILE = 0x00080003;
const STRING_BLOCK = 0x001C0001;
const RESOURCEIDS = 0x00080180;
const START_NAMESPACE = 0x00100100;
const END_NAMESPACE = 0x00100101;
const START_TAG = 0x00100102;
const END_TAG = 0x00100103;
const TEXT = 0x00100104;
const TYPE_NULL = 0;
const TYPE_REFERENCE = 1;
const TYPE_ATTRIBUTE = 2;
const TYPE_STRING = 3;
const TYPE_FLOAT = 4;
const TYPE_DIMENSION = 5;
const TYPE_FRACTION = 6;
const TYPE_INT_DEC = 16;
const TYPE_INT_HEX = 17;
const TYPE_INT_BOOLEAN = 18;
const TYPE_INT_COLOR_ARGB8 = 28;
const TYPE_INT_COLOR_RGB8 = 29;
const TYPE_INT_COLOR_ARGB4 = 30;
const TYPE_INT_COLOR_RGB4 = 31;
const UNIT_MASK = 15;
private static $radixMults = [0.00390625, 3.051758E-005, 1.192093E-007, 4.656613E-010];
private static $dimensionUnits = ["px","dip","sp","pt","in","mm","",""];
private static $fractionUnits = ["%","%p","","","","","",""];
private $xml='';
private $length = 0;
private $stringCount = 0;
private $styleCount = 0;
private $stringTab = [];
private $styleTab = [];
private $resourceIDs = [];
private $ns = [];
private $cur_ns = null;
private $root = null;
private $line = 0;
public function open($apkFile, $xmlFile='AndroidManifest.xml')
{
$zip = new ZipArchive;
if ($zip->open($apkFile) === true) {
$xml = $zip->getFromName($xmlFile);
$zip->close();
if ($xml){
try {
return $this->parseString($xml);
}catch (Exception $e){
}
}
}
return false;
}
public function parseString($xml)
{
$this->xml = $xml;
$this->length = strlen($xml);
$this->root = $this->parseBlock(self::AXML_FILE);
return true;
}
public function getXML($node = null, $lv = -1)
{
if ($lv == -1) $node = $this->root;
if (!$node) return '';
if ($node['type'] == self::END_TAG) $lv--;
$xml = ($node['line'] == 0 || $node['line'] == $this->line) ? '' : "\n".str_repeat(' ', $lv);
$xml .= $node['tag'];
$this->line = $node['line'];
foreach ($node['child'] as $c){
$xml .= $this->getXML($c, $lv+1);
}
return $xml;
}
public function getPackage()
{
return $this->getAttribute('manifest', 'package');
}
public function getVersionName()
{
return $this->getAttribute('manifest', 'android:versionName');
}
public function getVersionCode()
{
return $this->getAttribute('manifest', 'android:versionCode');
}
public function getAppName()
{
return $this->getAttribute('manifest/application', 'android:name');
}
public function getMainActivity()
{
for ($id=0; true; $id++){
$act = $this->getAttribute("manifest/application/activity[{$id}]/intent-filter/action", 'android:name');
if (!$act) break;
if ($act == 'android.intent.action.MAIN') return $this->getActivity($id);
}
return null;
}
public function getActivity($idx=0)
{
$idx = intval($idx);
return $this->getAttribute("manifest/application/activity[{$idx}]", 'android:name');
}
public function getAttribute($path, $name)
{
$r = $this->getElement($path);
if (is_null($r)) {
return null;
}
if (isset($r['attrs'])){
foreach ($r['attrs'] as $a) {
if ($a['ns_name'] == $name) {
return $this->getAttributeValue($a);
}
}
}
return null;
}
private function getElement($path)
{
if (!$this->root) return NULL;
$ps = explode('/', $path);
$r = $this->root;
foreach ($ps as $v){
if (preg_match('/([^ ]+)\[([09]+)$/', $v, $ms)){
$v = $ms[1];
$off = $ms[2];
}else {
$off = 0;
}
foreach ($r['child'] as $c){
if ($c['type'] == self::START_TAG && $c['ns_name'] == $v){
if ($off == 0){
$r = $c; continue 2;
}else {
$off--;
}
}
}
// 没有找到节点
return NULL;
}
return $r;
}
private function parseBlock($need = 0)
{
$o = 0;
$type = $this->get32($o);
if ($need && $type != $need) throw new Exception('Block Type Error', 1);
$size = $this->get32($o);
if ($size < 8 || $size > $this->length) throw new Exception('Block Size Error', 2);
$left = $this->length - $size;
$props = false;
switch ($type){
case self::AXML_FILE:
$props = [
'line' => 0,
'tag' => '<?xml version="1.0" encoding="utf-8"?>'
];
break;
case self::STRING_BLOCK:
$this->stringCount = $this->get32($o);
$this->styleCount = $this->get32($o);
$o += 4;
$strOffset = $this->get32($o);
$styOffset = $this->get32($o);
$strListOffset = $this->get32array($o, $this->stringCount);
$styListOffset = $this->get32array($o, $this->styleCount);
$this->stringTab = $this->stringCount > 0 ? $this->getStringTab($strOffset, $strListOffset) : [];
$this->styleTab = $this->styleCount > 0 ? $this->getStringTab($styOffset, $styListOffset) : [];
$o = $size;
break;
case self::RESOURCEIDS:
$count = $size / 4 - 2;
$this->resourceIDs = $this->get32array($o, $count);
break;
case self::START_NAMESPACE:
$o += 8;
$prefix = $this->get32($o);
$uri = $this->get32($o);
if (empty($this->cur_ns)){
$this->cur_ns = [];
$this->ns[] = &$this->cur_ns;
}
$this->cur_ns[$uri] = $prefix;
break;
case self::END_NAMESPACE:
$o += 8;
$prefix = $this->get32($o);
$uri = $this->get32($o);
if (empty($this->cur_ns)) {
break;
}
unset($this->cur_ns[$uri]);
break;
case self::START_TAG:
$line = $this->get32($o);
$o += 4;
$attrs = [];
$props = array(
'line' => $line,
'ns' => $this->getNameSpace($this->get32($o)),
'name' => $this->getString($this->get32($o)),
'flag' => $this->get32($o),
'count' => $this->get16($o),
'id' => $this->get16($o)-1,
'class' => $this->get16($o)-1,
'style' => $this->get16($o)-1,
'attrs' => &$attrs
);
$props['ns_name'] = $props['ns'].$props['name'];
for ($i=0; $i < $props['count']; $i++){
$a = array(
'ns' => $this->getNameSpace($this->get32($o)),
'name' => $this->getString($this->get32($o)),
'val_str' => $this->get32($o),
'val_type' => $this->get32($o),
'val_data' => $this->get32($o)
);
$a['ns_name'] = $a['ns'].$a['name'];
$a['val_type'] >>= 24;
$attrs[] = $a;
}
// 处理TAG字符串
$tag = "<{$props['ns_name']}";
foreach ($this->cur_ns as $uri => $prefix){
$uri = $this->getString($uri);
$prefix = $this->getString($prefix);
$tag .= " xmlns:{$prefix}=\"{$uri}\"";
}
foreach ($props['attrs'] as $a){
$tag .= " {$a['ns_name']}=\"".
$this->getAttributeValue($a).
'"';
}
$tag .= '>';
$props['tag'] = $tag;
unset($this->cur_ns);
$this->cur_ns = [];
$this->ns[] = &$this->cur_ns;
$left = -1;
break;
case self::END_TAG:
$line = $this->get32($o);
$o += 4;
$props = array(
'line' => $line,
'ns' => $this->getNameSpace($this->get32($o)),
'name' => $this->getString($this->get32($o))
);
$props['ns_name'] = $props['ns'].$props['name'];
$props['tag'] = "</{$props['ns_name']}>";
if (count($this->ns) > 1){
array_pop($this->ns);
unset($this->cur_ns);
$this->cur_ns = array_pop($this->ns);
$this->ns[] = &$this->cur_ns;
}
break;
case self::TEXT:
$o += 8;
$props = array(
'tag' => $this->getString($this->get32($o))
);
$o += 8;
break;
default:
throw new Exception('Block Type Error', 3);
break;
}
$this->skip($o);
$child = [];
while ($this->length > $left){
$c = $this->parseBlock();
if ($props && $c) $child[] = $c;
if ($left == -1 && $c['type'] == self::END_TAG){
$left = $this->length;
break;
}
}
if ($this->length != $left) throw new Exception('Block Overflow Error', 4);
if ($props){
$props['type'] = $type;
$props['size'] = $size;
$props['child'] = $child;
return $props;
}else {
return false;
}
}
private function getAttributeValue($a)
{
$type = &$a['val_type'];
$data = &$a['val_data'];
switch ($type){
case self::TYPE_STRING:
return $this->getString($a['val_str']);
case self::TYPE_ATTRIBUTE:
return sprintf('?%s%08X', self::_getPackage($data), $data);
case self::TYPE_REFERENCE:
return sprintf('@%s%08X', self::_getPackage($data), $data);
case self::TYPE_INT_HEX:
return sprintf('0x%08X', $data);
case self::TYPE_INT_BOOLEAN:
return ($data != 0 ? 'true' : 'false');
case self::TYPE_INT_COLOR_ARGB8:
case self::TYPE_INT_COLOR_RGB8:
case self::TYPE_INT_COLOR_ARGB4:
case self::TYPE_INT_COLOR_RGB4:
return sprintf('#%08X', $data);
case self::TYPE_DIMENSION:
return $this->_complexToFloat($data).self::$dimensionUnits[$data & self::UNIT_MASK];
case self::TYPE_FRACTION:
return $this->_complexToFloat($data).self::$fractionUnits[$data & self::UNIT_MASK];
case self::TYPE_FLOAT:
return $this->_int2float($data);
}
if ($type >=self::TYPE_INT_DEC && $type < self::TYPE_INT_COLOR_ARGB8){
return (string)$data;
}
return sprintf('<0x%X, type 0x%02X>', $data, $type);
}
private function _complexToFloat($data)
{
return (float)($data & 0xFFFFFF00) * self::$radixMults[($data>>4) & 3];
}
private function _int2float($v)
{
$x = ($v & ((1 << 23) - 1)) + (1 << 23) * ($v >> 31 | 1);
$exp = ($v >> 23 & 0xFF) - 127;
return $x * pow(2, $exp - 23);
}
private static function _getPackage($data)
{
return ($data >> 24 == 1) ? 'android:' : '';
}
private function getStringTab($base, $list)
{
$tab = [];
foreach ($list as $off){
$off += $base;
$len = $this->get16($off);
$mask = ($len >> 0x8) & 0xFF;
$len = $len & 0xFF;
if ($len == $mask){
if ($off + $len > $this->length) throw new Exception('String Table Overflow', 11);
$tab[] = substr($this->xml, $off, $len);
}else {
if ($off + $len * 2 > $this->length) throw new Exception('String Table Overflow', 11);
$str = substr($this->xml, $off, $len * 2);
$tab[] = mb_convert_encoding($str, 'UTF-8', 'UCS-2LE');
}
}
return $tab;
}
private function getString($id)
{
if ($id > -1 && $id < $this->stringCount){
return $this->stringTab[$id];
}else {
return '';
}
}
private function getNameSpace($uri)
{
for ($i=count($this->ns); $i > 0; ){
$ns = $this->ns[--$i];
if (isset($ns[$uri])){
$ns = $this->getString($ns[$uri]);
if (!empty($ns)) $ns .= ':';
return $ns;
}
}
return '';
}
private function get32(&$off)
{
$int = unpack('V', substr($this->xml, $off, 4));
$off += 4;
return array_shift($int);
}
private function get32array(&$off, $size)
{
if ($size <= 0) {
return null;
}
$arr = unpack('V*', substr($this->xml, $off, 4 * $size));
if (count($arr) != $size) throw new Exception('Array Size Error', 10);
$off += 4 * $size;
return $arr;
}
private function get16(&$off)
{
$int = unpack('v', substr($this->xml, $off, 2));
$off += 2;
return array_shift($int);
}
private function skip($size)
{
$this->xml = substr($this->xml, $size);
$this->length -= $size;
}
}

@ -0,0 +1,102 @@
<?php
namespace Base\Tool;
use \ZipArchive;
/**
* 用于读取plist文件信息
* @author elf<360197197@qq.com>
*/
class PlistParser
{
const PREG_INFO_PLIST = "/^Payload.*?\.app\/Info.plist$/";
private $xml;
public function openFromIpa($ipaFile, $preg)
{
$zip = new ZipArchive;
if ($zip->open($ipaFile) === true) {
$index = -1;
for( $i = 0; $i < $zip->numFiles; $i++ ){
$stat = $zip->statIndex($i);
if (preg_match($preg, $stat['name'], $matches)) {
$index = $stat['index'];
}
}
$content = $zip->getFromIndex($index);
$zip->close();
if ($content){
$xml = new \DOMDocument();
$xml->loadXML($content);
$this->xml = $xml;
return true;
}
}
die();
return false;
}
public function open($xmlFile)
{
$xml = new \DOMDocument();
$xml->load($xmlFile);
$this->xml = $xml;
return true;
}
public function getResult()
{
$plistTag = $this->xml->getElementsByTagName('plist');
$dict = $plistTag->item(0)->childNodes->item(1);
return $this->parseDictNode($dict);
}
public function parseDictNode($parentNode)
{
$lastKey = '';
$dict = [];
foreach ($parentNode->childNodes as $node) {
if ($node instanceof \DOMElement) {
if ($node->nodeName == 'key') {
$lastKey = $node->textContent;
} else {
if ($node->nodeName == 'dict') {
$dict[$lastKey] = $this->parseDictNode($node);
} elseif ($node->nodeName == 'array') {
$dict[$lastKey] = $this->parseArrayNode($node);
} elseif($node->nodeName == 'true') {
$dict[$lastKey] = true;
} elseif($node->nodeName == 'false') {
$dict[$lastKey] = false;
} else {
$dict[$lastKey] = $node->textContent;
}
}
}
}
return $dict;
}
public function parseArrayNode($parentNode)
{
$list = [];
foreach ($parentNode->childNodes as $node) {
if ($node instanceof \DOMElement) {
if ($node->nodeName == 'dict') {
$list[] = $this->parseDictNode($node);
} elseif ($node->nodeName == 'array') {
$list[] = $this->parseArrayNode($node);
} elseif($node->nodeName == 'true') {
$list[] = true;
} elseif($node->nodeName == 'false') {
$list[] = false;
} else {
$list[] = $node->textContent;
}
}
}
return $list;
}
}
Loading…
Cancel
Save