# 安卓游戏打包脚本
# 1.用apktool解包
# 2.复制res资源、assets资源、jniLib资源
# 3.修改包名、app名、渠道文件
# 4.添加app icon、合并AndroidManifest文件
# 5.添加app启动图,修改AndroidManifest文件
# 6.生成新的R文件
# 7.将新的R文件编译,生成dex,再用baksmali生成smali,替换旧的R文件
# 8.将jar资源打包成dex,再用baksmali生成smali,拷贝到smali目录下
# 9.统计方法量,并分割dex(将smali文件拷贝到目录smali_classes2)
# 10.用apktool回包
# 11.重新签名
import file_utils
import xml_utils
import smali_utils
import config_utils
import game_utils
import os
import os.path
import json
import sys
import importlib
import uuid
import zipfile
import time
import datetime


ignoreLauncher = ['jm_ysdk', 'jm_yijie', 'jm_quick', 'jm_beiyu', 'jm_xq_jrtt','jm_zy_ysdk']
adaptApp = ['yjzx']
startTime = ''

def pack(game, sdk, config):
    config['cache'] = uuid.uuid1()
    subChannel = config['subChannel']
    print('game = %s, sdk = %s, subChannel = %s, ...' % (game,sdk,subChannel))

    # 解包
    ret = decomplie(game, sdk, subChannel, config)
    if ret:
        return ret

    # 更改包名
    if 'packageName' in config and config['packageName'] != '':
        ret = changePackageName(game, sdk, subChannel, config)
        if ret:
            return ret
    # 更改app名
    if 'name' in config and config['name'] != '':
        ret = changeAppName(game, sdk, subChannel, config)
        if ret:
            return ret
    # 更改app icon
    if config['changeIcon']:
        ret = changeAppIcon(game, sdk, subChannel, config)
        if ret:
            return ret
    # 添加启动图操作
    if config['addLauncher']:
        ret = addLauncher(game, sdk, subChannel, config)
        if ret:
            return ret

    ret = copyIcon(game, sdk, subChannel, config)

    # 增加配置文件
    ret = createJmhyProperties(game, sdk, subChannel, config)
    if ret:
        return ret

    # 更改版本号
    ret = changeVersion(game, sdk, subChannel, config)
    if ret:
        return ret
    # 回编译
    ret = recomplie(game, sdk, subChannel, config)
    if ret:
        return ret
    # 对齐apk
    ret = alignApk(game, sdk, subChannel, config)
    if ret:
        return ret
    # 签名
    ret = apksignerApk(game, sdk, subChannel, config)
    if ret:
        return ret
    # 清理产生的中间文件
    if config['clearCache']:
        clearTemp(game, sdk, subChannel, config)

    endTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    d1 = datetime.datetime.strptime(endTime, '%Y-%m-%d %H:%M:%S')
    d2 = datetime.datetime.strptime(startTime,  '%Y-%m-%d %H:%M:%S')
    d = d1 - d2
    print ('开始时间:' + startTime)
    print ('结束时间:' + endTime)
    print ('用时:{}'.format(config_utils.getTime(d.seconds)))
    return 0

def decomplie(game, sdk, subChannel, config):
    '''
    解包
    '''
    apktoolPath = file_utils.getApkToolPath()
    gamePath = file_utils.getFullGameApk(game)
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])

    if os.path.exists(decompliePath):
        print('delete decomplie folder...')
        file_utils.deleteFolder(decompliePath)

    print('decomplie apk...')

    return file_utils.execJarCmd(apktoolPath, 'd -f "%s" -o "%s"' % (gamePath, decompliePath))

def removeOldCode(game, sdk, subChannel, config):
    '''
    删除旧代码
    '''
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    codePath = os.path.join(decompliePath, 'smali', 'com', 'jmhy', 'sdk')
    #file_utils.deleteFolder(codePath)
    allFiles = []
    allFiles = file_utils.list_files(codePath, allFiles)
    for f in allFiles:
        fpath, fname = os.path.split(f)  #分离文件名和路径
        if fname == 'R.smali' or fname.startswith('R$'):
            continue
        os.remove(f)
        #print('remove %s' % f)
    return 0

