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 节点作为解释:<public type="anim" name="abc_fade_in" id="0x7f010000" />
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 的回编等等了