# 安卓游戏打包脚本
# 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_record
import game_utils
import os
import os.path
import json
import sys
import importlib
import uuid
import zipfile

from xml.etree import ElementTree as ET
from xml.etree.ElementTree import SubElement

def pack(game, sdk, config):
    config['cache'] = uuid.uuid1()
    subChannel = config['subChannel']
    # 解包
    ret = decomplie(game, sdk, subChannel, config)
    if ret:
        return ret
    # 删除旧代码
    ret = removeOldCode(game, sdk, subChannel, config)
    if ret:
        return ret
    if 'deleteList' in config:
        # 删除旧资源
        ret = removeOldRes(game, sdk, subChannel, config)
        if ret:
            return ret
    # 删除一些不支持的属性
    ret = removeNoSupportAttr(game, sdk, subChannel, config)
    if ret:
        return ret
    # 删除一些不支持的配置
    ret = fixUnSupportConfig(game, sdk, subChannel, config)
    if ret:
        return ret
    # 合并Drawable-v4目录
    ret = mergeDrawableRes(game, sdk, subChannel, config)
    if ret:
        return ret
    # 移除相同的资源
    ret = removeSameRes(game, sdk, subChannel, config)
    if ret:
        return ret
    # 复制res资源
    ret = copyRes(game, sdk, subChannel, config)
    if ret:
        return ret
    # 合并主文件
    ret = mergeManifestRes(game, sdk, subChannel, config)
    if ret:
        return ret
    # 替换占位符
    ret = changePlaceholders(game, sdk, subChannel, config)
    if ret:
        return ret
    # 添加meta-data
    ret = addMetaData(game, sdk, subChannel, config)
    if ret:
        return ret
    # 复制app res资源
    ret = copyAppRes(game, sdk, subChannel, config)
    if ret:
        return ret
    # 合并语言文件
    ret = mergeLanguage(game, sdk, subChannel, config)
    if ret:
        return ret
    # 增加配置文件
    ret = addConfig(game, sdk, subChannel, config)
    if ret:
        return ret
    #保存旧包名
    config['oldPackageName'] = getPackageName(game, sdk, subChannel, config)
    # 更改包名
    if 'packageName' in config and config['packageName'] != '':
        ret = changePackageName(game, sdk, subChannel, config)
        if ret:
            return ret
    else:
        config['packageName'] = getPackageName(game, sdk, subChannel, config)
    # 更改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
    # 打包lib依赖
    ret = packJar(game, sdk, subChannel, config)
    if ret:
        return ret
    # sdk脚本处理
    ret = doSDKPostScript(game, sdk, config)
    if ret:
        return ret
    # 乐变sdk的特殊处理
    ret = game_utils.sdkLebianChange(game, sdk, config)
    if ret:
        return ret
    # 复制EntryActivity文件
    ret = copyEntryActivityCode(game, sdk, subChannel, config)
    if ret:
        return ret
    # 游戏脚本处理
    ret = doGamePostScript(game, sdk, config)
    if ret:
        return ret
    # 生成R文件
    '''ret = generateNewRFile(game, sdk, subChannel, config)
    if ret:
        return ret'''
    # 添加MultiDex支持
    if config['splitDex']:
        ret = splitDex(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)

    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 removeOldRes(game, sdk, subChannel, config):
    '''
    删除旧资源
    '''
    print('delete res ...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    for subPath in config['deleteList']:
        resPath = os.path.join(decompliePath, subPath)
        if os.path.exists(resPath):
            os.remove(resPath)
            print('delete ' + resPath)
    return 0

def removeOldCode(game, sdk, subChannel, config):
    '''
    删除旧代码
    '''
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    codePath = os.path.join(decompliePath, 'smali', 'com', 'jmhy', 'lib', 'record')
    #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'''

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

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

    removeList = []
    # 移除相同的资源
    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):
            print('resFile ==> ' + os.path.join(absPath, resFile))
            #xml_utils.removeSameRes(os.path.join(absPath, resFile), resList)
            removeList = xml_utils.removeSameRes2(os.path.join(absPath, resFile), resList, removeList)

    print('--------------')
    print(removeList)
    if len(removeList) == 0:
        print('no same res remove')
        return 0
    publicResPath = os.path.join(decompliePath, 'res/values/public.xml')
    print('publicResPath ---->' + publicResPath)
    xml_utils.removeIdFromPublic(publicResPath,removeList)


    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...')
    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 mergeLanguage(game, sdk, subChannel, config):
    '''
    合并语言文件
    '''
    print('merge language...')
    channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
    mergePath = file_utils.getFullPath(channelPath, 'merge')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])

    if not os.path.exists(mergePath):
        print('%s not exists!' % mergePath)
        return 0

    if not os.path.isdir(mergePath):
        print('%s not a dir!' % mergePath)
        return 0

    for fileName in os.listdir(mergePath):
        mergeJson(mergePath, decompliePath, fileName)
    return 0