def copyRes(game, sdk, subChannel, config):
    '''
    复制res资源
    '''
    # 拷贝sdk资源
    print('copy res...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    sdkPath = file_utils.getFullSDKPath(sdk)
    resPath = os.path.join(sdkPath, 'res')
    decomplieResPath = file_utils.getFullPath(decompliePath, 'res')

    for d in os.listdir(resPath):
        copyResWithType(resPath, decomplieResPath, d)

    # 拷贝assets
    print('copy assets...')
    assetsPath = file_utils.getFullPath(sdkPath, 'assets')
    decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
    if os.path.exists(assetsPath):
        ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
        if ret:
            return ret

    # 拷贝jniLib
    print('copy jniLibs...')
    jniPath = file_utils.getFullPath(sdkPath, 'jniLibs')
    decomplieJniPath = file_utils.getFullPath(decompliePath, 'lib')
    abiFilters = []
    if os.path.exists(decomplieJniPath):
        for abi in os.listdir(decomplieJniPath):
            if abi == 'armeabi-v7a' or abi == 'armeabi':
                abiFilters.append(abi)
    else:
        abiFilters = ['armeabi-v7a']

    if os.path.exists(jniPath):
        ret = file_utils.copyFileAllDir(jniPath, decomplieJniPath, False, abiFilters)
        if ret:
            return ret
    return 0

def mergeDrawableRes(game, sdk, subChannel, config):
    '''
    合并Drawable-v4目录
    '''
    print('merge drawable path...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    resPath = os.path.join(decompliePath, 'res')

    for path in os.listdir(resPath):
        print('res path %s' % path)
        if (path.startswith('drawable') or path.startswith('mipmap')) and path.endswith('-v4'):
            print('merge path %s' % path)
            v4DrawablePath = os.path.join(resPath, path)
            drawablePath = os.path.join(resPath, path[:-3])

            if os.path.exists(drawablePath):
                ret = file_utils.copyFileAllDir(v4DrawablePath, drawablePath, True)
                if ret:
                    return ret
            else:
                os.rename(v4DrawablePath, drawablePath)
    return 0

def removeSameRes(game, sdk, subChannel, config):
    '''
    移除相同的资源
    '''
    # 读取sdk的资源
    print('remove same res...')
    sdkPath = file_utils.getFullSDKPath(sdk)
    sdkResPath = os.path.join(sdkPath, 'res')
    resList = []
    for path in os.listdir(sdkResPath):
        if not path.startswith('values'):
            continue
        absPath = os.path.join(sdkResPath, path)
        for resFile in os.listdir(absPath):
            '''if not resFile.startswith('jm_'):
                continue'''
            if resFile.endswith('.DS_Store'):
                continue
            #print('readAllRes -- > ' + os.path.join(absPath, resFile))

            resList = xml_utils.readAllRes(os.path.join(absPath, resFile), resList)

    if len(resList) == 0:
        print('no same res found')
        return 0

    # 移除相同的资源
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    resPath = os.path.join(decompliePath, 'res')
    for path in os.listdir(resPath):
        if not path.startswith('values'):
            continue

        absPath = os.path.join(resPath, path)
        for resFile in os.listdir(absPath):
            xml_utils.removeSameRes(os.path.join(absPath, resFile), resList)

    return 0

def mergeManifestRes(game, sdk, subChannel, config):
    '''
    合并主文件
    '''
    print('merge AndroidManifest...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    sdkPath = file_utils.getFullSDKPath(sdk)
    libManifest = file_utils.getFullPath(sdkPath, 'manifest.xml')
    return xml_utils.mergeManifestRes(manifest, libManifest)

def copyAppRes(game, sdk, subChannel, config):
    '''
    拷贝app的资源,比如app icon、启动图等
    '''
    channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])

    # assets
    print('copy assets...')
    #适配一剑斩仙
    if game in adaptApp:
        print('适配一剑斩仙...')
        decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
        skinZipPath = os.path.join(decomplieAssetsPath, 'skin.zip')
        skinPath = os.path.join(decomplieAssetsPath, 'skin')
        if os.path.exists(skinZipPath):
            with zipfile.ZipFile(skinZipPath) as zf:
                zf.extractall(decomplieAssetsPath)
                print('create unzip skin' )
            assetsPath = file_utils.getFullPath(channelPath, 'assets')
            decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
            if os.path.exists(assetsPath):
                ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
            with zipfile.ZipFile(skinZipPath, 'w') as z:
                for root, dirs, files in os.walk(skinPath):
                    for single_file in files:
                        filepath = os.path.join(root, single_file)
                        print ('create zip ---> ' + filepath)
                        temPath = 'skin/' + single_file
                        z.write(filepath, temPath)
            z.close()
        else:
            print('normal copy assets...')
            assetsPath = file_utils.getFullPath(channelPath, 'assets')
            decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
            if os.path.exists(assetsPath):
                ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
                if ret:
                    return ret
    else:
        print('normal copy assets...')
        assetsPath = file_utils.getFullPath(channelPath, 'assets')
        decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
        if os.path.exists(assetsPath):
            ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
            if ret:
                return ret

    # icon
    print('copy icon...')
    ret = copyAppResWithType(decompliePath, channelPath, 'icon')
    if ret:
        return ret
    # 启动图
    print('copy splash...')
    ret = copyAppResWithType(decompliePath, channelPath, 'splash')
    if ret:
        return ret
    # 其他图片
    print('copy image...')
    ret = copyAppResWithType(decompliePath, channelPath, 'image')
    if ret:
        return ret
    return 0
    
def copyAppResWithType(decompliePath, channelPath, typeName):
    decomplieResPath = os.path.join(decompliePath, 'res')
    iconPath = os.path.join(channelPath, typeName)
    if not os.path.exists(iconPath):
        print('dir "%s" not exists' % iconPath)
        return 0
    for d in os.listdir(iconPath):
        ret = copyResWithType(iconPath, decomplieResPath, d)
        if ret:
            return ret

    return 0

def copyResWithType(resPath, decomplieResPath, typeName):
    # appt的打包目录会带-v4后缀
    resDir = os.path.join(resPath, typeName)
    target = os.path.join(decomplieResPath, typeName)
    targetV4 = os.path.join(decomplieResPath, typeName + '-v4')
    if not os.path.exists(target) and not os.path.exists(targetV4):
        os.makedirs(target)
        return file_utils.copyFileAllDir(resDir, target, False)
    elif not os.path.exists(target):
        return file_utils.copyFileAllDir(resDir, targetV4, False)
    else:
        return file_utils.copyFileAllDir(resDir, target, False)

def removeNoSupportAttr(game, sdk, subChannel, config):
    '''
    删除一些不支持的属性
    '''
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    
    xml_utils.removeRootAttr(manifest, 'compileSdkVersion')
    xml_utils.removeRootAttr(manifest, 'compileSdkVersionCodename')
    
    return 0

def fixUnSupportConfig(game, sdk, subChannel, config):
    '''
    删除一些不支持的配置
    '''
    # 检查minSdkVersion
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    yml = os.path.join(decompliePath, 'apktool.yml')

    minSdkVersion = 15
    file_utils.changeMinSdkVersion(yml, minSdkVersion)

    resPath = os.path.join(decompliePath, 'res')

    tag = '-v'
    for res in os.listdir(resPath):
        #print('res = ' + res)
        if res.startswith('values') and tag in res:
            start = res.index(tag)
            version = res[start+len(tag):]
            if not version.isdigit():
                continue
            
            version = int(version)
            print('version = %d' % version)
            if version < minSdkVersion:
                unSopportPath = os.path.join(resPath, res)
                print('unSopportPath = ' + unSopportPath)
                file_utils.deleteFolder(unSopportPath)
                print('deleteFolder = ' + unSopportPath)
    
    return 0

def changePackageName(game, sdk, subChannel, config):
    '''
    更改包名
    '''
    # 全局替换AndroidManifest里面的包名
    newPackageName = config['packageName']

    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    packageName = xml_utils.getPackageName(manifest)
    
    xml_utils.changePackageName(manifest, newPackageName)
    print('change package name %s --> %s' % (packageName, newPackageName))
    return 0

def changeAppName(game, sdk, subChannel, config):
    '''
    更改app名
    '''
    # 生成string.xml文件
    name = config['name']

    resName = 'common_sdk_name'
    if 'outName' in config:
        resName = resName + '_' + config['outName']
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    stringFile = os.path.join(decompliePath, 'res', 'values', 'sdk_strings_temp.xml')
    content = '<?xml version="1.0" encoding="utf-8"?><resources><string name="%s">%s</string></resources>' % (resName, name)
    file_utils.createFile(stringFile, content)

    # 修改主文件的app名的值
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    xml_utils.changeAppName(manifest, '@string/%s' % resName)
    print('change app name %s' % name)
    return 0

def changeAppIcon(game, sdk, subChannel, config):
    '''
    更改app icon
    '''
    print('change app icon...')
    resName = 'common_sdk_icon'

    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    xml_utils.changeAppIcon(manifest, '@mipmap/%s' % resName)
    return 0

def addMetaData(game, sdk, subChannel, config):
    '''
    添加meta-data
    '''
    if 'metaData' not in config:
        return 0

    print('add meta-data...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    xml_utils.addMetaData(manifest, config['metaData'])
    return 0

def addConfig(game, sdk, subChannel, config):
    '''
    添加config.json
    '''
    if 'configData' not in config:
        return 0

    print('add jmad.properties...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    configJson = os.path.join(decompliePath, 'assets', 'jmad.properties')
    jsonText = json.dumps(config['configData'], ensure_ascii=False)
    file_utils.createFile(configJson, jsonText)
    return 0

def createJmhyProperties(game, sdk, subChannel, config):
    '''
    创建jmhy.properties
    '''
    print('create jmad.properties...')
    propValue = config['properties']
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    properties = os.path.join(decompliePath, 'assets', 'jmad.properties')
    content = ''
    for key in propValue:
        content = '%s%s=%s\n' % (content, key, propValue[key])
    file_utils.createFile(properties, content)
    return 0

def changePlaceholders(game, sdk, subChannel, config):
    '''
    处理掉占位符
    '''
    if 'placeholders' not in config:
        return 0

    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')

    placeholders = config['placeholders']
    for placeholder in placeholders:
        oldText = '${%s}' % placeholder
        newText = placeholders[placeholder]
        print('change placeholder %s -> %s' % (oldText, newText))
        file_utils.replaceContent(manifest, oldText, newText)

    return 0

def addLauncher(game, sdk, subChannel, config):
    '''
    添加启动图
    '''
    # ysdk的特殊处理
    if sdk in ignoreLauncher:
        return 0

    channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
    splashPath = os.path.join(channelPath, 'splash')
    if len(os.listdir(splashPath)) == 0:
        print('dir splash is empty')
        return 0

    print('add launcher...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    activity = xml_utils.getLauncherActivityName(manifest)
    if activity == 'com.jmhy.sdk.template.LauncherActivity':
        print('add launcher already exist...')
        return 1
    # 添加关联资源
    internalPath = file_utils.getFullInternalPath()
    ret = copyAppResWithType(decompliePath, internalPath, 'launcher_res')
    if ret:
        return ret

    # 拷贝代码
    print('copy launcher code...')
    codePath = os.path.join(internalPath, 'launcher_code', 'smali')
    smaliPath = file_utils.getFullPath(decompliePath, 'smali')
    ret = file_utils.copyFileAllDir(codePath, smaliPath)
    if ret:
        return ret

    # 修改主文件信息
    print('change launcher config...')
    
    orientation = xml_utils.getScreenOrientation(manifest)
    activity = xml_utils.removeLauncherActivity(manifest)
    xml_utils.addLauncherActivity(manifest, orientation, 'com.jmhy.sdk.template.LauncherActivity')

    # 修改跳转的
    launcherActivity = os.path.join(decompliePath, 'smali', 'com', 'jmhy', 'sdk', 'template', 'LauncherActivity.smali')
    file_utils.replaceContent(launcherActivity, '{class}', activity)

    print('change launcher %s to %s' % (activity, 'com.jmhy.sdk.template.LauncherActivity'))

    # config['oldLauncher'] = activity

    if 'launcherTime' in config:
        timeHex = formatHex(config['launcherTime'])
        file_utils.replaceContent(launcherActivity, '0x0BB8', timeHex)

    return 0

def addMoreIcon(game, sdk, subChannel, config):
    '''
    添加多个图标
    '''
    print('add more icon support...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    icon = '@mipmap/common_sdk_icon'
    if not config['changeIcon']:
        manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
        icon = xml_utils.getApplicationAttr(manifest, 'icon')

    switchIcon = icon
    if config['switchIcon']:
        switchIcon = '@mipmap/common_sdk_icon2'

    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    return xml_utils.addMoreIcon(manifest, icon, switchIcon)

def copyIcon(game, sdk, subChannel, config):
    '''
    复制icon到res ,一键登录使用
    '''
    if config['changeIcon']:
        decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
        decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
        iconPath = os.path.join(decomplieResPath, 'mipmap-xhdpi', 'common_sdk_icon.png')
        desPath = os.path.join(decomplieResPath, 'drawable-hdpi', 'jm_cmcc_icon.png')
        file_utils.copyFile(iconPath, desPath)
    else:
        decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
        decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
        desPath = os.path.join(decomplieResPath, 'drawable-hdpi', 'jm_cmcc_icon.png')

        manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
        icon = xml_utils.getApplicationAttr(manifest, 'icon')
        tag = icon[1:].split("/")
        if len(tag) < 1:
            return
        hdpi = ['-xhdpi', '-xxhdpi', '-xxxhdpi']
        for h in hdpi:
            p1 = '%s%s' % (tag[0],h)
            png = tag[1] + ".png"
            iconPath = os.path.join(decomplieResPath, p1, png)
            if os.path.exists(iconPath):
                print ('iconPath = ' + iconPath)
                file_utils.copyFile(iconPath, desPath)
                break


def formatHex(millisecond):
    '''
    将毫秒转为16进制,4位格式
    '''
    timeHex = str(hex(millisecond)).upper()
    timeHex = timeHex[2:]
    formatHex = ''
    if len(timeHex) == 3:
        formatHex = '0x0' + timeHexaddLauncher
    elif len(timeHex) == 4:
        formatHex = '0x' + timeHex
    else:
        formatHex = '0x0BB8'
    return formatHex

def doSDKPostScript(game, sdk, config):
    '''
    执行sdk相关特殊处理脚本
    '''
    sdkPath = file_utils.getFullSDKPath(sdk)
    scriptPath = os.path.join(sdkPath, 'script')
    targetScript = os.path.join(scriptPath, 'sdk_script.py')
    if not os.path.exists(targetScript):
        print('sdk_script no exists')
        return 0

    print('doSDKPostScript...')
    sys.path.append(scriptPath)

    module = importlib.import_module('sdk_script')
    ret = module.execute(game, sdk, config)

    sys.path.remove(scriptPath)

    return ret

def doGamePostScript(game, sdk, config):
    '''
    执行游戏相关特殊处理脚本
    '''
    channelPath = file_utils.getFullGamePath(game)
    scriptPath = os.path.join(channelPath, 'script')
    targetScript = os.path.join(scriptPath, 'game_script.py')
    if not os.path.exists(targetScript):
        print('game_script no exists')
        return 0

    print('doGamePostScript...')
    sys.path.append(scriptPath)

    module = importlib.import_module('game_script')
    ret = module.execute(game, sdk, config)

    sys.path.remove(scriptPath)

    return ret

def addLogSdk(game, sdk, subChannel, config, logSdk):
    # 拷贝jniLibs
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    sdkPath = file_utils.getFullLogSDKPath(logSdk)

    print('copy log jniLibs...')
    jniPath = file_utils.getFullPath(sdkPath, 'jniLibs')
    decomplieJniPath = file_utils.getFullPath(decompliePath, 'lib')
    abiFilters = []
    if os.path.exists(decomplieJniPath):
        for abi in os.listdir(decomplieJniPath):
            if abi == 'armeabi-v7a' or abi == 'armeabi':
                abiFilters.append(abi)
    else:
        abiFilters = ['armeabi-v7a']

    if os.path.exists(jniPath):
        ret = file_utils.copyFileAllDir(jniPath, decomplieJniPath, False, abiFilters)
        if ret:
            return ret
    
    print('merge log AndroidManifest...')
    libManifest = file_utils.getFullPath(sdkPath, 'manifest.xml')
    if os.path.exists(libManifest):
        manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
        ret = xml_utils.mergeManifestRes(manifest, libManifest)
        if ret:
            return ret

    return packLogJar(game, sdk, subChannel, config, logSdk)

def generateNewRFile(game, sdk, subChannel, config):
    '''
    生成新的R文件
    '''
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    androidPlatforms = file_utils.getAndroidCompileToolPath()
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
    compliePath = file_utils.getFullPath(decompliePath, 'gen')

    if not os.path.exists(compliePath):
        os.makedirs(compliePath)

    # 生成R文件
    print('生成R文件 ...')
    if config['aapt2disable']:
        aapt = file_utils.getAAPTPath()
        ret = file_utils.getExecPermission(aapt)
        if ret:
            return ret
        createRCmd = '"%s" p -f -m -J "%s" -S "%s" -I "%s" -M "%s"' % (aapt, compliePath, decomplieResPath, androidPlatforms, manifest)
        ret = file_utils.execFormatCmd(createRCmd)
        if ret:
            return ret
    else:
        # compile
        aapt = file_utils.getAAPT2Path()
        ret = file_utils.getExecPermission(aapt)
        if ret:
            return ret

        print('compiled res ...')
        complieResPath = os.path.join(compliePath, 'compiled')
        complieResCmd = '"%s" compile --dir "%s" -o "%s"' % (aapt, decomplieResPath, complieResPath)
        file_utils.execFormatCmd(complieResCmd)

        # unzip
        print('unzip compiled res ...')
        unzipResPath = os.path.join(compliePath, 'aapt2_res')
        with zipfile.ZipFile(complieResPath) as zf:
            zf.extractall(unzipResPath)
            print('create unzip %s' % unzipResPath)

        # link
        print('link res ...')
        outApk = os.path.join(compliePath, 'res.apk')
        linkResCmd = '"%s" link -o "%s" -I "%s" --manifest "%s" --java "%s" --auto-add-overlay' % (aapt, outApk, androidPlatforms, manifest, compliePath)
        for filename in os.listdir(unzipResPath):
            linkResCmd += ' %s' % filename
        print('link cmd len is %s' % len(linkResCmd))
        ret = file_utils.execFormatCmd(linkResCmd, unzipResPath)
        if ret:
            return ret

    # 编译R文件
    print('complie R.java ...')
    packageName = xml_utils.getPackageName(manifest)
    packagePath = file_utils.getPackagePath(compliePath, packageName)
    RSourceFile = os.path.join(packagePath, 'R.java')
    complieRCmd = 'javac -source 1.8 -target 1.8 -encoding UTF-8 "%s"' % RSourceFile
    ret = file_utils.execFormatCmd(complieRCmd)
    if ret:
        return ret

    # 生成dex
    print('dex R.class ...')
    outDex = os.path.join(compliePath, 'classes.dex')
    if config['aapt2disable']:
        dx = file_utils.getDxPath()
        dexCmd = '--dex --no-warning --output="%s" "%s"' % (outDex, compliePath)
    else:
        dx = file_utils.getD8Path()
        clazz = os.path.join(packagePath, '*.class')
        dexCmd = '--lib "%s" --output "%s" %s' % (androidPlatforms, compliePath, clazz)
    ret = file_utils.execJarCmd(dx, dexCmd)
    if ret:
        return ret

    # 反向dex生成smali
    # 存放在out目录
    print('baksmali classes.dex ...')
    baksmaliPath = file_utils.getBaksmaliPath()
    outPath = file_utils.getFullPath(decompliePath, 'out')

    ret = file_utils.execJarCmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath))
    if ret:
        return ret

    # 将生成的文件拷贝到目标目录
    print('copy R.smali ...')
    smaliPath = file_utils.getFullPath(decompliePath, 'smali')
    file_utils.copyFileAllDir(outPath, smaliPath)
    return 0

def packJar(game, sdk, subChannel, config):
    '''
    打包所有的jar
    '''
    splitDex = config['splitDex']
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    outPath = file_utils.getFullPath(decompliePath, 'gen')

    if not os.path.exists(outPath):
        os.makedirs(outPath)
    if config['aapt2disable']:
        dx = file_utils.getDxPath()
        dexCmd = '--dex --multi-dex --no-warning --output="%s"' % outPath
    else:
        dx = file_utils.getD8Path()
        androidPlatforms = file_utils.getAndroidCompileToolPath()
        dexCmd = '--lib "%s" --output "%s"' % (androidPlatforms, outPath)

    # 找到所有lib依赖
    sdkPath = file_utils.getFullSDKPath(sdk)
    libs = os.path.join(sdkPath, 'libs')
    libConfig = os.path.join(libs, 'config.json')

    # 存在配置文件
    if os.path.exists(libConfig):
        jsonText = file_utils.readFile(libConfig)
        libConf = json.loads(jsonText)
        if 'libConfig' in config and config['libConfig'] in libConf:
            conf = config['libConfig']
            libList = libConf[conf]
            for jar in libList:
                dexCmd += ' ' + os.path.join(libs, jar)
        elif 'default' in libConf:
            libList = libConf['default']
            for jar in libList:
                dexCmd += ' ' + os.path.join(libs, jar)
        else:
            for jar in os.listdir(libs):
                if not jar.endswith('.jar'):
                    continue
                dexCmd += ' ' + os.path.join(libs, jar)
    else:
        for jar in os.listdir(libs):
            if not jar.endswith('.jar'):
                continue
            dexCmd += ' ' + os.path.join(libs, jar)

    # multidex.jar
    if splitDex:
        dexCmd += ' ' + file_utils.getMultiDexPath()

    # sdk实现类
    print('packageing all jar ...')
    dexCmd += ' ' + os.path.join(sdkPath, '%s.jar' % sdk)
    ret = file_utils.execJarCmd(dx, dexCmd)
    if ret:
        return ret

    # 反向dex生成smali
    # 存放在out目录
    print('baksmali classes.dex ...')
    outDex = os.path.join(outPath, 'classes.dex')
    baksmaliPath = file_utils.getBaksmaliPath()
    outPath = file_utils.getFullPath(decompliePath, 'out')
    
    ret = file_utils.execJarCmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath))
    if ret:
        return ret

    # 将生成的文件拷贝到目标目录
    print('copy all smali ...')
    smaliPath = file_utils.getFullPath(decompliePath, 'smali')
    ret = file_utils.copyFileAllDir(outPath, smaliPath, True)
    if ret:
        return ret

    return 0

def packLogJar(game, sdk, subChannel, config, logSdk):
    '''
    打包Log jar
    '''
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    outPath = file_utils.getFullPath(decompliePath, 'gen')

    if not os.path.exists(outPath):
        os.makedirs(outPath)

    if config['aapt2disable']:
        dx = file_utils.getDxPath()
        dexCmd = '--dex --multi-dex --no-warning --output="%s"' % outPath
    else:
        dx = file_utils.getD8Path()
        androidPlatforms = file_utils.getAndroidCompileToolPath()
        dexCmd = '--lib "%s" --output "%s"' % (androidPlatforms, outPath)

    # 找到所有lib依赖
    sdkPath = file_utils.getFullLogSDKPath(logSdk)
    libs = os.path.join(sdkPath, 'libs')
    libConfig = os.path.join(libs, 'config.json')

    # 存在配置文件
    if os.path.exists(libConfig):
        jsonText = file_utils.readFile(libConfig)
        libList = json.loads(jsonText)
        for jar in libList:
            if not jar.endswith('.jar'):
                continue
            dexCmd += ' ' + os.path.join(libs, jar)
    else:
        for jar in os.listdir(libs):
            if not jar.endswith('.jar'):
                continue
            dexCmd += ' ' + os.path.join(libs, jar)

    # sdk实现类
    print('packageing all log jar ...')
    dexCmd += ' ' + os.path.join(sdkPath, '%s.jar' % logSdk)
    ret = file_utils.execJarCmd(dx, dexCmd)
    if ret:
        return ret

    # 反向dex生成smali
    # 存放在out目录
    print('baksmali classes.dex ...')
    outDex = os.path.join(outPath, 'classes.dex')
    baksmaliPath = file_utils.getBaksmaliPath()
    outPath = file_utils.getFullPath(decompliePath, 'out')
    
    ret = file_utils.execJarCmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath))
    if ret:
        return ret

    # 将生成的文件拷贝到目标目录
    print('copy all log smali ...')
    smaliPath = file_utils.getFullPath(decompliePath, 'smali')
    ret = file_utils.copyFileAllDir(outPath, smaliPath, True)
    if ret:
        return ret

    return 0




