一,安卓V2签名加入渠道号
1,调用生成安卓子包自定义函数
$apk_name = 'demo.apk'; // 安卓包名
$channel_id = 1; // 需要加入的渠道号
try {
$apk_url =$this->setapk($apk_name ,$channel_id);
}catch (\Exception $e){
$this->error($e->getMessage());
}
//得到生成的子包存放地址
var_dump($apk_url );
exit;
2,自定义函数内确定母包地址和生成子包存放的地址
public function setapk($apk_name = '',$channel_id= 0){
//根据安卓包名得到名称与apk后缀
$appname_arr = explode('.',$apk_name);
//需要写入的渠道信息
$channel_json = json_encode(['channel'=>$channel_id]);
//母包地址
$m_rootpath = $_SERVER['DOCUMENT_ROOT'].'/static/app/'.$appname;
//拼接子包名称
$s_appname = $appname_arr[0].'_'.$channel_id.'.'.$appname_arr[1];
//指定子包存放的位置, /static/app/apk/子包名
$s_rootpath = $_SERVER['DOCUMENT_ROOT'].'/static/app/'.$appname_arr[1].'/'.$s_appname;
try {
$this->writeParam($m_rootpath,$s_rootpath,$channel_json);
$data = [
'local' => $s_rootpath,
'apk' => 'http://域名'.'/static/app/'.$appname_arr[1].'/'.$s_appname
];
return $data;
}catch (\Exception $e){
throw new \Exception('安卓子包生成失败:'.$e->getMessage());
}
}
private function readParam(string $filePath, bool $moreParams = false)
{
if (!file_exists($filePath)) {
throw new Exception(sprintf('需要分析的APK包 %s 不存在', $filePath));
}
$stream = fopen($filePath, 'r');
//跳到结尾 寻找中心目录偏移位置
fseek($stream, -6, SEEK_END);
//中心目录偏移位置
$centerDirectoryOffsetPosition = unpack('V*',fread($stream, 4))[1];
//检查是否存在魔法数
fseek($stream, $centerDirectoryOffsetPosition - 16);
if (fread($stream, 16) != 'APK Sig Block 42') {
throw new Exception('数据格式异常,不存在ApkSigBlock魔法数');
}
//获取V2签名块总大小
fseek($stream, ftell($stream) - 24);
$v2BlockSize = unpack('P*', fread($stream, 8))[1];
//获取v2签名块开始位置
$startPosition = $centerDirectoryOffsetPosition - 8 - $v2BlockSize + 0x00000010;
//获取v2签名 特殊标识ID 0x7109871a
fseek($stream, $startPosition);
bin2hex(fread($stream, 8));
// exit;
// if (bin2hex(fread($stream, 8)) != '1a870971ca030000') {
// throw new Exception('数据格式异常,不支持非V2签名进行操作');
// }
//获取第一个块大小
fseek($stream, -16, SEEK_CUR);
$blockSize1 = unpack('P*', fread($stream, 8))[1];
//获取第二个块大小
fseek($stream, $blockSize1, SEEK_CUR);
$blockSize2 = unpack('P*', fread($stream, 8))[1];
//获取第三个块大小
fseek($stream, $blockSize2, SEEK_CUR);
$blockSize3 = unpack('P*', fread($stream, 8))[1];
//缺失第三个包 暂时不知道为什么
if ($blockSize3 == $v2BlockSize) {
$blockSize3 = 0;
$attachContentSize = $v2BlockSize;
} else {
//获取附加信息块大小
//检查是否缺包打出来的子包
if (fread($stream, 4) == 'wwwq') {
//是缺失包
fseek($stream, -4, SEEK_CUR);
$attachContentSize = $blockSize3;
$blockSize3 = 0;
} else {
//不是缺失包
fseek($stream, -4, SEEK_CUR);
fseek($stream, $blockSize3, SEEK_CUR);
$attachContentSize = unpack('P*', fread($stream, 8))[1];
}
}
$moreInfo = [
'centerDirPosition' => $centerDirectoryOffsetPosition,
'v2BlockSize' => $v2BlockSize,
'v2BlockStartPosition' => $startPosition,
'b1Size' => $blockSize1,
'b2Size' => $blockSize2,
'b3Size' => $blockSize3,
];
$attachContent = '';
do {
//没有进行添加附加信息块 因为总大小与v2块总大小一致
if ($attachContentSize == $v2BlockSize) {
break;
}
//获取附加块信息
$attachContent = fread($stream, $attachContentSize);
fclose($stream);
break;
} while (false);
//返回更多信息
if ($moreParams) {
return array_merge(['attackContent' => $attachContent], $moreInfo);
}
return ['attackContent' => $attachContent];
}
private function writeParam(string $inputFile, string $outputFile, string $content)
{
if (!file_exists($inputFile)) {
throw new Exception(sprintf('masterApk包 %s 不存在', $inputFile));
}
$contentLength = strlen($content);
if ($contentLength == 0) {
throw new Exception('写入参数内容不能为空');
}
//补全兼容缺失包
$content = 'wwwq' . $content;
$contentLength = strlen($content);
$masterApkPackageInfo = $this->readParam($inputFile, true);
if ($masterApkPackageInfo['attackContent'] != '') {
throw new Exception('masterApk包不能是已经修改的包的');
}
$masterApkStream = fopen($inputFile, 'r');
$subPackageApkStream = fopen($outputFile, 'w');
//第一步 复制第一段内容
fwrite($subPackageApkStream, fread($masterApkStream, $masterApkPackageInfo['v2BlockStartPosition'] - 16));
//第二步 写出需要添加附加文本长度大小 与 偏移写出块大小
fwrite($subPackageApkStream, pack('P*', $masterApkPackageInfo['v2BlockSize'] + $contentLength + 8));
fseek($masterApkStream, 8, SEEK_CUR);
fwrite($subPackageApkStream, fread($masterApkStream, 8 + $masterApkPackageInfo['b1Size']));
fwrite($subPackageApkStream, fread($masterApkStream, 8 + $masterApkPackageInfo['b2Size']));
if ($masterApkPackageInfo['b3Size'] != 0) {
fwrite($subPackageApkStream, fread($masterApkStream, 8 + $masterApkPackageInfo['b3Size']));
}
//第三步 写出附加文本大小 与 附加文本内容
fwrite($subPackageApkStream, pack('P*', $contentLength));
fwrite($subPackageApkStream, $content);
//写出v2签名块大小
fwrite($subPackageApkStream, pack('P*', $masterApkPackageInfo['v2BlockSize'] + $contentLength + 8));
// exit(var_dump(222));
//复制后续内容
$copyLength = filesize($inputFile) - ftell($masterApkStream) - 14;
fseek($masterApkStream, 8, SEEK_CUR);
fwrite($subPackageApkStream, fread($masterApkStream, $copyLength));
fwrite($subPackageApkStream, pack('V*', $masterApkPackageInfo['centerDirPosition'] + $contentLength + 8));
fwrite($subPackageApkStream, pack('v*', 0));
fclose($masterApkStream);
fclose($subPackageApkStream);
}
3,怎么跟前端解释这个东西,前端怎么获取渠道号呢?
参考来源:安卓v2签名描述信息
参考来源: 点击跳转
二,苹果包怎么加入渠道号
1,调用生成苹果子包自定义函数
$ipa_name = 'demo.ipa'; // 安卓包名
$channel_id = 1; // 需要加入的渠道号
try {
$ipa_url = $this->setipa($ipa_name,$channel_id);
}catch (\Exception $e){
$this->error($e->getMessage());
}
//得到生成的子包存放地址
var_dump($ipa_url);
exit;
2,自定义函数内确定母包地址和生成子包存放的地址
public function setipa($ipa_name = '',$channel_id = 0){
//根据苹果包名得到名称与ipa后缀
$appname_arr = explode('.',$ipa_name);
//母包地址
$m_rootpath = $_SERVER['DOCUMENT_ROOT'].'/static/app/'.$ipa_name;
if (!file_exists($m_rootpath)) {
throw new \Exception("苹果母包不存在");
}
//拼接子包名称
$s_appname = $appname_arr[0].'_'.$channel_id.'.'.$appname_arr[1];
//指定子包存放的位置, /static/app/apk/子包名
$s_rootpath = $_SERVER['DOCUMENT_ROOT'].'/static/app/'.$appname_arr[1].'/'.$s_appname;
//复制母包生成子包
$copyipa = copy($m_rootpath,$s_rootpath);
if($copyipa){
$zip = new \ZipArchive();
$iszip = false;
if($zip->open($s_rootpath) === TRUE){
//在包内找到 _CodeSignature 文件夹,在同级目录下加入两个文件夹作为渠道号
//怎么找?可以用压缩软件直接打开然后慢慢找
//也可以先用压缩软件在里面直接创建文件夹看是否还能安装
//是用压缩软件打开后加文件夹,不是解压加文件夹再压缩
$zip->addEmptyDir( "Payload/Runner.app/channel");//这里加文件夹时需要一层一层的加,不然可能会出现找不到情况
$zip->addEmptyDir( "Payload/Runner.app/channel/".$channel_id);
$zip->close();
$iszip = true;
}
if($iszip){
$data = [
'local' => $s_rootpath,
'ipa' => 'http://域名'.'/static/app/'.$appname_arr[1].'/'.$s_appname
];
return $data;
}
}else{
throw new \Exception("苹果子包生成失败");
}
}
3,怎么跟前端解释这个东西,前端怎么获取渠道号呢?
需要找到包内文件夹的名称获取文件夹值
参考来源:点击跳转