两个Apk合并后R文件的纠正

R 文件与public.xml 不匹配的问题。我们先要了解 public.xml 文件是怎么来的,

public.xml 其实就 我们在使用 apktools 进行逆向 apk 的时候将 apk 中的  resource.arsc 中的id 与资源的关系解释成xml

resource.arsc 又是我们在打包成apk 时候 aapt 将我们所有的资源文件压缩成的一个数据库。

而我们是两个apk 进行融合~那么两个apk 执行appt 的时候并不知道有对方的存在,这样的话就必然存在id 值相似的问题,也就是我们在融合的时候出现 R 文件中引用的资源错误问题。

既然我们知道了问题原因,那么要解决比较容易找到办法了~

1.只要将两个 apk 的 public.xml 进行合并 并且保证 public.xml 中所有id的唯一

2.找到所有的 R 文件位置,将合并后的 public.xml 进比较纠正 R 文件中的资源值即可

好基本计划制定了,我们就按照计划开始做

一、怎么合并 两个public.xml,且保证合并后所有id 都是唯一

(因为是两个apk 所有也两个public 我们分为主 public 与次public ,目标是将次public 合并到主public 中)

先需要了解public中的内容,public.xml 是由于多个 public 节点组成:我们抽取一个public 节点作为解释:

type:表示为类型

name:资源名称

id:则是资源对应在 public 中的唯一标志

id的16进制值的意义:前两位 0x 表示为16进制  7f01 则表示类型 0000 在该类型中的自增长值

知道了规来我们就可以制定合并方案:

1.读取 主 public 生成 字典 字典中展现 主public 中所有 type 对应最大id值 以及整个public 中最大的值(如果主public 没有 次public 文件中类型那么我们就需要对类型进行升级)

2.为了保证资源唯一那么我们也有必要将 主public 中所有资源 对象化:

{type1: [{name:id, name1:id1}]

type2: [{name2:id2, name3:id4}]}

主要是为了防止如:主public 有个 app_name = "我是主" 次public 也有个 app_name =“我是次”这样的问题,这个可以根据自身需求进行判断

3.主不存在的 资源进入后我们只要在以上基础上判断 如果不属于新类型 则在旧类型的基础上转成10进制+1后 转成16进制存储即可(PS:记得更新 类型与最大id关系对象)。如果是新类型则在 最大id上将类型的那部分+1,其余逻辑一样

以下以node js  为脚本 编码了 public 合并代码:

----------------------------------------publicMergerTask.js---------------------------------------------

// 用于 public.xml 合并

//文件读取依赖

varfs=require('fs')

//这个依赖包主要是讲 xml 转化成js 对象

varxml2js=require('xml2js')

//母包 public.xml 位置

vartoXmlPath=

// 要合并的 public.xml 位置

varfromXmlPath=

/**

* 用于存储 public 的对象值如下:

* {

* type1:[{name1:id1},...],

* type2:[{name2:id2},...],

* ...

* }

*

* @type {{}}

*/

varmPublicObjectData={}

/**

* 每种类型的最大id值

* 以及 最大id值

*

* @type {

    type1:id1,

    type2:id2,

    maxTypeId:maxId

}

*/

varmTypeMaxId={}

varmToXml={}

/**

* 获取  新的 Id值

* @param type  对应类型

* @returns {string} 16 进制

*/

functiongetNewId(type) {

varid=mTypeMaxId[type]

if(id==undefined) {

id=getNewTypeId()

}else{

        //记得携带  16 进制标志 eval 的是进制转化 看后面那个16

id="0x"+eval(Number(id)+1).toString(16)

   }

mTypeMaxId[type]=id

returnid

}

/**

* 从 typeArray 中获取新的 类型的 id值起始值

* @param typeArray

* @returns {string} 16进制

*/

functiongetNewTypeId() {

varnewId=Number(mTypeMaxId['maxTypeId'])+1

//因为通过  Number 进行强转 所以需要添加 0x  标志为16进制

varmaxTypeId="0x"+eval(newId).toString(16)

mTypeMaxId['maxTypeId']=maxTypeId

returnmTypeMaxId['maxTypeId']+'0000'

}

//加载母包的 public.xml

//

//