def packReativeJar(game, sdk, subChannel, config, logSdk):
    '''
    打包Reative jar
    '''
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    outPath = file_utils.getFullPath(decompliePath, 'gen')

    if not os.path.exists(outPath):
        os.makedirs(outPath)

    if config['aapt2disable']:
        dx = file_utils.getDxPath()
        dexCmd = '--dex --multi-dex --no-warning --output="%s"' % outPath
    else:
        dx = file_utils.getD8Path()
        androidPlatforms = file_utils.getAndroidCompileToolPath()
        dexCmd = '--lib "%s" --output "%s"' % (androidPlatforms, outPath)

    # 找到所有lib依赖
    sdkPath = file_utils.getFullLogSDKPath(logSdk)
    libs = os.path.join(sdkPath, 'libs')
    libConfig = os.path.join(libs, 'config.json')

    # 存在配置文件
    if os.path.exists(libConfig):
        jsonText = file_utils.readFile(libConfig)
        libList = json.loads(jsonText)
        for jar in libList:
            if not jar.endswith('.jar'):
                continue
            dexCmd += ' ' + os.path.join(libs, jar)
    else:
        for jar in os.listdir(libs):
            if not jar.endswith('.jar'):
                continue
            dexCmd += ' ' + os.path.join(libs, jar)

    # sdk实现类
    print('packageing all log jar ...')
    dexCmd += ' ' + os.path.join(sdkPath, '%s.jar' % logSdk)
    ret = file_utils.execJarCmd(dx, dexCmd)
    if ret:
        return ret

    # 反向dex生成smali
    # 存放在out目录
    print('baksmali classes.dex ...')
    outDex = os.path.join(outPath, 'classes.dex')
    baksmaliPath = file_utils.getBaksmaliPath()
    outPath = file_utils.getFullPath(decompliePath, 'out')

    ret = file_utils.execJarCmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath))
    if ret:
        return ret

    # 将生成的文件拷贝到目标目录
    print('copy all log smali ...')
    smaliPath = file_utils.getFullPath(decompliePath, 'smali')
    ret = file_utils.copyFileAllDir(outPath, smaliPath, True)
    if ret:
        return ret

    return 0

