From 12f4d4f9d669013433c63c280935a3a276026b71 Mon Sep 17 00:00:00 2001 From: REAndroid Date: Thu, 29 Dec 2022 15:21:33 -0500 Subject: [PATCH] Implement resource values XML decoder --- .../lib/apk/ApkModuleXmlDecoder.java | 108 +++++++++++++++++- .../java/com/reandroid/lib/apk/ResFile.java | 3 + 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/reandroid/lib/apk/ApkModuleXmlDecoder.java b/src/main/java/com/reandroid/lib/apk/ApkModuleXmlDecoder.java index 0eece16..33ae279 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkModuleXmlDecoder.java +++ b/src/main/java/com/reandroid/lib/apk/ApkModuleXmlDecoder.java @@ -16,36 +16,50 @@ package com.reandroid.lib.apk; import com.reandroid.lib.arsc.chunk.PackageBlock; +import com.reandroid.lib.arsc.chunk.TableBlock; +import com.reandroid.lib.arsc.chunk.TypeBlock; import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock; import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock; +import com.reandroid.lib.arsc.container.SpecTypePair; +import com.reandroid.lib.arsc.decoder.ValueDecoder; import com.reandroid.lib.arsc.value.EntryBlock; +import com.reandroid.lib.arsc.value.ResConfig; +import com.reandroid.lib.arsc.value.ResValueInt; +import com.reandroid.lib.arsc.value.ValueType; import com.reandroid.lib.common.EntryStore; import com.reandroid.lib.common.Frameworks; import com.reandroid.lib.common.TableEntryStore; +import com.reandroid.xml.XMLAttribute; import com.reandroid.xml.XMLDocument; +import com.reandroid.xml.XMLElement; import com.reandroid.xml.XMLException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.List; +import java.util.*; -public class ApkModuleXmlDecoder { + public class ApkModuleXmlDecoder { private final ApkModule apkModule; + private final Map> decodedEntries; public ApkModuleXmlDecoder(ApkModule apkModule){ this.apkModule=apkModule; + this.decodedEntries = new HashMap<>(); } public void decodeTo(File outDir) throws IOException, XMLException { + this.decodedEntries.clear(); TableEntryStore entryStore=new TableEntryStore(); entryStore.add(Frameworks.getAndroid()); - entryStore.add(apkModule.getTableBlock()); + TableBlock tableBlock=apkModule.getTableBlock(); + entryStore.add(tableBlock); decodeAndroidManifest(entryStore, outDir); logMessage("Decoding resource files ..."); List resFileList=apkModule.listResFiles(); for(ResFile resFile:resFileList){ decodeResFile(entryStore, outDir, resFile); } + decodeValues(entryStore, outDir, tableBlock); } private void decodeResFile(EntryStore entryStore, File outDir, ResFile resFile) throws IOException, XMLException { @@ -73,6 +87,8 @@ public class ApkModuleXmlDecoder { FileOutputStream outputStream=new FileOutputStream(file); resFile.getInputSource().write(outputStream); outputStream.close(); + + addDecodedEntry(resFile.getEntryBlockList()); } private void decodeResXml(EntryStore entryStore, File outDir, ResFile resFile) throws IOException, XMLException{ @@ -90,6 +106,8 @@ public class ApkModuleXmlDecoder { logVerbose("Decoding: "+path); XMLDocument xmlDocument=resXmlBlock.decodeToXml(entryStore, packageBlock.getId()); xmlDocument.save(file, true); + + addDecodedEntry(resFile.getEntryBlockList()); } private void decodeAndroidManifest(EntryStore entryStore, File outDir) throws IOException, XMLException { @@ -104,6 +122,90 @@ public class ApkModuleXmlDecoder { XMLDocument xmlDocument=manifestBlock.decodeToXml(entryStore, currentPackageId); xmlDocument.save(file, true); } + private void addDecodedEntry(Collection entryBlockList){ + for(EntryBlock entryBlock:entryBlockList){ + addDecodedEntry(entryBlock); + } + } + private void addDecodedEntry(EntryBlock entryBlock){ + if(entryBlock.isNull()){ + return; + } + int resourceId=entryBlock.getResourceId(); + Set resConfigSet=decodedEntries.get(resourceId); + if(resConfigSet==null){ + resConfigSet=new HashSet<>(); + decodedEntries.put(resourceId, resConfigSet); + } + resConfigSet.add(entryBlock.getResConfig()); + } + private boolean containsDecodedEntry(EntryBlock entryBlock){ + Set resConfigSet=decodedEntries.get(entryBlock.getResourceId()); + if(resConfigSet==null){ + return false; + } + return resConfigSet.contains(entryBlock.getResConfig()); + } + private void decodeValues(EntryStore entryStore, File outDir, TableBlock tableBlock) throws IOException { + for(PackageBlock packageBlock:tableBlock.listPackages()){ + decodeValues(entryStore, outDir, packageBlock); + } + } + private void decodeValues(EntryStore entryStore, File outDir, PackageBlock packageBlock) throws IOException { + packageBlock.sortTypes(); + for(SpecTypePair specTypePair: packageBlock.listAllSpecTypePair()){ + for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){ + decodeValues(entryStore, outDir, typeBlock); + } + } + } + private void decodeValues(EntryStore entryStore, File outDir, TypeBlock typeBlock) throws IOException { + XMLDocument xmlDocument = new XMLDocument("resources"); + XMLElement docElement = xmlDocument.getDocumentElement(); + for(EntryBlock entryBlock:typeBlock.listEntries(true)){ + if(containsDecodedEntry(entryBlock)){ + continue; + } + docElement.addChild(decodeValue(entryStore, entryBlock)); + } + if(docElement.getChildesCount()==0){ + return; + } + File file=new File(outDir, typeBlock.getPackageBlock().getName()); + file=new File(file, ApkUtil.RES_DIR_NAME); + file=new File(file, "values"+typeBlock.getQualifiers()); + String type=typeBlock.getTypeName(); + if(!type.endsWith("s")){ + type=type+"s"; + } + file=new File(file, type+".xml"); + xmlDocument.save(file, false); + } + private XMLElement decodeValue(EntryStore entryStore, EntryBlock entryBlock){ + XMLElement element=new XMLElement(entryBlock.getTypeName()); + element.setResourceId(entryBlock.getResourceId()); + if(!entryBlock.isEntryTypeBag()){ + String name=entryBlock.getName(); + String value; + ResValueInt resValueInt=(ResValueInt) entryBlock.getResValue(); + if(resValueInt.getValueType()== ValueType.STRING){ + value=resValueInt.getValueAsString(); + }else { + value= ValueDecoder.decodeEntryValue(entryStore, + entryBlock.getPackageBlock(), + resValueInt.getValueType(), + resValueInt.getData()); + } + XMLAttribute attribute=new XMLAttribute("name", name); + element.addAttribute(attribute); + element.setTextContent(value); + }else { + // TODO: implement bags entry decoder + return null; + } + return element; + } + private void logMessage(String msg) { APKLogger apkLogger=apkModule.getApkLogger(); if(apkLogger!=null){ diff --git a/src/main/java/com/reandroid/lib/apk/ResFile.java b/src/main/java/com/reandroid/lib/apk/ResFile.java index a8c81a3..4787c8c 100644 --- a/src/main/java/com/reandroid/lib/apk/ResFile.java +++ b/src/main/java/com/reandroid/lib/apk/ResFile.java @@ -36,6 +36,9 @@ public class ResFile { this.inputSource=inputSource; this.entryBlockList=entryBlockList; } + public List getEntryBlockList(){ + return entryBlockList; + } public String validateTypeDirectoryName(){ EntryBlock entryBlock=pickOne(); if(entryBlock==null){