functionloadToXmlPath(cb) {

letparser=newxml2js.Parser();

letbuilder=newxml2js.Builder();

vartoPublicData

fs.readFile(toXmlPath,"utf-8",function(err,toXMLData) {

if(err) {

console.log("读取母包文件失败",err);

throwerr

       }

parser.parseString(toXMLData,function(err,res) {

mToXml=res

       });

toPublicData=mToXml['resources']['public']

loadPublicData(toPublicData,cb)

   })

}

//加载出 母包的

// mTypeMaxId 对象 与 mPublicObjectData 对象类型

functionloadPublicData(publicData,cb) {

varmaxId="0"

for(itemofpublicData) {

varattr=item['$']

vartype=attr['type']

varname=attr['name']

varid=attr['id']

maxId=Number(id)>Number(maxId)?id:maxId

addPublicTypeItem(type,name,id)

addTypeMaxId(type,id)

   }

/**

    * 在全局的 typeMaxId 存入 maxTypeId

    * @type {string}

    */

mTypeMaxId['maxTypeId']=maxId.substr(0,6)


cb()

}

/**

* 添加 public对象

* @param type

* @param name

* @param id

*/

functionaddPublicTypeItem(type,name,id) {

vartypeArry=mPublicObjectData[type]

if(typeArry==undefined) {

mPublicObjectData[type]=newArray()

   }

mPublicObjectData[type][name]=id

}

/**

* 添加 typeMaxId 对象

* @param type

* @param id

*/

functionaddTypeMaxId(type,id) {

varoldTypeValue=mTypeMaxId[type]

if(oldTypeValue==undefined) {

mTypeMaxId[type]=id

}elseif(Number(id)>Number(mTypeMaxId[type])) {

mTypeMaxId[type]=id

   }

}

//读取需要合并资源

functionreadFormXml() {

letparser=newxml2js.Parser();

letbuilder=newxml2js.Builder();

vartoPublicData=mToXml['resources']['public']

fs.readFile(fromXmlPath,"utf-8",function(err,fromXMLData) {

if(err) {

console.log("读取文件失败",err);

throwerr

       }

parser.parseString(fromXMLData,function(err,res) {

fromXml=res

       });

for(varitemoffromXml['resources']['public']) {

varattr=item['$']

vartype=attr['type']

            //这个id 需要重新生成

varid=getNewId(type)

varname=attr['name']

varisNotFilter=isNotFilterTypeName(type,name)

//如果存就不进行添加

if(!isNotFilter) {

continue

           }

toPublicData[toPublicData.length]={

'$': {'type':type,'name':name,'id':id}

           }

if('fill'==name){

console.log('fill',toPublicData[toPublicData.length])

           }

       }

varoutXml=builder.buildObject(mToXml)

fs.writeFile(toXmlPath,outXml,function(err) {

if(err) {

console.log("写入文件失败",err);

throwerr

           }

       })

   })

}

functionisNotFilterTypeName(type,name) {

varisNotFilter=false

    //这部分不知道是不是 node 的坑,name=fill 传入字典会返回一个function类型,所以nodejs 需要在此坐个过滤

if(mPublicObjectData[type]!=undefined&&typeofmPublicObjectData[type][name]!='string') {

isNotFilter=true

}else{

isNotFilter=mPublicObjectData[type]==undefined?true:mPublicObjectData[type][name]==undefined

   }

returnisNotFilter

}

functionarrange() {

loadToXmlPath(function() {

readFormXml();

   })

}

//入口

arrange();

----------------------------------------publicMergerTask.js---------------------------------------------

public的合并已经解决了

接下里就可以去纠正 R 文件与public 的映射关系了

我们观察R文件所在的文件目录,我们可以得出如下结论:

\1. 正真映射资源的R文件都是以R$ 开头

2.R$.smali 与中的 与public 中的type 对应

3.R$styleable.smali 与public 没有对应,因为里面存放的是一些数组,真正对应还是数组里面item,所以这个不需要我们处理

4.R$style.smali 中资源名称是以”_“ 为分隔符,而public中type=style 中的资源名称 则是以 "."分割

根据以上四点以及我们生成好的public作为纠正参考我们可以就可以开始写代码了

以下也是以node 作为脚本 所编码的纠正代码:

-------------------------coorectAllRTask.js----------------------------

//通过读取 public.xml 重新校对所有 typeSDK 相关的 R 文件对应值

//文件

varfs=require('fs')

//xml转对象

varxml2js=require('xml2js')

//按行读取依赖

varreadLine=require('readline')