def splitDex(game, sdk, subChannel, config):
    '''
    分割dex
    '''
    # 判断是否已经存在application
    # 存在,则往原application添加内容
    # 不存在,则拷贝一个默认的android.support.multidex.MultiDexApplication
    print('add MultiDex support...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    application = xml_utils.getApplicationAttr(manifest, 'name')
    if application is None:
        ret = xml_utils.changeApplicationAttr(manifest, 'name', 'android.support.multidex.MultiDexApplication')
        if ret:
            return ret
    else:
        smaliPath = os.path.join(decompliePath, 'smali')
        applicationFile = file_utils.getPackagePath(smaliPath, application)
        applicationFile += '.smali'
        ret = changeApplicationDex(applicationFile)
        if ret:
            return ret

    return splitSmali(game, sdk, subChannel, config, application)

def changeApplicationDex(file):
    '''
    修改application的smali文件,增加MultiDex操作
    '''
    index = file_utils.getApplicationSmaliIndex(file)
    file_utils.insertApplicationSmali(file, index)
    return 0

def splitSmali(game, sdk, subChannel, config, application):
    '''
    如果函数上限超过限制,自动拆分smali,以便生成多个dex文件
    '''
    print('splitSmali...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    smaliPath = os.path.join(decompliePath, 'smali')

    appPackage = None
    if application:
        appPackage = application[:application.rfind('.')]
        appPackage = appPackage.replace('.', '/')

    allFiles = []
    allFiles = file_utils.list_files(smaliPath, allFiles)
    
    #print('file count is %d' % len(allFiles))

    #maxFuncNum = 65535
    # 留一点空间,防止计算误差
    maxFuncNum = 64000
    currFucNum = 0
    totalFucNum = 0

    currDexIndex = 1

    allRefs = []

    #保证Application等类在第一个classex.dex文件中
    for f in allFiles:
        f = f.replace('\\', '/')
        if (appPackage and appPackage in f) or '/android/support/multidex' in f:
            currFucNum += smali_utils.get_smali_method_count(f, allRefs)

    totalFucNum = currFucNum
    for f in allFiles:
        f = f.replace('\\', '/')
        if not f.endswith('.smali'):
            continue

        if (appPackage and appPackage in f) or '/android/support/multidex' in f:
            continue

        thisFucNum = smali_utils.get_smali_method_count(f, allRefs)
        totalFucNum += thisFucNum

        #print('%d # %d ==> %s' % (thisFucNum, currDexIndex, f))
        #print('totalFucNum is %d' % totalFucNum)
        if currFucNum + thisFucNum >= maxFuncNum:
            currFucNum = thisFucNum
            currDexIndex += 1
            newDexPath = os.path.join(decompliePath, 'smali_classes%d' % currDexIndex)
            os.makedirs(newDexPath)
        else:
            currFucNum += thisFucNum

        if currDexIndex > 1:
            newDexPath = os.path.join(decompliePath, 'smali_classes%d' % currDexIndex)
            targetFile = newDexPath + f[len(smaliPath):]
            file_utils.copyFile(f, targetFile, True)
    return 0

def changeVersion(game, sdk, subChannel, config):
    '''
    更改版本号
    '''
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    yml = os.path.join(decompliePath, 'apktool.yml')

    versionCode = None
    versionName = None
    targetSdkVersion = None
    if 'versionCode' in config:
        versionCode = config['versionCode']
    if 'versionName' in config:
        versionName = config['versionName']
    if 'targetSdkVersion' in config:
        targetSdkVersion = config['targetSdkVersion']
    else:
        targetSdkVersion = 26
    print('changeVersion versionCode=%s,versionName=%s,targetSdkVersion=%s' % (versionCode, versionName, targetSdkVersion))

    return file_utils.changeVersion(yml, versionCode, versionName, targetSdkVersion)

def recomplie(game, sdk, subChannel, config):
    '''
    回编译
    '''
    print('recomplie apk...')
    apktoolPath = file_utils.getApkToolPath()
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])

    useAppt2 = ' --use-aapt2'
    if config['aapt2disable']:
        useAppt2 = ''

    return file_utils.execJarCmd(apktoolPath, 'b -f "%s" -o "%s"%s' % (decompliePath, outApk, useAppt2))

