qzuser 发表于 2022-8-6 20:54

申请会员ID:gfdgd xi【申请通过】

1、申 请 I D:gfdgd xi
2、个人邮箱:3025613752@qq.com
3、原创技术文章:对于 UEngine 运行器的代码分析(现写,全网唯一)
项目链接:https://gitee.com/gfdgd-xi/uengine-runner,代码均按照 GPLV3 协议开源


新版本Deepin/UOS发布后,可以在应用商店安装部分官方已适配的安卓应用,对爱好者来说,不能自己安装APK软件包始终差点意思,本程序可以为Deepin/UOS上的UEngine安卓运行环境安装自定义APK软件包,并能发送安装的APK包启动菜单到桌面或系统菜单。
https://storage.deepin.org/thread/202207271700065629_image.png

而这个程序核心的难点是关于 APK 信息的获取,本人也并没有找到一个比较适合的轮子
(如果不想从主文件 mainwindow.py 看代码,也提供了相应的 API:https://gitee.com/gfdgd-xi/uengine-runner/tree/main/api)
除了对 UEngine(deepin/UOS 出品的一款 Android 模拟器)的操作,其它大多数还是通用的
获取 APK 信息主要是调用了 aapt,可以通过,从以下源代码即可看出:

# 获取 aapt 的所有信息
def GetApkInformation(apkFilePath: "apk 所在路径")->"获取 aapt 的所有信息":
    return GetCommandReturn("aapt dump badging '{}'".format(apkFilePath))
是调用了 aapt dump badging apk文件 的命令,返回的为如下内容(接下来均以程序 QQ 为例)

package: name='com.tencent.mobileqq' versionCode='3118' versionName='8.9.3' platformBuildVersionName=''
install-location:'auto'
sdkVersion:'21'
targetSdkVersion:'26'
uses-permission: name='com.android.launcher.permission.INSTALL_SHORTCUT'
uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.VIBRATE'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.CHANGE_CONFIGURATION'
uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'
uses-permission: name='android.permission.WAKE_LOCK'
uses-permission: name='android.permission.SYSTEM_ALERT_WINDOW'
uses-permission: name='android.permission.RECORD_AUDIO'
uses-permission: name='com.tencent.msf.permission.account.sync'
uses-permission: name='android.permission.MODIFY_AUDIO_SETTINGS'
uses-permission: name='android.permission.CAMERA'
uses-permission: name='android.permission.CHANGE_WIFI_STATE'
uses-permission: name='android.permission.ACCESS_WIFI_STATE'
uses-permission: name='android.permission.KILL_BACKGROUND_PROCESSES'
uses-permission: name='com.android.launcher.permission.READ_SETTINGS'
uses-permission: name='com.android.launcher.permission.UNINSTALL_SHORTCUT'
uses-permission: name='android.permission.PERSISTENT_ACTIVITY'
uses-permission: name='android.permission.WRITE_SETTINGS'
uses-permission: name='android.permission.GET_TASKS'
uses-permission: name='com.tencent.permission.VIRUS_SCAN'
uses-permission: name='android.permission.READ_LOGS'
uses-permission: name='android.permission.READ_CONTACTS'
uses-permission: name='android.permission.FLASHLIGHT'
uses-permission: name='android.permission.BLUETOOTH'
uses-permission: name='android.permission.BLUETOOTH_ADMIN'
uses-permission: name='android.permission.BROADCAST_STICKY'
uses-permission: name='android.permission.WRITE_CONTACTS'
uses-permission: name='android.permission.WRITE_OWNER_DATA'
uses-permission: name='android.permission.SYSTEM_OVERLAY_WINDOW'
uses-permission: name='android.permission.USE_FINGERPRINT'
uses-permission: name='com.soter.permission.ACCESS_SOTER_KEYSTORE'
uses-permission: name='android.permission.USE_FACERECOGNITION'
uses-permission: name='android.permission.CHANGE_NETWORK_STATE'
uses-permission: name='android.permission.EXPAND_STATUS_BAR'
uses-permission: name='android.permission.INTERACT_ACROSS_USERS'
uses-permission: name='com.android.launcher.permission.WRITE_SETTINGS'
uses-permission: name='com.android.launcher2.permission.READ_SETTINGS'
uses-permission: name='com.android.launcher3.permission.READ_SETTINGS'
uses-permission: name='com.android.launcher3.permission.WRITE_SETTINGS'
uses-permission: name='com.google.android.launcher.permission.READ_SETTINGS'
uses-permission: name='com.bbk.launcher2.permission.READ_SETTINGS'
uses-permission: name='com.huaqin.launcherEx.permission.READ_SETTINGS'
uses-permission: name='com.htc.launcher.settings'
uses-permission: name='com.htc.launcher.permission.READ_SETTINGS'
uses-permission: name='com.htc.launcher.permission.WRITE_SETTINGS'
uses-permission: name='com.huawei.launcher2.permission.READ_SETTINGS'
uses-permission: name='com.huawei.launcher3.permission.READ_SETTINGS'
uses-permission: name='com.huawei.android.launcher.permission.READ_SETTINGS'
uses-permission: name='com.oppo.launcher.permission.READ_SETTINGS'
uses-permission: name='com.android.launcher2.permission.READ_SETTINGS'
uses-permission: name='com.meizu.android.launcher.permission.READ_SETTINGS'
uses-permission: name='com.meizu.launcher2.permission.READ_SETTINGS'
uses-permission: name='com.lenovo.launcher.permission.READ_SETTINGS'
uses-permission: name='com.ebproductions.android.launcher.permission.READ_SETTINGS'
uses-permission: name='com.sec.android.app.twlauncher.settings.READ_SETTINGS'
uses-permission: name='com.fede.launcher.permission.READ_SETTINGS'
uses-permission: name='net.qihoo.launcher.permission.READ_SETTINGS'
uses-permission: name='com.qihoo360.launcher.permission.READ_SETTINGS'
uses-permission: name='com.lge.launcher.permission.READ_SETTINGS'
uses-permission: name='org.adw.launcher.permission.READ_SETTINGS'
uses-permission: name='telecom.mdesk.permission.READ_SETTINGS'
uses-permission: name='com.tencent.mobileqq.permission.Pandora'
uses-permission: name='com.sonyericsson.home.permission.BROADCAST_BADGE'
uses-permission: name='com.sec.android.provider.badge.permission.READ'
uses-permission: name='com.sec.android.provider.badge.permission.WRITE'
uses-permission: name='com.lenovo.launcher.permission.BADGE_READ'
uses-permission: name='com.lenovo.launcher.permission.BADGE_WRITE'
uses-permission: name='com.huawei.android.launcher.permission.CHANGE_BADGE'
uses-permission: name='com.huawei.authentication.HW_ACCESS_AUTH_SERVICE'
uses-permission: name='android.permission.GET_ACCOUNTS'
uses-permission: name='android.permission.MANAGE_ACCOUNTS'
uses-permission: name='android.permission.AUTHENTICATE_ACCOUNTS'
uses-permission: name='android.permission.WRITE_CONTACTS'
uses-permission: name='android.permission.READ_SYNC_SETTINGS'
uses-permission: name='android.permission.WRITE_SYNC_SETTINGS'
uses-permission: name='android.permission.DISABLE_KEYGUARD'
uses-permission: name='android.permission.CHANGE_WIFI_MULTICAST_STATE'
uses-permission: name='com.tencent.mobileqq.permission.MM_MESSAGE'
uses-permission: name='android.permission.WRITE_CALENDAR'
uses-permission: name='android.permission.READ_CALENDAR'
uses-permission: name='android.permission.RESTART_PACKAGES'
uses-permission: name='android.permission.NFC'
uses-permission: name='android.permission.READ_APP_BADGE'
uses-permission: name='android.permission.READ_PACKAGE_BADGE'
uses-permission: name='miui.permission.READ_STEPS'
uses-permission: name='android.permission.REQUEST_INSTALL_PACKAGES'
uses-permission: name='com.google.android.gms.permission.ACTIVITY_RECOGNITION'
uses-permission: name='android.permission.FOREGROUND_SERVICE'
uses-permission: name='com.tencent.mobileqq.permission.TMF_SHARK'
uses-permission: name='com.tencent.photos.permission.DATA'
uses-permission: name='com.tencent.msf.permission.account.sync'
uses-permission: name='com.tencent.music.data.permission2'
uses-permission: name='android.permission.CHANGE_WIFI_STATE'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.ACCESS_WIFI_STATE'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.ACCESS_LOCATION'
uses-permission: name='android.permission.ACCESS_FINE_LOCATION'
uses-permission: name='android.permission.ACCESS_COARSE_LOCATION'
uses-permission: name='android.permission.CAMERA'
uses-permission: name='android.permission.WAKE_LOCK'
uses-permission: name='com.android.launcher.permission.INSTALL_SHORTCUT'
uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'
uses-permission: name='com.tencent.msg.permission.pushnotify'
uses-permission: name='com.tencent.msf.permission.account.sync'
uses-permission: name='com.tencent.wifisdk.permission.disconnect'
uses-permission: name='com.tencent.qqhead.permission.getheadresp'
uses-permission: name='com.tencent.qzone.permission.notify'
uses-permission: name='com.tencent.mobileqq.permission.MIPUSH_RECEIVE'
uses-permission: name='com.tencent.qav.permission.broadcast'
uses-permission: name='android.permission.ALARM_LOCK'
uses-permission: name='android.permission.REORDER_TASKS'
uses-permission: name='com.tencent.mobileqq.vfs.broadcast'
uses-permission: name='com.tencent.mobileqq.backtrace.warmed_up'
uses-permission: name='com.android.vending.CHECK_LICENSE'
uses-permission: name='com.tencent.mobileqq.permission.PROCESS_PUSH_MSG'
uses-permission: name='com.tencent.mobileqq.permission.PUSH_PROVIDER'
uses-permission: name='com.coloros.mcs.permission.RECIEVE_MCS_MESSAGE'
uses-permission: name='com.vivo.assistant.StepProvider'
uses-permission: name='com.vivo.assistant.permission.access.provider'
uses-permission: name='com.vivo.assistant.permission.sport.broadcast'
uses-permission: name='com.asus.msa.SupplementaryDID.ACCESS'
uses-permission: name='com.oplus.ocs.permission.third'
application-label:'QQ'
application-icon-120:'r/b/icon.png'
application-icon-160:'r/b/icon.png'
application-icon-213:'r/b/icon.png'
application-icon-240:'r/b/icon.png'
application-icon-320:'r/b/icon.png'
application-icon-480:'r/b/icon.png'
application-icon-640:'r/b/icon.png'
application-icon-65534:'r/b/icon.png'
application-icon-65535:'r/b/icon.png'
application: label='QQ' icon='r/b/icon.png'
uses-library-not-required:'com.sec.android.app.multiwindow'
uses-library-not-required:'scamera_sep'
launchable-activity: name='com.tencent.mobileqq.activity.SplashActivity'label='QQ' icon=''
uses-library-not-required:'com.google.android.media.effects'
uses-library-not-required:'com.motorola.hardware.frontcamera'
uses-library-not-required:'org.apache.http.legacy'
uses-library-not-required:'soterkeystore'
uses-permission: name='android.permission.READ_EXTERNAL_STORAGE'
uses-implied-permission: name='android.permission.READ_EXTERNAL_STORAGE' reason='requested WRITE_EXTERNAL_STORAGE'
feature-group: label=''
uses-gl-es: '0x20000'
uses-feature-not-required: name='android.hardware.bluetooth_le'
uses-feature: name='android.hardware.camera'
uses-feature-not-required: name='android.hardware.camera.autofocus'
uses-feature-not-required: name='android.hardware.location'
uses-feature-not-required: name='android.hardware.location.gps'
uses-feature-not-required: name='android.hardware.location.network'
uses-feature-not-required: name='android.hardware.telephony'
uses-feature: name='android.hardware.bluetooth'
uses-implied-feature: name='android.hardware.bluetooth' reason='requested android.permission.BLUETOOTH permission, requested android.permission.BLUETOOTH_ADMIN permission, and targetSdkVersion > 4'
uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
uses-feature: name='android.hardware.microphone'
uses-implied-feature: name='android.hardware.microphone' reason='requested android.permission.RECORD_AUDIO permission'
uses-feature: name='android.hardware.screen.landscape'
uses-implied-feature: name='android.hardware.screen.landscape' reason='one or more activities have specified a landscape orientation'
uses-feature: name='android.hardware.screen.portrait'
uses-implied-feature: name='android.hardware.screen.portrait' reason='one or more activities have specified a portrait orientation'
uses-feature: name='android.hardware.wifi'
uses-implied-feature: name='android.hardware.wifi' reason='requested android.permission.ACCESS_WIFI_STATE permission, requested android.permission.CHANGE_WIFI_MULTICAST_STATE permission, and requested android.permission.CHANGE_WIFI_STATE permission'
provides-component:'wallpaper'
provides-component:'payment'
main
other-activities
other-receivers
other-services
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--'
densities: '120' '160' '213' '240' '320' '480' '640' '65534' '65535'
native-code: 'arm64-v8a'

从以上内容可以看到程序申请的权限、图标在包里的路径、程序名、版本、包名、activity等等,可以获取大多数的信息,因此,就可以基于 aapt 获取 APK 信息
首先是包名和版本号,从获取信息的第一行就可以看到了
package: name='com.tencent.mobileqq' versionCode='3118' versionName='8.9.3' platformBuildVersionName=''
因此采用遍历读取每一行开头是否含有package:,定位到所在行,包名排除掉 versionCode 后面和 name 及其前面即可获取包名
版本号排除掉 versionName 及其前面和 platformBuildVersionName 即可

写出的代码如下:

# 获取 apk 包名
def GetApkPackageName(apkFilePath: "apk 所在路径")->"获取 apk 包名":
    info = GetApkInformation(apkFilePath)
    for line in info.split('\n'):
      if "package:" in line:
            line = line
            line = line.replace("package:", "")
            line = line.replace("name=", "")
            line = line.replace("'", "")
            line = line.replace(" ", "")
            return line


# 获取 APK 版本号
def GetApkVersion(apkFilePath):
    info = GetApkInformation(apkFilePath)
    for line in info.split('\n'):
    if "package:" in line:
            if "compileSdkVersion='" in line:
                line = line.replace(line, "")
                  if "platform" in line:
                        line = line.replace(line, "")
                        line = line.replace(line, "")
                        line = line.replace("versionName='", "")
                        line = line.replace("'", "")
                        line = line.replace(" ", "")
                        return line

而打开 Android 应用不是只有包名就可以打开的,还需要程序的activity,同样还是使用 aapt 返回的信息,在 143 行
launchable-activity: name='com.tencent.mobileqq.activity.SplashActivity'label='QQ' icon=''
截取中间的 com.tencent.mobileqq.activity.SplashActivity 即可,实现的代码如下:
# 获取 apk Activity
def GetApkActivityName(apkFilePath: "apk 所在路径")->"获取 apk Activity":
    info = GetApkInformation(apkFilePath)
    for line in info.split('\n'):
      if "launchable-activity" in line:
            line = line
            line = line.replace("launchable-activity: ", "")
            line = line.replace("'", "")
            line = line.replace(" ", "")
            line = line.replace("name=", "")
            line = line.replace("label=", "")
            line = line.replace("icon=", "")
            return line

而获取程序名称也和上面一样了,信息位于第 140 行,截取label=''中间的内容即可
application: label='QQ' icon='r/b/icon.png'
# 获取软件的中文名称
def GetApkChineseLabel(apkFilePath)->"获取软件的中文名称":
    info = GetApkInformation(apkFilePath)
    for line in info.split('\n'):
      if "application-label:" in line:
            line = line.replace("application-label:", "")
            line = line.replace("'", "")
            return line


而获取图标是全部信息获取难度最高的,首先从上面的示例文件中可以看到第 140 行就指定了图标在 APK 内的路径,而 APK 本质也是一个压缩包,因此我们拿 zipfile 这个库进行解压
application: label='QQ' icon='r/b/icon.png'
https://storage.deepin.org/thread/202208062040267917_image.png
下面是 UEngine 运行器 1.2.3 (commit:fde7a0d,文件名:main.py)实现读取图标的代码(1.3.0 就更新了获取方式了)

# 获取图标在包内的路径
def GetApkIconInApk(apkFilePath)->"获取图标在包内的路径":
    info = GetApkInformation(apkFilePath)
    for line in info.split('\n'):
      if "application:" in line:
            line = line
            line = line.replace("icon='", "")
            if "'" in line:
                line = line
            return line

# 获取 apk 文件的图标(部分程序不支持)
def SaveApkIcon(apkFilePath, iconSavePath)->"获取 apk 文件的图标(部分程序不支持)":
    zip = zipfile.ZipFile(apkFilePath)
    iconData = zip.read(GetApkIconInApk(apkFilePath))
    with open(iconSavePath, 'w+b') as saveIconFile:
      saveIconFile.write(iconData)

但是如果搞像 B 站这个应用,会发现提取不了图标,你会巧妙的发现,指向的不是 png 而是 xml
application: label='哔哩哔哩' icon='res/uFR.xml'
这是目前 1.8.0 的获取代码(因为这一块并不是我贡献的,所以不详细介绍)
(核心)getxmlimg.py:

import PIL.Image as Image
import PIL.ImageDraw as ImageDraw
import zipfile
import subprocess
import re

class getsavexml():

    def savexml(self,apkFilePath,xmlpath,iconSavePath):
      cmddumpid = "aapt dump xmltree "+ apkFilePath + " " + xmlpath
      print(cmddumpid)
      xmltree =subprocess.getoutput(cmddumpid)
      xmls = xmltree.splitlines()
      # find strs ,print next line
      def FindStrs(lines,strs):
            i=0
            while i < len(lines):
                if re.search(strs,lines):
                  tmpstr = lines
                  i += 1
                  Resultstr = tmpstr.split(":")[-1].split("=")[-1].split("0x")[-1]
                  return Resultstr
                else:
                  i += 1
      #从apk的信息中获取前后景图片的ID号
      backimgid =FindStrs(xmls,"background")
      foreimgid =FindStrs(xmls,"foreground")
      print(backimgid)
      print(foreimgid)

      # 直接从apk resource文件获取前后两层图片路径及ID字符串
      resource =subprocess.getoutput("aapt dump --values resources " +apkFilePath + "| grep -iE -A1 " +"\"" + backimgid + "|" + foreimgid + "\"")
      resourcelines = resource.splitlines()
      print(resourcelines)

      # 从过滤出的字符串中获取所有相同ID的图片路径
      def Findpicpath(lines,imgid):
            i=0
            Resultstr = []
            while i < len(lines):
                if re.search(imgid,lines) and re.search("string8",lines) :
                  print(lines)
                  tmpstr = lines.replace("\"","")
                  i += 1
                  Resultstr.append(tmpstr.split()[-1])
                else:
                  i += 1
            return Resultstr

      #获取所有带前后图片ID的图片路径(相同背景或者前景的图片ID但分辨率不一样)
      backimgs =Findpicpath(resourcelines,backimgid)
      foreimgs =Findpicpath(resourcelines,foreimgid)
      print(backimgs)
      print(foreimgs)
      #获取分辨率最高的图片路径
      def getmaxsize(imgs):
            j = 0
            size=(0,0)
            zipapk = zipfile.ZipFile(apkFilePath)
            imgpath = ""
            while j < len(imgs):
                print(imgs)
                img = Image.open(zipapk.open(imgs))
                print(imgs)
                print(img.size)
                if size < img.size:
                  size = img.size
                  imgpath = imgs
                j += 1
            return imgpath

      # 获取到文件列表后,进行比较分辨率,选取分辨率最高的张图片
      iconbackpath = getmaxsize(backimgs)
      iconforepath = getmaxsize(foreimgs)
      print(iconbackpath + " " + iconforepath)

      #从APK文件获取最终图片
      zipapk = zipfile.ZipFile(apkFilePath)
      iconback = zipapk.open(iconbackpath)
      iconfore = zipapk.open(iconforepath)


      # 叠加图片,mask 设置前景为蒙版
      iconbackimg =Image.open(iconback).convert("RGBA")
      iconforeimg =Image.open(iconfore).convert("RGBA")
      iconbackimg.paste(iconforeimg,mask=iconforeimg)


      # 圆角图片函数,网上拷贝的
      def circle_corner(img, radii):#把原图片变成圆角,这个函数是从网上找的,原址 https://www.pyget.cn/p/185266
            """
            圆角处理
            :param img: 源图象。
            :param radii: 半径,如:30。
            :return: 返回一个圆角处理后的图象。
            """
            # 画圆(用于分离4个角)
            circle = Image.new('L', (radii * 2, radii * 2), 0)# 创建一个黑色背景的画布
            draw = ImageDraw.Draw(circle)
            draw.ellipse((0, 0, radii * 2, radii * 2), fill=255)# 画白色圆形
            # 原图
            img = img.convert("RGBA")
            w, h = img.size
            # 画4个角(将整圆分离为4个部分)
            alpha = Image.new('L', img.size, 255)
            alpha.paste(circle.crop((0, 0, radii, radii)), (0, 0))# 左上角
            alpha.paste(circle.crop((radii, 0, radii * 2, radii)), (w - radii, 0))# 右上角
            alpha.paste(circle.crop((radii, radii, radii * 2, radii * 2)), (w - radii, h - radii))# 右下角
            alpha.paste(circle.crop((0, radii, radii, radii * 2)), (0, h - radii))# 左下角
            # alpha.show()
            img.putalpha(alpha)# 白色区域透明可见,黑色区域不可见
            return img

      #圆角半径1/8边长,保存icon图片
      w,h = iconbackimg.size
      iconimg = circle_corner(iconbackimg,int(w/8))
      iconimg.save(iconSavePath)



mainwindow.py:

# 保存apk图标
def SaveApkIcon(apkFilePath, iconSavePath)->"保存 apk 文件的图标":
    try:
      if os.path.exists(iconSavePath):
            os.remove(iconSavePath)
      info = GetApkInformation(apkFilePath)
      for line in info.split('\n'):
            if "application:" in line:
                xmlpath = line.split(":")[-1].split()[-1].split("=")[-1].replace("'","")
                if xmlpath.endswith('.xml'):
                        xmlsave = getsavexml()
                        print(xmlpath)
                        xmlsave.savexml(apkFilePath,xmlpath,iconSavePath)
                        return
                else:
                  zip = zipfile.ZipFile(apkFilePath)
                  iconData = zip.read(xmlpath)
                  with open(iconSavePath, 'w+b') as saveIconFile:
                        saveIconFile.write(iconData)
                        return
      print("None Icon! Show defult icon")
      shutil.copy(programPath + "/defult.png", iconSavePath)
    except:
      traceback.print_exc()
      print("Error, show defult icon")
      shutil.copy(programPath + "/defult.png", iconSavePath)

其它的例如 root 镜像、键盘映射、添加 adb 运行列表就不详细介绍了


核心的功能就完成了
(到时候审核过/没过后再传代码仓库和写 README)

Hmily 发表于 2022-8-8 11:15

I D:gfdgd_xi
邮箱:3025613752@qq.com

申请通过,欢迎光临吾爱破解论坛,期待吾爱破解有你更加精彩,ID和密码自己通过邮件密码找回功能修改,请即时登陆并修改密码!
登陆后请在一周内在此帖报道,否则将删除ID信息。

ps:你应该先测试好用户名再进行申请,空格是不行的,给你重新追加了符号。

gfdgd_xi 发表于 2022-8-8 13:46

Hmily 发表于 2022-8-8 11:15
I D:gfdgd_xi
邮箱:



感谢感谢,我发完才发现我ID不对
现过来报告
页: [1]
查看完整版本: 申请会员ID:gfdgd xi【申请通过】