def mergeJson(srcDir, changeDir, fileName):
    srcFile = os.path.join(srcDir, fileName)
    if os.path.isdir(srcFile):
        for fileName2 in os.listdir(srcFile):
            changeDir2 = os.path.join(changeDir, fileName)
            mergeJson(srcFile, changeDir2, fileName2)
    else:
        changeFile = os.path.join(changeDir, fileName)
        if not os.path.exists(changeFile):
            print('%s not exists!' % changeFile)
            return 0
        jsonText1 = file_utils.readFile(srcFile)
        jsonText2 = file_utils.readFile(changeFile)
        print('*************src config*************')
        print(jsonText1)
        print('************************************')
        print('*************target config*************')
        print(jsonText2)
        print('************************************')

        json1 = json.loads(jsonText1)
        json2 = json.loads(jsonText2)

        for item in json1:
            if item in json2:
                json2[item] = json1[item]

        jsonStr = json.dumps(json2, ensure_ascii=False)
        print('*************merge config*************')
        print(jsonStr)
        print('************************************')
        print('>> %s' % changeFile)
        file_utils.createFile(changeFile, jsonStr)
    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 getPackageName(game, sdk, subChannel, config):
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
    packageName = xml_utils.getPackageName(manifest)
    return packageName

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

    resName = 'shanshen_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 = 'record_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:
        print('configData is null')
        return 0

    print('add config.json...')
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    configJson = os.path.join(decompliePath, 'assets', 'tool_config.json')
    jsonText = json.dumps(config['configData'], ensure_ascii=False)
    file_utils.createFile(configJson, jsonText)
    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):
    '''
    添加启动图
    '''
    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.shanshen.sdk.template.LauncherActivity':
        print('add launcher already exist...')
        return 1

    # 添加关联资源
    internalPath = os.path.join(file_utils.getCurrentPath(), 'internal_shanshen')
    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.shanshen.sdk.template.LauncherActivity')

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

    print('change launcher %s to %s' % (activity, 'com.shanshen.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 formatHex(millisecond):
    '''
    将毫秒转为16进制，4位格式
    '''
    timeHex = str(hex(millisecond)).upper()
    timeHex = timeHex[2:]
    formatHex = ''
    if len(timeHex) == 3:
        formatHex = '0x0' + timeHex
    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 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('create R.java ...')
    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 ...')
    dx = file_utils.getDxPath()
    outDex = os.path.join(compliePath, 'classes.dex')
    ret = file_utils.execJarCmd(dx, '--dex --output="%s" "%s"' % (outDex, compliePath))
    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 ...')
    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 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']

    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...')
    path = os.path.join(file_utils.getCurrentPath(), 'keystore', 'key.json')
    jsonText = file_utils.readFile(path)
    signConfig = json.loads(jsonText)
    keystore = {}
    if game in signConfig:
        if sdk in signConfig[game] and subChannel in signConfig[game][sdk]:
            keystore = signConfig[game][sdk][subChannel]
        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')
        print('signedApk = ' + signedApk)
    elif 'outName' in config:
        signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])
        print('signedApk = ' + signedApk)

    # 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 = ''
    if 'appid' in properties:
        appid = properties['appid']
    if 'appkey' in properties:
        appkey = properties['appkey']

    return file_utils.execJarCmd(walle, 'put -e version=%s,agent=%s,appid=%s,appkey=%s "%s" "%s"' % (config_utils_record.getDate(), properties['agent'], appid, appkey, 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):
    '''
    控制台打包
    '''
    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

    # 读取配置
    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_record.checkConfig(config):
        return 1

    # 处理参数
    config_utils_record.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

def copyEntryActivityCode(game, sdk, subChannel, config):
    '''
    拷贝代码
    '''
    print('copy EntryActivity.smali')
    sdkPath = file_utils.getFullSDKPath(sdk)
    EntryActivity = 'EntryActivity.smali'
    entryFile = os.path.join(sdkPath, 'smali', EntryActivity)
    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
    smaliPath = os.path.join(decompliePath, 'smali')
    targetPath = file_utils.getPackagePath(smaliPath, config['packageName'])
    targetFile = os.path.join(targetPath, EntryActivity)
    ret = file_utils.copyFile(entryFile, targetFile)
    if ret:
        return ret
    writeActivityToManifest(os.path.join(decompliePath, 'AndroidManifest.xml'),config)
    oldText = 'com/jmhy/floatsdk/sample/EntryActivity'
    newText = config['packageName'].replace('.', '/') + "/EntryActivity"
    print("EntryActivity.smali change '{}' to '{}' ...".format(oldText,newText))
    file_utils.replaceContent(targetFile, oldText, newText)
    return 0

def writeActivityToManifest(targetManifest, config):
    androidNS = 'http://schemas.android.com/apk/res/android'
    ET.register_namespace('android', androidNS)
    targetTree = ET.parse(targetManifest)
    targetRoot = targetTree.getroot()
    appNode = targetRoot.find('application')
    activitys = appNode.findall('activity')
    keyName = "{0}{1}{2}name".format("{",androidNS,"}")
    theme = "{0}{1}{2}theme".format("{",androidNS,"}")
    for activity in activitys:
        activityName = activity.get(keyName)
        if activityName.find('.EntryActivity')>=0:
            oldName = activityName;
            newName = config['packageName'] + ".EntryActivity"
            activity.set(keyName,newName)
            print("EntryActivity change '{}' to '{}' ...".format(oldName, newName))
        if activityName.find('com.tool.floatsdk.ui.WebActivity')>=0:
            if activity.get(theme) is not None or activity.get(theme) != "":
                del activity.attrib[theme]

    perNode = targetRoot.find('permissions')
    #permissions = perNode.findall('uses-permission')
    print(perNode)
    print("------------")

    targetTree.write(targetManifest, 'UTF-8')