def alignApk(game, sdk, subChannel, config):
    '''
    对齐apk
    '''
    print('align apk...')
    outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
    alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
    alignapkTool = file_utils.getAlignPath()
    if os.path.exists(alignApk):
        os.remove(alignApk)

    ret = file_utils.getExecPermission(alignapkTool)
    if ret:
        return ret

    # zipalign.exe -v -p 4 input.apk output.apk
    return file_utils.execFormatCmd('"%s" -f -p 4 "%s" "%s"' % (alignapkTool, outApk, alignApk))

def apksignerApk(game, sdk, subChannel, config):
    '''
    签名apk
    '''
    print('sign apk...')
    print('game = %s, sdk = %s, subChannel = %s, ...' % (game,sdk,subChannel))
    path = os.path.join(file_utils.getCurrentPath(), 'keystore', 'key.json')
    jsonText = file_utils.readFile(path)
    signConfig = json.loads(jsonText)
    keystore = {}
    for key in signConfig.keys():
        print(key)
        if game.find(key) > -1:
            if sdk in signConfig[key]:
                keystore = signConfig[key][sdk]
            else:
                keystore = signConfig['default']
        else:
            keystore = signConfig['default']

    # if game in signConfig:
    #     if sdk in signConfig[game]:
    #         keystore = signConfig[game][sdk]
    #     else:
    #         keystore = signConfig['default']
    # else:
    #     keystore = signConfig['default']

    print('storeFile is "%s"' % keystore['storeFile'])
    
    apksigner = file_utils.getApksignerPath()
    alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
    signedApk = file_utils.getSignApkPath(game, sdk, subChannel, config['cache'])
    storeFile = os.path.join(file_utils.getCurrentPath(), 'keystore', keystore['storeFile'])

    if 'outName' in config and 'outPath' in config:
        if not os.path.exists(config['outPath']):
            os.makedirs(config['outPath'])

        signedApk = os.path.join(config['outPath'], config['outName'] + '.apk')
    elif 'outName' in config:
        signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])
    
    # java -jar apksigner.jar sign --ks key.jks --ks-key-alias releasekey --ks-pass pass:pp123456 --key-pass pass:pp123456 --out output.apk input.apk
    v2disable = ''
    if 'v2disable' in config and config['v2disable']:
        v2disable = ' --v2-signing-enabled=false'
        
    return file_utils.execJarCmd(apksigner, 'sign%s --ks "%s" --ks-key-alias %s --ks-pass pass:%s --key-pass pass:%s --out "%s" "%s"' % (v2disable, storeFile, keystore['keyAlias'], keystore['storePassword'], keystore['keyPassword'], signedApk, alignApk))