//项目路径

varmTagPcakagePath=

//完成合并public.xml位置

varmPublicPath=

// 需要纠正的R文件集合位置

varmCoorectRTxt=

/**

* 用于存储 public 的对象值如下:

* {

* type1:[{name1:id1},...],

* type2:[{name2:id2},...],

* ...

* }

*

* @type {{}}

*/

varmPublicXMLData={}

/**

* 先将 public 文件加载出来

* 作为全局使用

* 主要是从母包中读取出 mPublicXMLData 对象

* @param callback

*/

functionreadPublicXMLData(callback) {

letparser=newxml2js.Parser();

varpublicData

varresult={}

fs.readFile(mPublicPath,"utf-8",function(err,toXMLData) {

if(err) {

console.log("读取 母包 public文件失败",err);

throwerr

       }

parser.parseString(toXMLData,function(err,res) {

publicData=res['resources']['public']

       });

for(varitemofpublicData) {

varattr=item['$']

vartype=item['$']['type']

varname=item['$']['name']

varid=item['$']['id']

varnewItem={name:id}

vartypeArray=result[type]

if(typeArray==undefined) {

result[type]=newArray()

result[type][name]=id

}else{

result[type][name]=id

           }

       }

mPublicXMLData=result;

callback();

   })

}

/**

* 读取我们写好在 渠道或者 插件中需要修正的 R 文件列表

* 每个包名需要换行

*/

functionreadCoorectRXMLFile(){

varreadObj=readLine.createInterface({input:fs.createReadStream(mCoorectRTxt)})

varrList=[]

readObj.on('line',function(line) {

rList.push(line)

   })

readObj.on('close',function() {

for(varfileofrList) {

            // 在R 文件位置记录文件中 位置是以 . 为分割需要转化成 路径分割

varreg=newRegExp("\\.",'g');

file=file.replace(reg,'/')

file=mTagPcakagePath+"/game/smali/"+file

readRPath(file)

       }

   })

}

/**

* 读取 R 的文件夹 将其下的 所有 R相关文件读取出来

* R$styleable.smali 除外

* @param folderpath

*/

functionreadRPath(folderpath) {

//获取一个目录下所有相关的R 文件

varallRFiles=fs.readdirSync(folderpath).filter((f)=>{

        //过滤 非R$ 开头和 R$styleable.smali文件

returnf.startsWith("R$")&&!f.endsWith('R$styleable.smali')

   })

for(varfofallRFiles) {

coorectR(folderpath+"/"+f,function(arr,filePath) {

fs.writeFile(filePath,arr.toString(),function(err){

console.log(err)

           })

       })

   }

}

/**

* 开始对R 文件 中与 public 文件中对应的 值进行修正

* @param filePath

* @param callback

*/

functioncoorectR(filePath,callback) {

vararr=""

varreadObj=readLine.createInterface({input:fs.createReadStream(filePath)})

varend=filePath.lastIndexOf('.')

varstart=filePath.lastIndexOf('$')+1

vartype=filePath.substr(start,end-start)

varisStyle='style'==type

vartypeArray=mPublicXMLData[type]

readObj.on('line',function(line) {

if(line.startsWith('.field public static final ')) {

varname=getNameBySmali(line,isStyle)

varvalue=getValueBySmali(line)

varnewValue=typeArray[name]

if(typeofnewValue=='string'){

// console.log(filePath, type, name,newValue)

line=line.replace(value,newValue)

           }

       }

arr+=line+'\n'

   })

readObj.on('close',function() {

callback(arr,filePath)

   })

}

functiongetNameBySmali(line,isStyle){

varnameLastIndex=line.indexOf(':');

varnameStartIndex='.field public static final '.length;

varname=line.substr(nameStartIndex,nameLastIndex-nameStartIndex).trim()

if(isStyle){

varreg=newRegExp("_",'g');

name=name.replace(reg,'.')

   }

returnname

}

functiongetValueBySmali(line){

//+2 是因为 除了本身外,他还有个空格

varvalueStartIndex=line.indexOf("=")+2

varvalue=line.substr(valueStartIndex,line.length).trim()

returnvalue

}

functionarrange() {

readPublicXMLData(function() {

readCoorectRXMLFile()

   });

}

arrange();

剩余就是apktool 的回编等等了

你可能感兴趣的:(两个Apk合并后R文件的纠正)