From 71437db825b63a3624ff55129ac3cd088fd228ba Mon Sep 17 00:00:00 2001 From: REAndroid Date: Mon, 6 Mar 2023 11:51:50 -0500 Subject: [PATCH] make ApkFile as parent --- .../java/com/reandroid/apk/ApkModule.java | 71 ++++++++++----- .../reandroid/apk/ApkModuleXmlDecoder.java | 6 +- .../java/com/reandroid/apk/FrameworkApk.java | 4 +- src/main/java/com/reandroid/apk/ResFile.java | 13 --- src/main/java/com/reandroid/arsc/ApkFile.java | 28 ++++++ .../com/reandroid/arsc/chunk/MainChunk.java | 5 +- .../com/reandroid/arsc/chunk/TableBlock.java | 89 ++++++++++++++----- .../arsc/chunk/xml/AndroidManifestBlock.java | 8 +- .../arsc/chunk/xml/ResXmlDocument.java | 33 ++++++- 9 files changed, 190 insertions(+), 67 deletions(-) create mode 100644 src/main/java/com/reandroid/arsc/ApkFile.java diff --git a/src/main/java/com/reandroid/apk/ApkModule.java b/src/main/java/com/reandroid/apk/ApkModule.java index b929a00..2a3769a 100644 --- a/src/main/java/com/reandroid/apk/ApkModule.java +++ b/src/main/java/com/reandroid/apk/ApkModule.java @@ -16,6 +16,7 @@ package com.reandroid.apk; import com.reandroid.archive.*; +import com.reandroid.arsc.ApkFile; import com.reandroid.arsc.array.PackageArray; import com.reandroid.arsc.chunk.Chunk; import com.reandroid.arsc.chunk.PackageBlock; @@ -34,12 +35,13 @@ import com.reandroid.xml.XMLDocument; import com.reandroid.xml.XMLException; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.zip.ZipEntry; -public class ApkModule { +public class ApkModule implements ApkFile { private final String moduleName; private final APKArchive apkArchive; private boolean loadDefaultFramework = true; @@ -91,9 +93,7 @@ public class ApkModule { return results; } public XMLDocument decodeXMLFile(String path) throws IOException, XMLException { - InputSource inputSource = apkArchive.getInputSource(path); - ResXmlDocument resXmlDocument = new ResXmlDocument(); - resXmlDocument.readBytes(inputSource.openStream()); + ResXmlDocument resXmlDocument = loadResXmlDocument(path); AndroidManifestBlock manifestBlock = getAndroidManifestBlock(); int pkgId = manifestBlock.guessCurrentPackageId(); return resXmlDocument.decodeToXml(getTableEntryStore(), pkgId); @@ -124,10 +124,10 @@ public class ApkModule { AndroidManifestBlock manifestBlock; try { manifestBlock=getAndroidManifestBlock(); - } catch (IOException ignored) { + return manifestBlock.getMainActivity()!=null; + } catch (Exception ignored) { return false; } - return manifestBlock.getMainActivity()!=null; } public String getModuleName(){ return moduleName; @@ -315,35 +315,61 @@ public class ApkModule { return mManifestBlock!=null || getApkArchive().getInputSource(AndroidManifestBlock.FILE_NAME)!=null; } - public AndroidManifestBlock getAndroidManifestBlock() throws IOException { + public boolean hasTableBlock(){ + return mTableBlock!=null + || getApkArchive().getInputSource(TableBlock.FILE_NAME)!=null; + } + public AndroidManifestBlock getAndroidManifestBlock() { if(mManifestBlock!=null){ return mManifestBlock; } APKArchive archive=getApkArchive(); InputSource inputSource = archive.getInputSource(AndroidManifestBlock.FILE_NAME); if(inputSource==null){ - throw new IOException("Entry not found: "+AndroidManifestBlock.FILE_NAME); + return null; + } + InputStream inputStream = null; + try { + inputStream = inputSource.openStream(); + AndroidManifestBlock manifestBlock=AndroidManifestBlock.load(inputStream); + inputStream.close(); + BlockInputSource blockInputSource=new BlockInputSource<>(inputSource.getName(),manifestBlock); + blockInputSource.setSort(inputSource.getSort()); + blockInputSource.setMethod(inputSource.getMethod()); + archive.add(blockInputSource); + manifestBlock.setApkFile(this); + mManifestBlock = manifestBlock; + } catch (IOException exception) { + throw new IllegalArgumentException(exception); } - InputStream inputStream = inputSource.openStream(); - AndroidManifestBlock manifestBlock=AndroidManifestBlock.load(inputStream); - inputStream.close(); - BlockInputSource blockInputSource=new BlockInputSource<>(inputSource.getName(),manifestBlock); - blockInputSource.setSort(inputSource.getSort()); - blockInputSource.setMethod(inputSource.getMethod()); - archive.add(blockInputSource); - mManifestBlock=manifestBlock; return mManifestBlock; } - public boolean hasTableBlock(){ - return mTableBlock!=null - || getApkArchive().getInputSource(TableBlock.FILE_NAME)!=null; - } - public TableBlock getTableBlock() throws IOException { + @Override + public TableBlock getTableBlock() { if(mTableBlock==null){ - mTableBlock=loadTableBlock(); + if(!hasTableBlock()){ + return null; + } + try { + mTableBlock = loadTableBlock(); + } catch (IOException exception) { + throw new IllegalArgumentException(exception); + } } return mTableBlock; } + @Override + public ResXmlDocument loadResXmlDocument(String path) throws IOException{ + InputSource inputSource = getApkArchive().getInputSource(path); + if(inputSource==null){ + throw new FileNotFoundException("No such file in apk: " + path); + } + ResXmlDocument resXmlDocument = new ResXmlDocument(); + resXmlDocument.setApkFile(this); + resXmlDocument.readBytes(inputSource.openStream()); + return resXmlDocument; + } + // If we need TableStringPool only, this loads pool without // loading packages and other chunk blocks for faster and less memory usage public TableStringPool getVolatileTableStringPool() throws IOException{ @@ -391,6 +417,7 @@ public class ApkModule { blockInputSource.setMethod(inputSource.getMethod()); blockInputSource.setSort(inputSource.getSort()); archive.add(blockInputSource); + tableBlock.setApkFile(this); return tableBlock; } public APKArchive getApkArchive() { diff --git a/src/main/java/com/reandroid/apk/ApkModuleXmlDecoder.java b/src/main/java/com/reandroid/apk/ApkModuleXmlDecoder.java index 9fd1622..010b1ce 100644 --- a/src/main/java/com/reandroid/apk/ApkModuleXmlDecoder.java +++ b/src/main/java/com/reandroid/apk/ApkModuleXmlDecoder.java @@ -124,14 +124,14 @@ import java.util.*; resFile.getInputSource().write(outputStream); outputStream.close(); - addDecodedEntry(resFile.pickOne()); + addDecodedEntry(entry); } private void decodeResXml(EntryStore entryStore, File outDir, ResFile resFile) throws IOException, XMLException{ Entry entry =resFile.pickOne(); PackageBlock packageBlock= entry.getPackageBlock(); - ResXmlDocument resXmlDocument =new ResXmlDocument(); - resXmlDocument.readBytes(resFile.getInputSource().openStream()); + ResXmlDocument resXmlDocument = apkModule.loadResXmlDocument( + resFile.getInputSource().getName()); File pkgDir=new File(outDir, getPackageDirName(packageBlock)); File resDir=new File(pkgDir, ApkUtil.RES_DIR_NAME); diff --git a/src/main/java/com/reandroid/apk/FrameworkApk.java b/src/main/java/com/reandroid/apk/FrameworkApk.java index d47676e..c581267 100644 --- a/src/main/java/com/reandroid/apk/FrameworkApk.java +++ b/src/main/java/com/reandroid/apk/FrameworkApk.java @@ -55,7 +55,8 @@ public class FrameworkApk extends ApkModule{ archive.add(tableSource); archive.add(manifestSource); } - public FrameworkTable getTableBlock() throws IOException { + @Override + public FrameworkTable getTableBlock() { return (FrameworkTable) super.getTableBlock(); } @Override @@ -67,6 +68,7 @@ public class FrameworkApk extends ApkModule{ } InputStream inputStream = inputSource.openStream(); FrameworkTable frameworkTable=FrameworkTable.load(inputStream); + frameworkTable.setApkFile(this); if(hasAndroidManifestBlock()){ optimizeTable(frameworkTable); } diff --git a/src/main/java/com/reandroid/apk/ResFile.java b/src/main/java/com/reandroid/apk/ResFile.java index ae4b05f..71e555f 100644 --- a/src/main/java/com/reandroid/apk/ResFile.java +++ b/src/main/java/com/reandroid/apk/ResFile.java @@ -129,19 +129,6 @@ public class ResFile { } return mBinXml; } - public boolean dumpToJson(File rootDir) throws IOException { - if(!isBinaryXml()){ - return false; - } - String fileName=getFilePath()+ApkUtil.JSON_FILE_EXTENSION; - fileName=fileName.replace('/', File.separatorChar); - File file=new File(rootDir, fileName); - ResXmlDocument resXmlDocument =new ResXmlDocument(); - resXmlDocument.readBytes(getInputSource().openStream()); - JSONObject jsonObject= resXmlDocument.toJson(); - jsonObject.write(file); - return true; - } public File buildOutFile(File dir){ String path=getFilePath(); path=path.replace('/', File.separatorChar); diff --git a/src/main/java/com/reandroid/arsc/ApkFile.java b/src/main/java/com/reandroid/arsc/ApkFile.java new file mode 100644 index 0000000..eacdbc9 --- /dev/null +++ b/src/main/java/com/reandroid/arsc/ApkFile.java @@ -0,0 +1,28 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.arsc; + +import com.reandroid.arsc.chunk.TableBlock; +import com.reandroid.arsc.chunk.xml.AndroidManifestBlock; +import com.reandroid.arsc.chunk.xml.ResXmlDocument; + +import java.io.IOException; + +public interface ApkFile { + AndroidManifestBlock getAndroidManifestBlock(); + TableBlock getTableBlock(); + ResXmlDocument loadResXmlDocument(String path) throws IOException; +} diff --git a/src/main/java/com/reandroid/arsc/chunk/MainChunk.java b/src/main/java/com/reandroid/arsc/chunk/MainChunk.java index d9700c2..4d26f69 100644 --- a/src/main/java/com/reandroid/arsc/chunk/MainChunk.java +++ b/src/main/java/com/reandroid/arsc/chunk/MainChunk.java @@ -1,7 +1,10 @@ package com.reandroid.arsc.chunk; +import com.reandroid.arsc.ApkFile; import com.reandroid.arsc.pool.StringPool; public interface MainChunk { - public StringPool getStringPool(); + StringPool getStringPool(); + ApkFile getApkFile(); + void setApkFile(ApkFile apkFile); } diff --git a/src/main/java/com/reandroid/arsc/chunk/TableBlock.java b/src/main/java/com/reandroid/arsc/chunk/TableBlock.java index cc69ce3..9b0027f 100755 --- a/src/main/java/com/reandroid/arsc/chunk/TableBlock.java +++ b/src/main/java/com/reandroid/arsc/chunk/TableBlock.java @@ -15,6 +15,7 @@ */ package com.reandroid.arsc.chunk; +import com.reandroid.arsc.ApkFile; import com.reandroid.arsc.BuildInfo; import com.reandroid.arsc.array.PackageArray; import com.reandroid.arsc.group.EntryGroup; @@ -22,24 +23,23 @@ import com.reandroid.arsc.header.HeaderBlock; import com.reandroid.arsc.header.InfoHeader; import com.reandroid.arsc.header.TableHeader; import com.reandroid.arsc.io.BlockReader; -import com.reandroid.arsc.pool.StringPool; import com.reandroid.arsc.pool.TableStringPool; import com.reandroid.arsc.value.StagedAliasEntry; +import com.reandroid.common.EntryStore; import com.reandroid.common.Frameworks; import com.reandroid.json.JSONConvert; import com.reandroid.json.JSONArray; import com.reandroid.json.JSONObject; import java.io.*; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.*; -public class TableBlock extends Chunk - implements MainChunk, JSONConvert { + public class TableBlock extends Chunk + implements MainChunk, JSONConvert, EntryStore { private final TableStringPool mTableStringPool; private final PackageArray mPackageArray; private final Set mFrameWorks=new HashSet<>(); + private ApkFile mApkFile; public TableBlock() { super(new TableHeader(), 2); TableHeader header = getHeaderBlock(); @@ -61,6 +61,14 @@ public class TableBlock extends Chunk public TableStringPool getStringPool() { return mTableStringPool; } + @Override + public ApkFile getApkFile(){ + return mApkFile; + } + @Override + public void setApkFile(ApkFile apkFile){ + this.mApkFile = apkFile; + } public TableStringPool getTableStringPool(){ return mTableStringPool; } @@ -132,16 +140,23 @@ public class TableBlock extends Chunk return length; } - @Override - public String toString(){ - StringBuilder builder=new StringBuilder(); - builder.append(super.toString()); - builder.append(", packages="); - int pkgCount=mPackageArray.childesCount(); - builder.append(pkgCount); - return builder.toString(); - } public EntryGroup search(int resourceId){ + if(resourceId==0){ + return null; + } + EntryGroup entryGroup = searchLocal(resourceId); + if(entryGroup!=null){ + return entryGroup; + } + for(TableBlock tableBlock:getFrameWorks()){ + entryGroup = tableBlock.search(resourceId); + if(entryGroup!=null){ + return entryGroup; + } + } + return null; + } + private EntryGroup searchLocal(int resourceId){ if(resourceId==0){ return null; } @@ -156,14 +171,36 @@ public class TableBlock extends Chunk return entryGroup; } } - for(TableBlock tableBlock:getFrameWorks()){ - EntryGroup entryGroup = tableBlock.search(resourceId); - if(entryGroup!=null){ - return entryGroup; - } - } return null; } + @Override + public Collection getEntryGroups(int resourceId) { + List results = new ArrayList<>(); + EntryGroup entryGroup = searchLocal(resourceId); + if(entryGroup!=null){ + results.add(entryGroup); + } + for(TableBlock framework:getFrameWorks()){ + results.addAll(framework.getEntryGroups(resourceId)); + } + return results; + } + @Override + public EntryGroup getEntryGroup(int resourceId) { + return search(resourceId); + } + @Override + public Collection getPackageBlocks(int packageId) { + List results=new ArrayList<>(); + PackageBlock packageBlock = getPackageBlockById(packageId); + if(packageBlock!=null){ + results.add(packageBlock); + } + for(TableBlock tableBlock:getFrameWorks()){ + results.addAll(tableBlock.getPackageBlocks(packageId)); + } + return results; + } public int searchResourceIdAlias(int resourceId){ for(PackageBlock packageBlock:listPackages()){ StagedAliasEntry stagedAliasEntry = @@ -216,6 +253,16 @@ public class TableBlock extends Chunk getPackageArray().merge(tableBlock.getPackageArray()); refresh(); } + @Override + public String toString(){ + StringBuilder builder=new StringBuilder(); + builder.append(super.toString()); + builder.append(", packages="); + int pkgCount=mPackageArray.childesCount(); + builder.append(pkgCount); + return builder.toString(); + } + public static TableBlock loadWithAndroidFramework(InputStream inputStream) throws IOException{ TableBlock tableBlock=load(inputStream); tableBlock.addFramework(Frameworks.getAndroid()); diff --git a/src/main/java/com/reandroid/arsc/chunk/xml/AndroidManifestBlock.java b/src/main/java/com/reandroid/arsc/chunk/xml/AndroidManifestBlock.java index 1515646..4994bba 100644 --- a/src/main/java/com/reandroid/arsc/chunk/xml/AndroidManifestBlock.java +++ b/src/main/java/com/reandroid/arsc/chunk/xml/AndroidManifestBlock.java @@ -25,13 +25,17 @@ import java.util.ArrayList; import java.util.List; public class AndroidManifestBlock extends ResXmlDocument { + private int mGuessedPackageId; public AndroidManifestBlock(){ super(); super.getStringPool().setUtf8(false); } // TODO: find a better way - public byte guessCurrentPackageId(){ - return (byte) ((getIconResourceId()>>24) & 0xff); + public int guessCurrentPackageId(){ + if(mGuessedPackageId == 0){ + mGuessedPackageId = ((getIconResourceId()>>24) & 0xff); + } + return mGuessedPackageId; } public int getIconResourceId(){ ResXmlElement applicationElement = getApplicationElement(); diff --git a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlDocument.java b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlDocument.java index b7c7836..fc8675f 100755 --- a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlDocument.java +++ b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlDocument.java @@ -15,10 +15,8 @@ */ package com.reandroid.arsc.chunk.xml; - import com.reandroid.arsc.chunk.Chunk; - import com.reandroid.arsc.chunk.ChunkType; - import com.reandroid.arsc.chunk.MainChunk; - import com.reandroid.arsc.chunk.ParentChunk; + import com.reandroid.arsc.ApkFile; + import com.reandroid.arsc.chunk.*; import com.reandroid.arsc.container.SingleBlockContainer; import com.reandroid.arsc.header.HeaderBlock; import com.reandroid.arsc.io.BlockReader; @@ -45,6 +43,7 @@ private final ResXmlIDMap mResXmlIDMap; private ResXmlElement mResXmlElement; private final SingleBlockContainer mResXmlElementContainer; + private ApkFile mApkFile; public ResXmlDocument() { super(new HeaderBlock(ChunkType.XML),3); this.mResXmlStringPool=new ResXmlStringPool(true); @@ -169,6 +168,14 @@ return mResXmlStringPool; } @Override + public ApkFile getApkFile(){ + return mApkFile; + } + @Override + public void setApkFile(ApkFile apkFile){ + this.mApkFile = apkFile; + } + @Override public StringPool getSpecStringPool() { return null; } @@ -224,6 +231,24 @@ xmlElement.fromJson(json.optJSONObject(ResXmlDocument.NAME_element)); refresh(); } + public XMLDocument decodeToXml() throws XMLException { + ApkFile apkFile = getApkFile(); + if(apkFile == null){ + throw new XMLException("Null parent apk file"); + } + int currentPackageId = 0; + AndroidManifestBlock manifestBlock; + if(this instanceof AndroidManifestBlock){ + manifestBlock = ((AndroidManifestBlock)this); + }else { + manifestBlock = apkFile.getAndroidManifestBlock(); + } + if(manifestBlock!=null){ + currentPackageId = manifestBlock.guessCurrentPackageId(); + } + TableBlock tableBlock = apkFile.getTableBlock(); + return decodeToXml(tableBlock, currentPackageId); + } public XMLDocument decodeToXml(EntryStore entryStore, int currentPackageId) throws XMLException { XMLDocument xmlDocument = new XMLDocument(); XMLElement xmlElement = getResXmlElement()