def addChannel(game, sdk, subChannel, config):
    '''
    添加渠道信息
    '''
    if 'v2disable' in config and config['v2disable']:
        return 0
    
    walle = file_utils.getWallePath()
    signedApk = file_utils.getSignApkPath(game, sdk, subChannel, config['cache'])
    if 'outName' in config and 'outPath' in config:
        signedApk = os.path.join(config['outPath'], config['outName'] + '.apk')
    elif 'outName' in config:
        signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])

    properties = config['properties']

    appid = ''
    appkey = ''
    host = ''
    if 'appid' in properties:
        appid = properties['appid']
    if 'appkey' in properties:
        appkey = properties['appkey']
    if 'host' in properties:
        host = properties['host']

    return file_utils.execJarCmd(walle, 'put -e version=%s,agent=%s,appid=%s,appkey=%s,host=%s "%s" "%s"' % (config_utils.getDate(), properties['agent'], appid, appkey, host, signedApk, signedApk))

def clearTemp(game, sdk, subChannel, config):
    '''
    清空中间产生的文件
    '''
    print('clear temp...')
    alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
    outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
    if os.path.exists(alignApk):
        os.remove(alignApk)
    if os.path.exists(outApk):
        os.remove(outApk)

    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    file_utils.deleteFolder(decompliePath)

    print('clear temp end')

def packConsoleInput():
    '''
    控制台打包
    '''
    if len(sys.argv) < 3:
        print('argument is missing')
        return 1

    # 校验参数
    game = sys.argv[1]
    sdk = sys.argv[2]
    # 可选参数,没有则默认打全部渠道
    subChannel = None
    if len(sys.argv) > 3:
        subChannel = sys.argv[3]
        
    return packConsole(game, sdk, subChannel)

def packConsole(game, sdk, subChannel):
    '''
    控制台打包
    '''
    print('--------- yfsdk ---------')

    if not os.path.exists(file_utils.getFullGameApk(game)):
        print('game "%s" not exists' % game)
        return 1
    # if not os.path.exists(file_utils.getFullSDKPath(sdk)):
    #     print('sdk "%s" not exists' % sdk)
    #     return 1
    global  startTime
    startTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    # 读取配置
    channelPath = file_utils.getChannelPath(game, sdk)
    configPath = os.path.join(channelPath, 'config.json')
    if not os.path.exists(configPath):
        print('%s not exists' % configPath)
        return 1
    
    jsonText = file_utils.readFile(configPath)
    config = json.loads(jsonText)

    # 检查参数
    if not config_utils.checkConfig(config):
        return 1

    # 处理参数
    config_utils.replaceArgs(config)

    successCount = 0
    failureCount = 0
    if type(config) == dict:
        if subChannel is None or config['subChannel'] == subChannel:
            ret = pack(game, sdk, config)
            if ret:
                failureCount += 1
            else:
                successCount += 1
        else:
            print('subChannel "%s" no found' % subChannel)
            return 1
    elif type(config) == list:
        found = False
        for itemConfig in config:
            if subChannel is None or itemConfig['subChannel'] == subChannel:
                found = True
                ret = pack(game, sdk, itemConfig)
                if ret:
                    failureCount += 1
                else:
                    successCount += 1

        if not found:
            print('subChannel "%s" no found' % subChannel)
            return 1

    print('success %d, failure %d' % (successCount, failureCount))
        
    return 0