diff --git a/README.md b/README.md index a917514..74bfcde 100755 --- a/README.md +++ b/README.md @@ -5,59 +5,27 @@ import com.reandroid.lib.arsc.chunk.TableBlock; import com.reandroid.lib.arsc.io.BlockReader; - public static void example() throws IOException { - File inFile=new File("resources.arsc"); - - TableBlock tableBlock=new TableBlock(); - tableBlock.readBytes(inFile); - - //edit tableBlock as desired, for example to change the package: - PackageBlock packageBlock=tableBlock.getPackageArray().get(0); - packageBlock.setPackageName("com.new.package.name"); - - //refresh to recalculate offsets - tableBlock.refresh(); - - //convert to json object - JSONObject jsonObject=tableBlock.toJson(); - System.out.println(jsonObject.toString(4)); - - //save the edited table - File outFile=new File("resources_out.arsc"); - tableBlock.writeBytes(outFile); - } - - public static void exampleManifest() throws IOException { - File inFile=new File("AndroidManifest.xml"); - - AndroidManifestBlock manifestBlock=new AndroidManifestBlock(); - manifestBlock.readBytes(file); - - List permissionNames = manifestBlock.getUsesPermissions(); - for(String perm:permissionNames){ - System.out.println(perm); - } - - //edit AndroidManifest as desired, for example to change the package: - - manifestBlock.setPackageName("com.new.package.name"); - - // add some permission - - manifestBlock.addUsesPermission("android.permission.WRITE_EXTERNAL_STORAGE"); - - //refresh to recalculate offsets - manifestBlock.refresh(); - - //save the edited xml - File outFile=new File("AndroidManifest_out.xml"); - manifestBlock.writeBytes(outFile); - } - public static void convertToJson() throws IOException{ + public static void example() throws IOException{ File inFile=new File("test.apk"); File outDir=new File("test_out"); + ApkModule apkModule=ApkModule.loadApkFile(inFile); - apkModule.convertToJson(outDir); - } + + ApkJsonDecoder decoder=new ApkJsonDecoder(apkModule); + outDir=decoder.writeToDirectory(outDir); + System.out.println("Decoded to: "+outDir); + + // You can do any logical modification on any json files + + // To convert back json to apk + + ApkJsonEncoder encoder=new ApkJsonEncoder(); + ApkModule encodedModule=encoder.scanDirectory(outDir); + + File outApk=new File("test_out_re-encoded.apk"); + encodedModule.writeApk(outApk); + + System.out.println("Created apk: "+outApk); + } ``` diff --git a/libs/ArchiveUtil.jar b/libs/ArchiveUtil.jar index 44b6cd7..3cf1d71 100644 Binary files a/libs/ArchiveUtil.jar and b/libs/ArchiveUtil.jar differ diff --git a/src/main/java/com/reandroid/lib/apk/ApkEntry.java b/src/main/java/com/reandroid/lib/apk/ApkEntry.java deleted file mode 100644 index 7054a3e..0000000 --- a/src/main/java/com/reandroid/lib/apk/ApkEntry.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.reandroid.lib.apk; - -import com.reandroid.archive.InputSource; - -public class ApkEntry { - private InputSource mInputSource; - public ApkEntry(InputSource inputSource){ - this.mInputSource=inputSource; - } -} diff --git a/src/main/java/com/reandroid/lib/apk/ApkJsonDecoder.java b/src/main/java/com/reandroid/lib/apk/ApkJsonDecoder.java new file mode 100644 index 0000000..d00bf49 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/ApkJsonDecoder.java @@ -0,0 +1,171 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.InputSource; +import com.reandroid.lib.arsc.chunk.TableBlock; +import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock; +import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock; +import com.reandroid.lib.json.JSONObject; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +public class ApkJsonDecoder { + private final ApkModule apkModule; + private final Set decodedPaths; + private final boolean splitTypes; + public ApkJsonDecoder(ApkModule apkModule, boolean splitTypes){ + this.apkModule = apkModule; + this.splitTypes = splitTypes; + this.decodedPaths = new HashSet<>(); + } + public ApkJsonDecoder(ApkModule apkModule){ + this(apkModule, false); + } + public File writeToDirectory(File dir) throws IOException { + this.decodedPaths.clear(); + writeUncompressed(dir); + writeManifest(dir); + writeTable(dir); + writeResourceIds(dir); + writeResources(dir); + writeRootFiles(dir); + return new File(dir, apkModule.getModuleName()); + } + private void writeUncompressed(File dir) throws IOException { + File file=toUncompressedJsonFile(dir); + UncompressedFiles uncompressedFiles=new UncompressedFiles(); + uncompressedFiles.add(apkModule.getApkArchive()); + uncompressedFiles.toJson().write(file); + } + private void writeResources(File dir) throws IOException { + for(ResFile resFile:apkModule.listResFiles()){ + writeResource(dir, resFile); + } + } + private void writeResource(File dir, ResFile resFile) throws IOException { + if(resFile.isBinaryXml()){ + writeResourceJson(dir, resFile); + } + } + private void writeResourceJson(File dir, ResFile resFile) throws IOException { + InputSource inputSource= resFile.getInputSource(); + String path=inputSource.getAlias(); + File file=toResJson(dir, path); + ResXmlBlock resXmlBlock=new ResXmlBlock(); + resXmlBlock.readBytes(inputSource.openStream()); + JSONObject jsonObject=resXmlBlock.toJson(); + jsonObject.write(file); + addDecoded(path); + } + private void writeRootFiles(File dir) throws IOException { + for(InputSource inputSource:apkModule.getApkArchive().listInputSources()){ + writeRootFile(dir, inputSource); + } + } + private void writeRootFile(File dir, InputSource inputSource) throws IOException { + String path=inputSource.getAlias(); + if(hasDecoded(path)){ + return; + } + File file=toRootFile(dir, path); + File parent=file.getParentFile(); + if(parent!=null && !parent.exists()){ + parent.mkdirs(); + } + FileOutputStream outputStream=new FileOutputStream(file); + inputSource.write(outputStream); + outputStream.close(); + addDecoded(path); + } + private void writeTable(File dir) throws IOException { + if(!splitTypes){ + writeTableSingle(dir); + return; + } + writeTableSplit(dir); + } + private void writeTableSplit(File dir) throws IOException { + if(!apkModule.hasTableBlock()){ + return; + } + TableBlock tableBlock = apkModule.getTableBlock(); + File splitDir= toJsonTableSplitDir(dir); + TableBlockJson tableBlockJson=new TableBlockJson(tableBlock); + tableBlockJson.writeJsonFiles(splitDir); + addDecoded(TableBlock.FILE_NAME); + } + private void writeTableSingle(File dir) throws IOException { + if(!apkModule.hasTableBlock()){ + return; + } + TableBlock tableBlock = apkModule.getTableBlock(); + File file= toJsonTableFile(dir); + tableBlock.toJson().write(file); + addDecoded(TableBlock.FILE_NAME); + } + private void writeResourceIds(File dir) throws IOException { + if(!apkModule.hasTableBlock()){ + return; + } + TableBlock tableBlock = apkModule.getTableBlock(); + ResourceIds resourceIds=new ResourceIds(); + resourceIds.loadTableBlock(tableBlock); + JSONObject jsonObject= resourceIds.toJson(); + File file=toResourceIds(dir); + jsonObject.write(file); + } + private void writeManifest(File dir) throws IOException { + if(!apkModule.hasAndroidManifestBlock()){ + return; + } + AndroidManifestBlock manifestBlock = apkModule.getAndroidManifestBlock(); + File file = toJsonManifestFile(dir); + manifestBlock.toJson().write(file); + addDecoded(AndroidManifestBlock.FILE_NAME); + } + private boolean hasDecoded(String path){ + return decodedPaths.contains(path); + } + private void addDecoded(String path){ + this.decodedPaths.add(path); + } + private File toJsonTableFile(File dir){ + File file=new File(dir, apkModule.getModuleName()); + String name = TableBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION; + return new File(file, name); + } + private File toJsonTableSplitDir(File dir){ + File file=new File(dir, apkModule.getModuleName()); + return new File(file, ApkUtil.SPLIT_JSON_DIRECTORY); + } + private File toResourceIds(File dir){ + File file=new File(dir, apkModule.getModuleName()); + String name = ResourceIds.JSON_FILE_NAME; + return new File(file, name); + } + private File toUncompressedJsonFile(File dir){ + File file = new File(dir, apkModule.getModuleName()); + return new File(file, UncompressedFiles.JSON_FILE); + } + private File toJsonManifestFile(File dir){ + File file=new File(dir, apkModule.getModuleName()); + String name = AndroidManifestBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION; + return new File(file, name); + } + private File toResJson(File dir, String path){ + File file=new File(dir, apkModule.getModuleName()); + file=new File(file, ApkUtil.RES_JSON_NAME); + path=path + ApkUtil.JSON_FILE_EXTENSION; + path=path.replace('/', File.separatorChar); + return new File(file, path); + } + private File toRootFile(File dir, String path){ + File file=new File(dir, apkModule.getModuleName()); + file=new File(file, ApkUtil.ROOT_NAME); + path=path.replace('/', File.separatorChar); + return new File(file, path); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/ApkJsonEncoder.java b/src/main/java/com/reandroid/lib/apk/ApkJsonEncoder.java new file mode 100644 index 0000000..23f8157 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/ApkJsonEncoder.java @@ -0,0 +1,110 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.APKArchive; +import com.reandroid.archive.FileInputSource; +import com.reandroid.lib.arsc.chunk.TableBlock; +import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class ApkJsonEncoder { + private APKArchive apkArchive; + public ApkJsonEncoder(){ + } + public ApkModule scanDirectory(File moduleDir){ + this.apkArchive=new APKArchive(); + String moduleName=moduleDir.getName(); + scanManifest(moduleDir); + scanTable(moduleDir); + scanResJsonDirs(moduleDir); + scanRootDirs(moduleDir); + ApkModule module=new ApkModule(moduleName, apkArchive); + loadUncompressed(module, moduleDir); + return module; + } + private void loadUncompressed(ApkModule module, File moduleDir){ + File jsonFile=toUncompressedJsonFile(moduleDir); + UncompressedFiles uf= module.getUncompressedFiles(); + try { + uf.fromJson(jsonFile); + } catch (IOException ignored) { + } + } + private void scanRootDirs(File moduleDir){ + File rootDir=toRootDir(moduleDir); + List jsonFileList=ApkUtil.recursiveFiles(rootDir); + for(File file:jsonFileList){ + scanRootFile(rootDir, file); + } + } + private void scanRootFile(File rootDir, File file){ + String path=ApkUtil.toArchivePath(rootDir, file); + FileInputSource inputSource=new FileInputSource(file, path); + apkArchive.add(inputSource); + } + private void scanResJsonDirs(File moduleDir){ + File resJsonDir=toResJsonDir(moduleDir); + List jsonFileList=ApkUtil.recursiveFiles(resJsonDir); + for(File file:jsonFileList){ + scanResJsonFile(resJsonDir, file); + } + } + private void scanResJsonFile(File resJsonDir, File file){ + JsonXmlInputSource inputSource=JsonXmlInputSource.fromFile(resJsonDir, file); + apkArchive.add(inputSource); + } + private void scanManifest(File moduleDir){ + File file=toJsonManifestFile(moduleDir); + if(!file.isFile()){ + return; + } + JsonManifestInputSource inputSource=JsonManifestInputSource.fromFile(moduleDir, file); + apkArchive.add(inputSource); + } + private void scanTable(File moduleDir) { + boolean splitFound=scanTableSplitJson(moduleDir); + if(splitFound){ + return; + } + scanTableSingleJson(moduleDir); + } + private boolean scanTableSplitJson(File moduleDir) { + File dir=toJsonTableSplitDir(moduleDir); + if(!dir.isDirectory()){ + return false; + } + SplitJsonTableInputSource inputSource=new SplitJsonTableInputSource(dir); + apkArchive.add(inputSource); + return true; + } + private void scanTableSingleJson(File moduleDir) { + File file=toJsonTableFile(moduleDir); + if(!file.isFile()){ + return; + } + SingleJsonTableInputSource inputSource= SingleJsonTableInputSource.fromFile(moduleDir, file); + apkArchive.add(inputSource); + } + private File toJsonTableFile(File dir){ + String name = TableBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION; + return new File(dir, name); + } + private File toJsonManifestFile(File dir){ + String name = AndroidManifestBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION; + return new File(dir, name); + } + private File toUncompressedJsonFile(File dir){ + return new File(dir, UncompressedFiles.JSON_FILE); + } + private File toJsonTableSplitDir(File dir){ + return new File(dir, ApkUtil.SPLIT_JSON_DIRECTORY); + } + private File toResJsonDir(File dir){ + return new File(dir, ApkUtil.RES_JSON_NAME); + } + private File toRootDir(File dir){ + return new File(dir, ApkUtil.ROOT_NAME); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/ApkModule.java b/src/main/java/com/reandroid/lib/apk/ApkModule.java index 20a6725..e05f6ab 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkModule.java +++ b/src/main/java/com/reandroid/lib/apk/ApkModule.java @@ -2,6 +2,8 @@ package com.reandroid.lib.apk; import com.reandroid.archive.APKArchive; import com.reandroid.archive.InputSource; +import com.reandroid.archive.ZipArchive; +import com.reandroid.archive.ZipSerializer; import com.reandroid.lib.arsc.array.PackageArray; import com.reandroid.lib.arsc.chunk.PackageBlock; import com.reandroid.lib.arsc.chunk.TableBlock; @@ -12,22 +14,36 @@ import com.reandroid.lib.arsc.pool.TableStringPool; import com.reandroid.lib.arsc.value.EntryBlock; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; public class ApkModule { + private final String moduleName; private final APKArchive apkArchive; private boolean loadDefaultFramework = true; private TableBlock mTableBlock; private AndroidManifestBlock mManifestBlock; - private ApkModule(APKArchive apkArchive){ + private final UncompressedFiles mUncompressedFiles; + ApkModule(String moduleName, APKArchive apkArchive){ + this.moduleName=moduleName; this.apkArchive=apkArchive; + this.mUncompressedFiles=new UncompressedFiles(); + this.mUncompressedFiles.add(apkArchive); } - public void writeTo(File file) throws IOException { - APKArchive archive=getApkArchive(); - archive.writeApk(file); + public String getModuleName(){ + return moduleName; + } + public void writeApk(File file) throws IOException { + ZipArchive archive=new ZipArchive(); + archive.addAll(getApkArchive().listInputSources()); + UncompressedFiles uf=getUncompressedFiles(); + uf.apply(archive); + ZipSerializer serializer=new ZipSerializer(archive.listInputSources(), false); + serializer.writeZip(file); + } + public UncompressedFiles getUncompressedFiles(){ + return mUncompressedFiles; } public void removeDir(String dirName){ getApkArchive().removeDir(dirName); @@ -164,48 +180,12 @@ public class ApkModule { public void setLoadDefaultFramework(boolean loadDefaultFramework) { this.loadDefaultFramework = loadDefaultFramework; } - public void convertToJson(File outDir) throws IOException { - Set convertedFiles=new HashSet<>(); - if(hasAndroidManifestBlock()){ - AndroidManifestBlock manifestBlock=getAndroidManifestBlock(); - String fileName=AndroidManifestBlock.FILE_NAME+ApkUtil.JSON_FILE_EXTENSION; - File file=new File(outDir, fileName); - manifestBlock.toJson().write(file); - convertedFiles.add(AndroidManifestBlock.FILE_NAME); - } - if(hasTableBlock()){ - TableBlock tableBlock=getTableBlock(); - String fileName=TableBlock.FILE_NAME+ApkUtil.JSON_FILE_EXTENSION; - File file=new File(outDir, fileName); - tableBlock.toJson().write(file); - convertedFiles.add(TableBlock.FILE_NAME); - } - List resFileList=listResFiles(); - for(ResFile resFile:resFileList){ - boolean convertOk=resFile.dumpToJson(outDir); - if(convertOk){ - convertedFiles.add(resFile.getFilePath()); - } - } - List allSources = getApkArchive().listInputSources(); - for(InputSource inputSource:allSources){ - String path=inputSource.getAlias(); - if(convertedFiles.contains(path)){ - continue; - } - path=path.replace('/', File.separatorChar); - File file=new File(outDir, path); - File dir=file.getParentFile(); - if(dir!=null && !dir.exists()){ - dir.mkdirs(); - } - FileOutputStream outputStream=new FileOutputStream(file); - inputSource.write(outputStream); - outputStream.close(); - } - } + public static ApkModule loadApkFile(File apkFile) throws IOException { + return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME); + } + public static ApkModule loadApkFile(File apkFile, String moduleName) throws IOException { APKArchive archive=APKArchive.loadZippedApk(apkFile); - return new ApkModule(archive); + return new ApkModule(moduleName, archive); } } diff --git a/src/main/java/com/reandroid/lib/apk/ApkUtil.java b/src/main/java/com/reandroid/lib/apk/ApkUtil.java index 90605de..3af579a 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkUtil.java +++ b/src/main/java/com/reandroid/lib/apk/ApkUtil.java @@ -1,5 +1,9 @@ package com.reandroid.lib.apk; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + public class ApkUtil { public static String replaceRootDir(String path, String dirName){ int i=path.indexOf('/')+1; @@ -12,5 +16,100 @@ public class ApkUtil { } return path; } - public static final String JSON_FILE_EXTENSION=".a.json"; + public static String toArchiveResourcePath(File dir, File file){ + String path = toArchivePath(dir, file); + if(path.endsWith(ApkUtil.JSON_FILE_EXTENSION)){ + int i2=path.length()-ApkUtil.JSON_FILE_EXTENSION.length(); + path=path.substring(0, i2); + } + return path; + } + public static String toArchivePath(File dir, File file){ + String dirPath = dir.getAbsolutePath()+File.separator; + String path = file.getAbsolutePath().substring(dirPath.length()); + path=path.replace(File.separatorChar, '/'); + return path; + } + public static List recursiveFiles(File dir, String ext){ + List results=new ArrayList<>(); + if(dir.isFile()){ + results.add(dir); + return results; + } + if(!dir.isDirectory()){ + return results; + } + File[] files=dir.listFiles(); + if(files==null){ + return results; + } + for(File file:files){ + if(file.isFile()){ + if(ext!=null && !file.getName().endsWith(ext)){ + continue; + } + results.add(file); + continue; + } + results.addAll(recursiveFiles(file)); + } + return results; + } + public static List recursiveFiles(File dir){ + List results=new ArrayList<>(); + if(dir.isFile()){ + results.add(dir); + return results; + } + if(!dir.isDirectory()){ + return results; + } + File[] files=dir.listFiles(); + if(files==null){ + return results; + } + for(File file:files){ + if(file.isFile()){ + results.add(file); + continue; + } + results.addAll(recursiveFiles(file)); + } + return results; + } + public static List listDirectories(File dir){ + List results=new ArrayList<>(); + File[] files=dir.listFiles(); + if(files==null){ + return results; + } + for(File file:files){ + if(file.isDirectory()){ + results.add(file); + } + } + return results; + } + public static List listFiles(File dir, String ext){ + List results=new ArrayList<>(); + File[] files=dir.listFiles(); + if(files==null){ + return results; + } + for(File file:files){ + if(file.isFile()){ + if(ext!=null && !file.getName().endsWith(ext)){ + continue; + } + results.add(file); + } + } + return results; + } + public static final String JSON_FILE_EXTENSION=".json"; + public static final String RES_JSON_NAME="res-json"; + public static final String ROOT_NAME="root"; + public static final String PACKAGE_JSON_FILE="package.json"; + public static final String SPLIT_JSON_DIRECTORY="resources"; + public static final String DEF_MODULE_NAME="base"; } diff --git a/src/main/java/com/reandroid/lib/apk/BlockInputSource.java b/src/main/java/com/reandroid/lib/apk/BlockInputSource.java index 9c5010a..84e80ab 100644 --- a/src/main/java/com/reandroid/lib/apk/BlockInputSource.java +++ b/src/main/java/com/reandroid/lib/apk/BlockInputSource.java @@ -1,24 +1,37 @@ package com.reandroid.lib.apk; +import com.reandroid.archive.ByteInputSource; import com.reandroid.archive.InputSource; +import com.reandroid.lib.arsc.base.Block; import com.reandroid.lib.arsc.chunk.BaseChunk; +import com.reandroid.lib.arsc.chunk.TableBlock; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; -public class BlockInputSource extends InputSource { +public class BlockInputSource extends ByteInputSource{ private final T mBlock; public BlockInputSource(String name, T block) { - super(name); + super(new byte[0], name); this.mBlock=block; } public T getBlock() { + mBlock.refresh(); return mBlock; } @Override - public InputStream openStream(){ - T block=getBlock(); - block.refresh(); - return new ByteArrayInputStream(block.getBytes()); + public long getLength() throws IOException{ + Block block = getBlock(); + return block.countBytes(); + } + @Override + public long write(OutputStream outputStream) throws IOException { + return getBlock().writeBytes(outputStream); + } + @Override + public byte[] getBytes() { + return getBlock().getBytes(); } } diff --git a/src/main/java/com/reandroid/lib/apk/JsonManifestInputSource.java b/src/main/java/com/reandroid/lib/apk/JsonManifestInputSource.java new file mode 100644 index 0000000..96e44c4 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/JsonManifestInputSource.java @@ -0,0 +1,21 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.FileInputSource; +import com.reandroid.archive.InputSource; +import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock; + +import java.io.File; + +public class JsonManifestInputSource extends JsonXmlInputSource { + public JsonManifestInputSource(InputSource inputSource) { + super(inputSource); + } + AndroidManifestBlock newInstance(){ + return new AndroidManifestBlock(); + } + public static JsonManifestInputSource fromFile(File rootDir, File jsonFile){ + String path=ApkUtil.toArchiveResourcePath(rootDir, jsonFile); + FileInputSource fileInputSource=new FileInputSource(jsonFile, path); + return new JsonManifestInputSource(fileInputSource); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/JsonXmlInputSource.java b/src/main/java/com/reandroid/lib/apk/JsonXmlInputSource.java new file mode 100644 index 0000000..68660bb --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/JsonXmlInputSource.java @@ -0,0 +1,47 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.FileInputSource; +import com.reandroid.archive.InputSource; +import com.reandroid.lib.arsc.base.Block; +import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock; +import com.reandroid.lib.json.JSONObject; + +import java.io.*; + +public class JsonXmlInputSource extends InputSource { + private final InputSource inputSource; + public JsonXmlInputSource(InputSource inputSource) { + super(inputSource.getAlias()); + this.inputSource=inputSource; + } + @Override + public long write(OutputStream outputStream) throws IOException { + return getResXmlBlock().writeBytes(outputStream); + } + @Override + public InputStream openStream() throws IOException { + ResXmlBlock resXmlBlock= getResXmlBlock(); + return new ByteArrayInputStream(resXmlBlock.getBytes()); + } + @Override + public long getLength() throws IOException{ + ResXmlBlock resXmlBlock = getResXmlBlock(); + return resXmlBlock.countBytes(); + } + private ResXmlBlock getResXmlBlock() throws IOException{ + ResXmlBlock resXmlBlock=newInstance(); + InputStream inputStream=inputSource.openStream(); + JSONObject jsonObject=new JSONObject(inputStream); + resXmlBlock.fromJson(jsonObject); + inputStream.close(); + return resXmlBlock; + } + ResXmlBlock newInstance(){ + return new ResXmlBlock(); + } + public static JsonXmlInputSource fromFile(File rootDir, File jsonFile){ + String path=ApkUtil.toArchiveResourcePath(rootDir, jsonFile); + FileInputSource fileInputSource=new FileInputSource(jsonFile, path); + return new JsonXmlInputSource(fileInputSource); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/ResourceIds.java b/src/main/java/com/reandroid/lib/apk/ResourceIds.java new file mode 100644 index 0000000..15c673d --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/ResourceIds.java @@ -0,0 +1,472 @@ +package com.reandroid.lib.apk; + + +import com.reandroid.lib.arsc.chunk.PackageBlock; +import com.reandroid.lib.arsc.chunk.TableBlock; +import com.reandroid.lib.arsc.group.EntryGroup; +import com.reandroid.lib.json.JSONArray; +import com.reandroid.lib.json.JSONObject; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ResourceIds { + private final Table mTable; + public ResourceIds(Table table){ + this.mTable=table; + } + public ResourceIds(){ + this(new Table()); + } + public JSONObject toJson(){ + return mTable.toJson(); + } + public void loadTableBlock(TableBlock tableBlock){ + for(PackageBlock packageBlock:tableBlock.listPackages()){ + loadPackageBlock(packageBlock); + } + } + public void loadPackageBlock(PackageBlock packageBlock){ + Collection entryGroupList = packageBlock.listEntryGroup(); + String name= packageBlock.getName(); + for(EntryGroup entryGroup:entryGroupList){ + Table.Package.Type.Entry entry= Table.Package.Type.Entry.fromEntryGroup(entryGroup); + mTable.add(entry); + if(name==null){ + continue; + } + Table.Package.Type type=entry.type; + if(type!=null && type.mPackage!=null){ + type.mPackage.name=name; + name=null; + } + } + } + + public static class Table{ + public final Map packageMap; + public Table(){ + this.packageMap = new HashMap<>(); + } + public void add(Package pkg){ + Package exist=this.packageMap.get(pkg.id); + if(exist!=null){ + exist.merge(pkg); + return; + } + this.packageMap.put(pkg.id, pkg); + } + public void add(Package.Type.Entry entry){ + if(entry==null){ + return; + } + byte pkgId=entry.getPackageId(); + Package pkg = packageMap.get(pkgId); + if(pkg==null){ + pkg=new Package(pkgId); + packageMap.put(pkgId, pkg); + } + pkg.add(entry); + } + public Package.Type.Entry getEntry(int resourceId){ + byte packageId = (byte) ((resourceId>>24) & 0xff); + byte typeId = (byte) ((resourceId>>16) & 0xff); + short entryId = (short) (resourceId & 0xff); + Package pkg = getPackage(packageId); + if(pkg == null){ + return null; + } + return getEntry(packageId, typeId, entryId); + } + public Package getPackage(byte packageId){ + return packageMap.get(packageId); + } + public Package.Type getType(byte packageId, byte typeId){ + Package pkg=getPackage(packageId); + if(pkg==null){ + return null; + } + return pkg.getType(typeId); + } + public Package.Type.Entry getEntry(byte packageId, byte typeId, short entryId){ + Package pkg=getPackage(packageId); + if(pkg==null){ + return null; + } + return pkg.getEntry(typeId, entryId); + } + public JSONObject toJson(){ + JSONObject jsonObject=new JSONObject(); + JSONArray jsonArray=new JSONArray(); + for(Package pkg: packageMap.values()){ + jsonArray.put(pkg.toJson()); + } + jsonObject.put("packages", jsonArray); + return jsonObject; + } + public static Table fromJson(JSONObject jsonObject){ + Table table=new Table(); + JSONArray jsonArray= jsonObject.optJSONArray("packages"); + if(jsonArray!=null){ + int length= jsonArray.length(); + for(int i=0;i typeMap; + public Package(byte id){ + this.id = id; + this.typeMap = new HashMap<>(); + } + public void merge(Package pkg){ + if(pkg==this||pkg==null){ + return; + } + if(pkg.id!=this.id){ + throw new IllegalArgumentException("Different package id: "+this.id+"!="+pkg.id); + } + if(pkg.name!=null){ + this.name = pkg.name; + } + for(Type type:pkg.typeMap.values()){ + add(type); + } + } + public Type getType(byte typeId){ + return typeMap.get(typeId); + } + public void add(Type type){ + Byte typeId= type.id;; + Type exist=this.typeMap.get(typeId); + if(exist!=null){ + exist.merge(type); + return; + } + type.mPackage=this; + this.typeMap.put(typeId, type); + } + public Package.Type.Entry getEntry(byte typeId, short entryId){ + Package.Type type=getType(typeId); + if(type==null){ + return null; + } + return type.getEntry(entryId); + } + public void add(Type.Entry entry){ + if(entry==null){ + return; + } + if(entry.getPackageId()!=this.id){ + throw new IllegalArgumentException("Different package id: "+entry); + } + byte typeId=entry.getTypeId(); + Type type=typeMap.get(typeId); + if(type==null){ + type=new Type(typeId); + type.mPackage=this; + typeMap.put(typeId, type); + } + type.add(entry); + } + public String getHexId(){ + return String.format("0x%02x", id); + } + public JSONObject toJson(){ + JSONObject jsonObject=new JSONObject(); + jsonObject.put("id", this.id); + if(this.name!=null){ + jsonObject.put("name", this.name); + } + JSONArray jsonArray=new JSONArray(); + for(Type type:typeMap.values()){ + jsonArray.put(type.toJson()); + } + jsonObject.put("types", jsonArray); + return jsonObject; + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Package aPackage = (Package) o; + return id == aPackage.id; + } + @Override + public int hashCode() { + return Objects.hash(id); + } + @Override + public String toString(){ + return getHexId() + ", types=" + typeMap.size(); + } + + public static Package fromJson(JSONObject jsonObject){ + Package pkg=new Package((byte) jsonObject.getInt("id")); + pkg.name = jsonObject.optString("name", null); + JSONArray jsonArray = jsonObject.optJSONArray("types"); + int length = jsonArray.length(); + for(int i=0;i entryMap; + public Type(byte id){ + this.id = id; + this.entryMap = new HashMap<>(); + } + public byte getPackageId(){ + if(mPackage!=null){ + return mPackage.id; + } + return 0; + } + public void merge(Type type){ + if(type==this||type==null){ + return; + } + if(this.id!= type.id){ + throw new IllegalArgumentException("Different type ids: "+id+"!="+type.id); + } + if(type.name!=null){ + this.name=type.name; + } + for(Entry entry:type.entryMap.values()){ + Short entryId=entry.getEntryId(); + Entry existEntry=this.entryMap.get(entryId); + if(existEntry != null && Objects.equals(existEntry.name, entry.name)){ + continue; + } + this.entryMap.remove(entryId); + entry.type=this; + this.entryMap.put(entryId, entry); + } + } + public Entry getEntry(short entryId){ + return entryMap.get(entryId); + } + public String getHexId(){ + return String.format("0x%02x", id); + } + public void add(Entry entry){ + if(entry==null){ + return; + } + if(entry.getTypeId()!=this.id){ + throw new IllegalArgumentException("Different type id: "+entry); + } + short key=entry.getEntryId(); + Entry exist=entryMap.get(key); + if(exist!=null){ + if(Objects.equals(exist.name, entry.name)){ + return; + } + throw new IllegalArgumentException("Duplicate entry exist: "+exist+", entry: "+entry); + } + if(name == null){ + this.name = entry.typeName; + } + entry.type=this; + entryMap.put(key, entry); + } + + public JSONObject toJson(){ + JSONObject jsonObject=new JSONObject(); + jsonObject.put("id", id); + jsonObject.put("name", name); + JSONArray jsonArray=new JSONArray(); + for(Entry entry: entryMap.values()){ + jsonArray.put(entry.toJson()); + } + jsonObject.put("entries", jsonArray); + return jsonObject; + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Type that = (Type) o; + return id == that.id; + } + @Override + public int hashCode() { + return Objects.hash(id); + } + @Override + public String toString(){ + StringBuilder builder=new StringBuilder(); + builder.append(getHexId()); + if(name !=null){ + builder.append(" ").append(name); + } + builder.append(", entries=").append(entryMap.size()); + return builder.toString(); + } + + public static Type fromJson(JSONObject jsonObject){ + Type type = new Type((byte) jsonObject.getInt("id")); + type.name = jsonObject.optString("name", null); + JSONArray jsonArray = jsonObject.optJSONArray("entries"); + if(jsonArray!=null){ + int length=jsonArray.length(); + for(int i=0;i{ + public int resourceId; + public String typeName; + public String name; + public Type type; + public Entry(int resourceId, String typeName, String name){ + this.resourceId = resourceId; + this.typeName = typeName; + this.name = name; + } + public Entry(int resourceId, String name){ + this(resourceId, null, name); + } + public String getTypeName(){ + if(this.type!=null){ + return this.type.name; + } + return this.typeName; + } + public byte getPackageId(){ + if(this.type!=null){ + Package pkg=this.type.mPackage; + if(pkg!=null){ + return pkg.id; + } + } + return (byte) ((resourceId>>24) & 0xff); + } + public byte getTypeId(){ + if(this.type!=null){ + return this.type.id; + } + return (byte) ((resourceId>>16) & 0xff); + } + public short getEntryId(){ + return (short) (resourceId & 0xffff); + } + public int getResourceId(){ + return ((getPackageId() & 0xff)<<24) + | ((getTypeId() & 0xff)<<16) + | (getEntryId() & 0xffff); + } + public String getHexId(){ + return String.format("0x%08x", getResourceId()); + } + @Override + public int compareTo(Entry entry) { + return Integer.compare(getResourceId(), entry.getResourceId()); + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Entry that = (Entry) o; + return getResourceId() == that.getResourceId(); + } + @Override + public int hashCode() { + return Objects.hash(getResourceId()); + } + public JSONObject toJson(){ + JSONObject jsonObject=new JSONObject(); + jsonObject.put("id", getResourceId()); + jsonObject.put("name", name); + return jsonObject; + } + public String toXml(){ + StringBuilder builder=new StringBuilder(); + builder.append(""); + return builder.toString(); + } + @Override + public String toString(){ + return toJson().toString(); + } + public static Entry fromEntryGroup(EntryGroup entryGroup){ + return new Entry(entryGroup.getResourceId(), + entryGroup.getTypeName(), + entryGroup.getSpecName()); + } + public static Entry fromJson(JSONObject jsonObject){ + return new Entry(jsonObject.getInt("id"), + jsonObject.optString("type", null), + jsonObject.getString("name")); + } + public static Entry fromXml(String xmlElement){ + String element=xmlElement; + element=element.replaceAll("[\n\r\t]+", " "); + element=element.trim(); + String start="")){ + return null; + } + element=element.substring(start.length()).trim(); + Pattern pattern=PATTERN; + int id=0; + String type=null; + String name=null; + Matcher matcher=pattern.matcher(element); + while (matcher.find()){ + String attr=matcher.group("Attr").toLowerCase(); + String value=matcher.group("Value"); + element=matcher.group("Next"); + if(attr.equals("id")){ + id=Integer.decode(value); + }else if(attr.equals("name")){ + name=value; + }else if(attr.equals("type")){ + type=value; + } + matcher= pattern.matcher(element); + } + if(id==0){ + throw new IllegalArgumentException("Missing id: "+xmlElement); + } + if(name==null){ + throw new IllegalArgumentException("Missing name: "+xmlElement); + } + return new Entry(id, type, name); + } + private static final Pattern PATTERN=Pattern.compile("^\\s*(?[^\\s=\"]+)\\s*=\\s*\"(?[^\"]+)\"(?.*)$"); + } + } + + } + } + + public static final String JSON_FILE_NAME ="resource-ids.json"; +} diff --git a/src/main/java/com/reandroid/lib/apk/SingleJsonTableInputSource.java b/src/main/java/com/reandroid/lib/apk/SingleJsonTableInputSource.java new file mode 100644 index 0000000..c9751a1 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/SingleJsonTableInputSource.java @@ -0,0 +1,53 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.FileInputSource; +import com.reandroid.archive.InputSource; +import com.reandroid.lib.arsc.chunk.TableBlock; +import com.reandroid.lib.json.JSONObject; + +import java.io.*; + +public class SingleJsonTableInputSource extends InputSource { + private final InputSource inputSource; + private TableBlock mCache; + public SingleJsonTableInputSource(InputSource inputSource) { + super(inputSource.getAlias()); + this.inputSource=inputSource; + } + @Override + public long write(OutputStream outputStream) throws IOException { + return getTableBlock().writeBytes(outputStream); + } + @Override + public InputStream openStream() throws IOException { + TableBlock tableBlock = getTableBlock(); + return new ByteArrayInputStream(tableBlock.getBytes()); + } + @Override + public long getLength() throws IOException{ + TableBlock tableBlock = getTableBlock(); + return tableBlock.countBytes(); + } + private TableBlock getTableBlock() throws IOException{ + if(mCache!=null){ + return mCache; + } + TableBlock tableBlock=newInstance(); + InputStream inputStream=inputSource.openStream(); + JSONObject jsonObject=new JSONObject(inputStream); + StringPoolBuilder poolBuilder=new StringPoolBuilder(); + poolBuilder.build(jsonObject); + poolBuilder.apply(tableBlock); + tableBlock.fromJson(jsonObject); + mCache=tableBlock; + return tableBlock; + } + TableBlock newInstance(){ + return new TableBlock(); + } + public static SingleJsonTableInputSource fromFile(File rootDir, File jsonFile){ + String path=ApkUtil.toArchiveResourcePath(rootDir, jsonFile); + FileInputSource fileInputSource=new FileInputSource(jsonFile, path); + return new SingleJsonTableInputSource(fileInputSource); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/SplitJsonTableInputSource.java b/src/main/java/com/reandroid/lib/apk/SplitJsonTableInputSource.java new file mode 100644 index 0000000..d99b168 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/SplitJsonTableInputSource.java @@ -0,0 +1,38 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.InputSource; +import com.reandroid.lib.arsc.chunk.TableBlock; + +import java.io.*; + +public class SplitJsonTableInputSource extends InputSource { + private final File dir; + private TableBlock mCache; + public SplitJsonTableInputSource(File dir) { + super(TableBlock.FILE_NAME); + this.dir=dir; + } + @Override + public long write(OutputStream outputStream) throws IOException { + return getTableBlock().writeBytes(outputStream); + } + @Override + public InputStream openStream() throws IOException { + TableBlock tableBlock = getTableBlock(); + return new ByteArrayInputStream(tableBlock.getBytes()); + } + @Override + public long getLength() throws IOException{ + TableBlock tableBlock = getTableBlock(); + return tableBlock.countBytes(); + } + private TableBlock getTableBlock() throws IOException { + if(mCache!=null){ + return mCache; + } + TableBlockJsonBuilder builder=new TableBlockJsonBuilder(); + TableBlock tableBlock=builder.scanDirectory(dir); + mCache=tableBlock; + return tableBlock; + } +} diff --git a/src/main/java/com/reandroid/lib/apk/StringPoolBuilder.java b/src/main/java/com/reandroid/lib/apk/StringPoolBuilder.java new file mode 100644 index 0000000..0e43601 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/StringPoolBuilder.java @@ -0,0 +1,135 @@ +package com.reandroid.lib.apk; + +import com.reandroid.lib.arsc.chunk.PackageBlock; +import com.reandroid.lib.arsc.chunk.TableBlock; +import com.reandroid.lib.arsc.pool.SpecStringPool; +import com.reandroid.lib.arsc.pool.TableStringPool; +import com.reandroid.lib.json.JSONArray; +import com.reandroid.lib.json.JSONObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.*; + +public class StringPoolBuilder { + private final Map> mSpecNameMap; + private final Set mTableStrings; + private byte mCurrentPackageId; + private JSONArray mStyledStrings; + public StringPoolBuilder(){ + this.mSpecNameMap = new HashMap<>(); + this.mTableStrings = new HashSet<>(); + } + public void apply(TableBlock tableBlock){ + applyTableString(tableBlock.getTableStringPool()); + for(byte pkgId:mSpecNameMap.keySet()){ + PackageBlock packageBlock=tableBlock.getPackageArray().getOrCreate(pkgId); + applySpecString(packageBlock.getSpecStringPool()); + } + } + private void applyTableString(TableStringPool stringPool){ + stringPool.fromJson(mStyledStrings); + stringPool.addStrings(getTableString()); + stringPool.refresh(); + } + private void applySpecString(SpecStringPool stringPool){ + byte pkgId= (byte) stringPool.getPackageBlock().getPackageId(); + stringPool.addStrings(getSpecString(pkgId)); + stringPool.refresh(); + } + public void scanDirectory(File resourcesDir) throws IOException { + mCurrentPackageId=0; + List pkgDirList=ApkUtil.listDirectories(resourcesDir); + for(File dir:pkgDirList){ + File pkgFile=new File(dir, ApkUtil.PACKAGE_JSON_FILE); + scanFile(pkgFile); + List jsonFileList=ApkUtil.recursiveFiles(dir, ".json"); + for(File file:jsonFileList){ + if(file.equals(pkgFile)){ + continue; + } + scanFile(file); + } + } + } + public void scanFile(File jsonFile) throws IOException { + FileInputStream inputStream=new FileInputStream(jsonFile); + JSONObject jsonObject=new JSONObject(inputStream); + build(jsonObject); + } + public void build(JSONObject jsonObject){ + scan(jsonObject); + } + public Set getTableString(){ + return mTableStrings; + } + public Set getSpecString(byte pkgId){ + return mSpecNameMap.get(pkgId); + } + private void scan(JSONObject jsonObject){ + if(jsonObject.has("is_array")){ + addSpecName(jsonObject.optString("name")); + } + if(jsonObject.has("value_type")){ + if("STRING".equals(jsonObject.getString("value_type"))){ + String data= jsonObject.getString("data"); + addTableString(data); + } + return; + }else if(jsonObject.has(PackageBlock.NAME_package_id)){ + mCurrentPackageId= (byte) jsonObject.getInt(PackageBlock.NAME_package_id); + } + Set keyList = jsonObject.keySet(); + for(String key:keyList){ + Object obj=jsonObject.get(key); + if(obj instanceof JSONObject){ + scan((JSONObject) obj); + continue; + } + if(obj instanceof JSONArray){ + JSONArray jsonArray = (JSONArray) obj; + if(TableBlock.NAME_styled_strings.equals(key)){ + this.mStyledStrings = jsonArray; + }else { + scan(jsonArray); + } + } + } + } + private void scan(JSONArray jsonArray){ + if(jsonArray==null){ + return; + } + for(Object obj:jsonArray.getArrayList()){ + if(obj instanceof JSONObject){ + scan((JSONObject) obj); + continue; + } + if(obj instanceof JSONArray){ + scan((JSONArray) obj); + } + } + } + private void addTableString(String name){ + if(name==null){ + return; + } + mTableStrings.add(name); + } + private void addSpecName(String name){ + if(name==null){ + return; + } + byte pkgId=mCurrentPackageId; + if(pkgId==0){ + throw new IllegalArgumentException("Current package id is 0"); + } + Set specNames=mSpecNameMap.get(pkgId); + if(specNames==null){ + specNames=new HashSet<>(); + mSpecNameMap.put(pkgId, specNames); + } + specNames.add(name); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/TableBlockJson.java b/src/main/java/com/reandroid/lib/apk/TableBlockJson.java new file mode 100644 index 0000000..bba8272 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/TableBlockJson.java @@ -0,0 +1,62 @@ +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.container.SpecTypePair; +import com.reandroid.lib.json.JSONObject; + +import java.io.File; +import java.io.IOException; + +public class TableBlockJson { + private final TableBlock tableBlock; + public TableBlockJson(TableBlock tableBlock){ + this.tableBlock=tableBlock; + } + public void writeJsonFiles(File outDir) throws IOException { + for(PackageBlock packageBlock: tableBlock.listPackages()){ + writeJsonFiles(outDir, packageBlock); + } + } + private void writeJsonFiles(File rootDir, PackageBlock packageBlock) throws IOException { + File pkgDir=new File(rootDir, getDirName(packageBlock)); + if(!pkgDir.exists()){ + pkgDir.mkdirs(); + } + File infoFile=new File(pkgDir, PackageBlock.JSON_FILE_NAME); + JSONObject jsonObject=new JSONObject(); + jsonObject.put(PackageBlock.NAME_package_id, packageBlock.getPackageId()); + jsonObject.put(PackageBlock.NAME_package_name, packageBlock.getPackageName()); + jsonObject.write(infoFile); + for(SpecTypePair specTypePair: packageBlock.listAllSpecTypePair()){ + for(TypeBlock typeBlock:specTypePair.getTypeBlockArray().listItems()){ + writeJsonFiles(pkgDir, typeBlock); + } + } + } + private void writeJsonFiles(File pkgDir, TypeBlock typeBlock) throws IOException { + String name= getFileName(typeBlock)+".json"; + File file=new File(pkgDir, name); + JSONObject jsonObject = typeBlock.toJson(); + jsonObject.write(file); + } + private String getFileName(TypeBlock typeBlock){ + StringBuilder builder=new StringBuilder(); + builder.append(String.format("0x%02x", typeBlock.getTypeId())); + String name= typeBlock.getTypeName(); + builder.append('-').append(name); + builder.append(typeBlock.getResConfig().getQualifiers()); + return builder.toString(); + } + private String getDirName(PackageBlock packageBlock){ + StringBuilder builder=new StringBuilder(); + builder.append(String.format("0x%02x", packageBlock.getPackageId())); + String name= packageBlock.getPackageName(); + if(name!=null){ + builder.append('-'); + builder.append(name); + } + return builder.toString(); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/TableBlockJsonBuilder.java b/src/main/java/com/reandroid/lib/apk/TableBlockJsonBuilder.java new file mode 100644 index 0000000..571a723 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/TableBlockJsonBuilder.java @@ -0,0 +1,63 @@ +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.value.ResConfig; +import com.reandroid.lib.json.JSONObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.List; + +public class TableBlockJsonBuilder { + private final StringPoolBuilder poolBuilder; + public TableBlockJsonBuilder(){ + poolBuilder=new StringPoolBuilder(); + } + public TableBlock scanDirectory(File resourcesDir) throws IOException { + if(!resourcesDir.isDirectory()){ + throw new IOException("No such directory: "+resourcesDir); + } + List pkgDirList=ApkUtil.listDirectories(resourcesDir); + if(pkgDirList.size()==0){ + throw new IOException("No package sub directory found in : "+resourcesDir); + } + TableBlock tableBlock=new TableBlock(); + poolBuilder.scanDirectory(resourcesDir); + poolBuilder.apply(tableBlock); + for(File pkgDir:pkgDirList){ + scanPackageDirectory(tableBlock, pkgDir); + } + tableBlock.refresh(); + return tableBlock; + } + private void scanPackageDirectory(TableBlock tableBlock, File pkgDir) throws IOException{ + File pkgFile=new File(pkgDir, "package.json"); + if(!pkgFile.isFile()){ + throw new IOException("Invalid package directory! Package file missing: "+pkgFile); + } + FileInputStream inputStream=new FileInputStream(pkgFile); + JSONObject jsonObject=new JSONObject(inputStream); + int id = jsonObject.getInt(PackageBlock.NAME_package_id); + String name=jsonObject.optString(PackageBlock.NAME_package_name); + PackageBlock pkg=tableBlock.getPackageArray().getOrCreate((byte) id); + pkg.setName(name); + List typeFileList=ApkUtil.listFiles(pkgDir, ".json"); + typeFileList.remove(pkgFile); + for(File typeFile:typeFileList){ + loadType(pkg, typeFile); + } + } + private void loadType(PackageBlock packageBlock, File typeJsonFile) throws IOException{ + FileInputStream inputStream=new FileInputStream(typeJsonFile); + JSONObject jsonObject=new JSONObject(inputStream); + int id= jsonObject.getInt("id"); + JSONObject configObj=jsonObject.getJSONObject("config"); + ResConfig resConfig=new ResConfig(); + resConfig.fromJson(configObj); + TypeBlock typeBlock=packageBlock.getSpecTypePairArray().getOrCreate((byte) id, resConfig); + typeBlock.fromJson(jsonObject); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/UncompressedFiles.java b/src/main/java/com/reandroid/lib/apk/UncompressedFiles.java new file mode 100644 index 0000000..1a45e25 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/UncompressedFiles.java @@ -0,0 +1,81 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.InputSource; +import com.reandroid.archive.ZipArchive; +import com.reandroid.lib.json.JSONArray; +import com.reandroid.lib.json.JSONConvert; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.ZipEntry; + +public class UncompressedFiles implements JSONConvert { + private final Set mPathList; + public UncompressedFiles(){ + this.mPathList=new HashSet<>(); + } + public void apply(ZipArchive archive){ + for(InputSource inputSource:archive.listInputSources()){ + apply(inputSource); + } + } + public void apply(InputSource inputSource){ + if(contains(inputSource.getName()) || contains(inputSource.getAlias())){ + inputSource.setMethod(ZipEntry.STORED); + }else { + inputSource.setMethod(ZipEntry.DEFLATED); + } + } + public boolean contains(String path){ + return mPathList.contains(path); + } + public void add(ZipArchive zipArchive){ + for(InputSource inputSource: zipArchive.listInputSources()){ + add(inputSource); + } + } + public void add(InputSource inputSource){ + if(inputSource.getMethod()!=ZipEntry.STORED){ + return; + } + add(inputSource.getAlias()); + } + public void add(String path){ + if(path==null || path.length()==0){ + return; + } + path=path.replace(File.separatorChar, '/').trim(); + while (path.startsWith("/")){ + path=path.substring(1); + } + mPathList.add(path); + } + public void clear(){ + mPathList.clear(); + } + @Override + public JSONArray toJson() { + return new JSONArray(mPathList); + } + @Override + public void fromJson(JSONArray json) { + if(json==null){ + return; + } + int length = json.length(); + for(int i=0;i implements JSO } @Override public String toString(){ - return toJson().toString(4); + return getClass().getSimpleName()+": size="+childesCount(); } private static final String NAME_id="id"; } diff --git a/src/main/java/com/reandroid/lib/arsc/array/SpecTypePairArray.java b/src/main/java/com/reandroid/lib/arsc/array/SpecTypePairArray.java index 9cfbb5a..c228675 100755 --- a/src/main/java/com/reandroid/lib/arsc/array/SpecTypePairArray.java +++ b/src/main/java/com/reandroid/lib/arsc/array/SpecTypePairArray.java @@ -4,6 +4,7 @@ import com.reandroid.lib.arsc.base.BlockArray; import com.reandroid.lib.arsc.chunk.TypeBlock; import com.reandroid.lib.arsc.container.SpecTypePair; import com.reandroid.lib.arsc.value.EntryBlock; +import com.reandroid.lib.arsc.value.ResConfig; import com.reandroid.lib.json.JSONConvert; import com.reandroid.lib.json.JSONArray; import com.reandroid.lib.json.JSONObject; @@ -61,6 +62,10 @@ public class SpecTypePairArray extends BlockArray implements JSONC } return pair.getTypeBlock(qualifiers); } + public TypeBlock getOrCreate(byte typeId, ResConfig resConfig){ + SpecTypePair pair=getOrCreate(typeId); + return pair.getTypeBlockArray().getOrCreate(resConfig); + } public SpecTypePair getOrCreate(byte typeId){ SpecTypePair pair=getPair(typeId); if(pair!=null){ @@ -143,6 +148,52 @@ public class SpecTypePairArray extends BlockArray implements JSONC } return results; } + public byte getSmallestTypeId(){ + SpecTypePair[] childes=getChildes(); + if(childes==null){ + return 0; + } + int result=0; + boolean firstFound=false; + for (int i=0;i extends OffsetBlockArray this.mUtf8=is_utf8; setEndBytes((byte)0x00); } + public List toStringList(){ + return new AbstractList() { + @Override + public String get(int i) { + T item=StringArray.this.get(i); + if(item==null){ + return null; + } + return item.getHtml(); + } + @Override + public int size() { + return childesCount(); + } + }; + } public List removeUnusedStrings(){ List allUnused=listUnusedStrings(); remove(allUnused); @@ -53,6 +70,7 @@ public abstract class StringArray extends OffsetBlockArray protected void refreshChildes(){ // Not required } + // Only styled strings @Override public JSONArray toJson() { return toJson(true); @@ -74,6 +92,9 @@ public abstract class StringArray extends OffsetBlockArray if(jsonObject==null){ continue; } + if(i>750){ + i=i+0; + } jsonArray.put(i, jsonObject); i++; } @@ -82,19 +103,9 @@ public abstract class StringArray extends OffsetBlockArray } return jsonArray; } + // Only styled strings @Override public void fromJson(JSONArray json) { - clearChildes(); - if(json==null){ - return; - } - int length = json.length(); - ensureSize(length); - for(int i=0; i implements JSONConvert } return typeBlock.getEntry(entryId); } + public TypeBlock getOrCreate(ResConfig resConfig){ + TypeBlock typeBlock=getTypeBlock(resConfig); + if(typeBlock!=null){ + return typeBlock; + } + byte id=getTypeId(); + typeBlock=createNext(); + typeBlock.setTypeId(id); + ResConfig config=typeBlock.getResConfig(); + config.copyFrom(resConfig); + return typeBlock; + } public TypeBlock getOrCreate(String qualifiers){ TypeBlock typeBlock=getTypeBlock(qualifiers); if(typeBlock!=null){ diff --git a/src/main/java/com/reandroid/lib/arsc/base/BlockArray.java b/src/main/java/com/reandroid/lib/arsc/base/BlockArray.java index f7dee78..ce09ef0 100755 --- a/src/main/java/com/reandroid/lib/arsc/base/BlockArray.java +++ b/src/main/java/com/reandroid/lib/arsc/base/BlockArray.java @@ -183,8 +183,23 @@ public abstract class BlockArray extends BlockContainer impl return false; } public void remove(Collection blockList){ + T[] items=elementData; + if(items==null || items.length==0){ + return; + } + int len=items.length; for(T block:blockList){ - remove(block, false); + if(block==null){ + continue; + } + int i=block.getIndex(); + if(i<0 || i>=len){ + continue; + } + if(items[i]!=block){ + continue; + } + items[i]=null; } trimNullBlocks(); } diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/BaseTypeBlock.java b/src/main/java/com/reandroid/lib/arsc/chunk/BaseTypeBlock.java index 5947302..6cbd485 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/BaseTypeBlock.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/BaseTypeBlock.java @@ -62,6 +62,13 @@ abstract class BaseTypeBlock extends BaseChunk { } return null; } + public String getTypeName(){ + TypeString typeString=getTypeString(); + if(typeString==null){ + return null; + } + return typeString.get(); + } public TypeString getTypeString(){ if(mTypeString!=null){ if(mTypeString.getId()==getTypeId()){ diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/PackageBlock.java b/src/main/java/com/reandroid/lib/arsc/chunk/PackageBlock.java index d8fa9e4..ebc38bd 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/PackageBlock.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/PackageBlock.java @@ -4,8 +4,11 @@ import com.reandroid.lib.arsc.array.LibraryInfoArray; import com.reandroid.lib.arsc.array.SpecTypePairArray; import com.reandroid.lib.arsc.base.Block; import com.reandroid.lib.arsc.container.PackageLastBlocks; +import com.reandroid.lib.arsc.container.SingleBlockContainer; import com.reandroid.lib.arsc.container.SpecTypePair; import com.reandroid.lib.arsc.group.EntryGroup; +import com.reandroid.lib.arsc.io.BlockLoad; +import com.reandroid.lib.arsc.io.BlockReader; import com.reandroid.lib.arsc.item.IntegerItem; import com.reandroid.lib.arsc.item.PackageName; import com.reandroid.lib.arsc.item.ReferenceItem; @@ -17,17 +20,19 @@ import com.reandroid.lib.arsc.value.LibraryInfo; import com.reandroid.lib.json.JSONConvert; import com.reandroid.lib.json.JSONObject; +import java.io.IOException; import java.util.*; -public class PackageBlock extends BaseChunk implements JSONConvert { +public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert { private final IntegerItem mPackageId; private final PackageName mPackageName; - private final IntegerItem mTypeStrings; - private final IntegerItem mLastPublicType; - private final IntegerItem mKeyStrings; - private final IntegerItem mLastPublicKey; + private final IntegerItem mTypeStringPoolOffset; + private final IntegerItem mTypeStringPoolCount; + private final IntegerItem mSpecStringPoolOffset; + private final IntegerItem mSpecStringPoolCount; + private final SingleBlockContainer mTypeIdOffsetContainer; private final IntegerItem mTypeIdOffset; private final TypeStringPool mTypeStringPool; @@ -44,13 +49,18 @@ public class PackageBlock extends BaseChunk implements JSONConvert super(ChunkType.PACKAGE, 3); this.mPackageId=new IntegerItem(); this.mPackageName=new PackageName(); - this.mTypeStrings=new IntegerItem(); - this.mLastPublicType=new IntegerItem(); - this.mKeyStrings=new IntegerItem(); - this.mLastPublicKey=new IntegerItem(); - this.mTypeIdOffset=new IntegerItem(); - this.mTypeStringPool=new TypeStringPool(false); + this.mTypeStringPoolOffset = new IntegerItem(); + this.mTypeStringPoolCount = new IntegerItem(); + this.mSpecStringPoolOffset = new IntegerItem(); + this.mSpecStringPoolCount = new IntegerItem(); + + this.mTypeIdOffsetContainer = new SingleBlockContainer<>(); + this.mTypeIdOffset = new IntegerItem(); + this.mTypeIdOffsetContainer.setItem(mTypeIdOffset); + + + this.mTypeStringPool=new TypeStringPool(false, mTypeIdOffset); this.mSpecStringPool=new SpecStringPool(true); this.mSpecTypePairArray=new SpecTypePairArray(); @@ -59,13 +69,15 @@ public class PackageBlock extends BaseChunk implements JSONConvert this.mEntriesGroup=new HashMap<>(); + mPackageId.setBlockLoad(this); + addToHeader(mPackageId); addToHeader(mPackageName); - addToHeader(mTypeStrings); - addToHeader(mLastPublicType); - addToHeader(mKeyStrings); - addToHeader(mLastPublicKey); - addToHeader(mTypeIdOffset); + addToHeader(mTypeStringPoolOffset); + addToHeader(mTypeStringPoolCount); + addToHeader(mSpecStringPoolOffset); + addToHeader(mSpecStringPoolCount); + addToHeader(mTypeIdOffsetContainer); addChild(mTypeStringPool); addChild(mSpecStringPool); @@ -73,6 +85,16 @@ public class PackageBlock extends BaseChunk implements JSONConvert addChild(mPackageLastBlocks); } + @Override + public void onBlockLoaded(BlockReader reader, Block sender) throws IOException { + if(sender==mPackageId){ + int headerSize=getHeaderBlock().getHeaderSize(); + if(headerSize!=288){ + mTypeIdOffset.set(0); + mTypeIdOffsetContainer.setItem(null); + } + } + } public void removeEmpty(){ getSpecTypePairArray().removeEmptyPairs(); } @@ -115,33 +137,7 @@ public class PackageBlock extends BaseChunk implements JSONConvert public void setPackageName(String name){ mPackageName.set(name); } - public void setTypeStrings(int i){ - mTypeStrings.set(i); - } - public int getLastPublicType(){ - return mLastPublicType.get(); - } - public void setLastPublicType(int i){ - mLastPublicType.set(i); - } - public int getKeyStrings(){ - return mKeyStrings.get(); - } - public void setKeyStrings(int i){ - mKeyStrings.set(i); - } - public int getLastPublicKey(){ - return mLastPublicKey.get(); - } - public void setLastPublicKey(int i){ - mLastPublicKey.set(i); - } - public int getTypeIdOffset(){ - return mTypeIdOffset.get(); - } - public void setTypeIdOffset(int i){ - mTypeIdOffset.set(i); - } + public TypeStringPool getTypeStringPool(){ return mTypeStringPool; } @@ -250,9 +246,26 @@ public class PackageBlock extends BaseChunk implements JSONConvert return getSpecTypePairArray().listItems(); } - private void refreshKeyStrings(){ + private void refreshTypeStringPoolOffset(){ + int pos=countUpTo(mTypeStringPool); + mTypeStringPoolOffset.set(pos); + } + private void refreshTypeStringPoolCount(){ + mTypeStringPoolCount.set(mTypeStringPool.countStrings()); + } + private void refreshSpecStringPoolOffset(){ int pos=countUpTo(mSpecStringPool); - mKeyStrings.set(pos); + mSpecStringPoolOffset.set(pos); + } + private void refreshSpecStringCount(){ + mSpecStringPoolCount.set(mSpecStringPool.countStrings()); + } + private void refreshTypeIdOffset(){ + int smallestId=getSpecTypePairArray().getSmallestTypeId(); + if(smallestId>0){ + smallestId=smallestId-1; + } + mTypeIdOffset.set(smallestId); } public void onEntryAdded(EntryBlock entryBlock){ updateEntry(entryBlock); @@ -263,14 +276,18 @@ public class PackageBlock extends BaseChunk implements JSONConvert @Override protected void onChunkRefreshed() { - refreshKeyStrings(); + refreshTypeStringPoolOffset(); + refreshTypeStringPoolCount(); + refreshSpecStringPoolOffset(); + refreshSpecStringCount(); + refreshTypeIdOffset(); } @Override public JSONObject toJson() { JSONObject jsonObject=new JSONObject(); - jsonObject.put(NAME_id, getId()); - jsonObject.put(NAME_name, getName()); + jsonObject.put(NAME_package_id, getId()); + jsonObject.put(NAME_package_name, getName()); jsonObject.put(NAME_specs, getSpecTypePairArray().toJson()); LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray(); if(libraryInfoArray.childesCount()>0){ @@ -280,8 +297,8 @@ public class PackageBlock extends BaseChunk implements JSONConvert } @Override public void fromJson(JSONObject json) { - setId(json.getInt(NAME_id)); - setName(json.getString(NAME_name)); + setId(json.getInt(NAME_package_id)); + setName(json.getString(NAME_package_name)); getSpecTypePairArray().fromJson(json.getJSONArray(NAME_specs)); LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray(); libraryInfoArray.fromJson(json.optJSONArray(NAME_libraries)); @@ -302,8 +319,9 @@ public class PackageBlock extends BaseChunk implements JSONConvert return builder.toString(); } - private static final String NAME_id="id"; - private static final String NAME_name="name"; + public static final String NAME_package_id = "package_id"; + public static final String NAME_package_name = "package_name"; + public static final String JSON_FILE_NAME = "package.json"; private static final String NAME_specs="specs"; private static final String NAME_libraries="libraries"; } diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/TableBlock.java b/src/main/java/com/reandroid/lib/arsc/chunk/TableBlock.java index 542fa3e..e59d3ee 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/TableBlock.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/TableBlock.java @@ -130,10 +130,6 @@ public class TableBlock extends BaseChunk implements JSONConvert { } @Override public void fromJson(JSONObject json) { - JSONArray jsonArray= json.optJSONArray(NAME_styled_strings); - if(jsonArray!=null){ - getTableStringPool().fromJson(jsonArray); - } getPackageArray().fromJson(json.getJSONArray(NAME_packages)); refresh(); } @@ -190,5 +186,5 @@ public class TableBlock extends BaseChunk implements JSONConvert { public static final String FILE_NAME="resources.arsc"; private static final String NAME_packages="packages"; - private static final String NAME_styled_strings="styled_strings"; + public static final String NAME_styled_strings="styled_strings"; } diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/BaseXmlChunk.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/BaseXmlChunk.java index 0f71db2..f64b196 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/BaseXmlChunk.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/BaseXmlChunk.java @@ -40,7 +40,7 @@ public class BaseXmlChunk extends BaseChunk { return mLineNumber.get(); } public void setCommentReference(int val){ - mLineNumber.set(val); + mCommentReference.set(val); } public int getCommentReference(){ return mCommentReference.get(); @@ -119,6 +119,21 @@ public class BaseXmlChunk extends BaseChunk { public String getUri(){ return getString(getNamespaceReference()); } + public String getComment(){ + return getString(getCommentReference()); + } + public void setComment(String comment){ + if(comment==null||comment.length()==0){ + setCommentReference(-1); + }else { + String old=getComment(); + if(comment.equals(old)){ + return; + } + ResXmlString xmlString = getOrCreateResXmlString(comment); + setCommentReference(xmlString.getIndex()); + } + } public ResXmlElement getParentResXmlElement(){ Block parent=getParent(); @@ -137,6 +152,14 @@ public class BaseXmlChunk extends BaseChunk { @Override protected void onChunkRefreshed() { + } + @Override + public void onChunkLoaded(){ + super.onChunkLoaded(); + if(mCommentReference.get()!=-1){ + String junk=getString(mCommentReference.get()); + System.out.println(junk); + } } @Override public String toString(){ diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlBlock.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlBlock.java index 5a18555..8987bc0 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlBlock.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlBlock.java @@ -160,7 +160,7 @@ public class ResXmlBlock extends BaseChunk implements JSONConvert { buildResourceIds(attributeList); Set allStrings=recursiveStrings(json.optJSONObject(ResXmlBlock.NAME_element)); ResXmlStringPool stringPool = getStringPool(); - stringPool.addAllStrings(allStrings); + stringPool.addStrings(allStrings); stringPool.refresh(); } private void buildResourceIds(List attributeList){ diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlElement.java b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlElement.java index b7df458..b10f38a 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlElement.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/xml/ResXmlElement.java @@ -316,6 +316,20 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert"+txt+"<"; + return txt; } return super.toString(); } diff --git a/src/main/java/com/reandroid/lib/arsc/item/IntegerArray.java b/src/main/java/com/reandroid/lib/arsc/item/IntegerArray.java index 4f8ba78..a7d47bd 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/IntegerArray.java +++ b/src/main/java/com/reandroid/lib/arsc/item/IntegerArray.java @@ -89,13 +89,6 @@ public class IntegerArray extends BlockItem { public final int size(){ return getBytesLength()/4; } - final void add(int value){ - int len=getBytesLength(); - len=len + 4; - setBytesLength(len, false); - int pos=size()-1; - put(pos, value); - } public final void put(int index, int value){ int i=index*4; byte[] bts = getBytesInternal(); @@ -104,7 +97,6 @@ public class IntegerArray extends BlockItem { bts[i+1]= (byte) (value >>> 8 & 0xff); bts[i]= (byte) (value & 0xff); } - @Override public void onBytesChanged() { diff --git a/src/main/java/com/reandroid/lib/arsc/item/SpecString.java b/src/main/java/com/reandroid/lib/arsc/item/SpecString.java index d51cb9e..a727c87 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/SpecString.java +++ b/src/main/java/com/reandroid/lib/arsc/item/SpecString.java @@ -6,6 +6,7 @@ public class SpecString extends StringItem { } @Override public StyleItem getStyle(){ + // Spec (resource name) don't have style unless to obfuscate/confuse other decompilers return null; } } diff --git a/src/main/java/com/reandroid/lib/arsc/item/StringItem.java b/src/main/java/com/reandroid/lib/arsc/item/StringItem.java index 25ee7d9..7969a18 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/StringItem.java +++ b/src/main/java/com/reandroid/lib/arsc/item/StringItem.java @@ -173,7 +173,7 @@ public class StringItem extends BlockItem implements JSONConvert { if(styleItem==null){ return false; } - return !styleItem.isNull(); + return styleItem.getSpanInfoList().size()>0; } public StyleItem getStyle(){ BaseStringPool stringPool=getStringPool(); @@ -362,6 +362,6 @@ public class StringItem extends BlockItem implements JSONConvert { private final CharsetDecoder UTF16LE_DECODER = StandardCharsets.UTF_16LE.newDecoder(); private final CharsetDecoder UTF8_DECODER = StandardCharsets.UTF_8.newDecoder(); - private static final String NAME_string="string"; - private static final String NAME_style="style"; + public static final String NAME_string="string"; + public static final String NAME_style="style"; } diff --git a/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java b/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java index 9ae950c..88567b2 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java +++ b/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java @@ -153,6 +153,9 @@ public class StyleItem extends IntegerArray implements JSONConvert { @Override public StyleSpanInfo get(int i) { int ref=getStringRef(i); + if(ref<=0){ + return null; + } return new StyleSpanInfo( getStringFromPool(ref), getFirstChar(i), @@ -192,7 +195,7 @@ public class StyleItem extends IntegerArray implements JSONConvert { return null; } List spanInfoList = getSpanInfoList(); - if(spanInfoList.size()==0){ + if(isEmpty(spanInfoList)){ return str; } StringBuilder builder=new StringBuilder(); @@ -202,6 +205,9 @@ public class StyleItem extends IntegerArray implements JSONConvert { char ch=allChars[i]; boolean lastAppend=false; for(StyleSpanInfo info:spanInfoList){ + if(info==null){ + continue; + } boolean isLast=(info.getLast()==i); if(info.getFirst()==i || isLast){ if(isLast && !lastAppend){ @@ -221,6 +227,17 @@ public class StyleItem extends IntegerArray implements JSONConvert { } return builder.toString(); } + private boolean isEmpty(List spanInfoList){ + if(spanInfoList.size()==0){ + return true; + } + for(StyleSpanInfo spanInfo:spanInfoList){ + if(spanInfo!=null){ + return false; + } + } + return true; + } @Override public void onBytesChanged() { } @@ -262,10 +279,16 @@ public class StyleItem extends IntegerArray implements JSONConvert { JSONArray jsonArray=new JSONArray(); int i=0; for(StyleSpanInfo spanInfo:getSpanInfoList()){ + if(spanInfo==null){ + continue; + } JSONObject jsonObjectSpan=spanInfo.toJson(); jsonArray.put(i, jsonObjectSpan); i++; } + if(i==0){ + return null; + } jsonObject.put(NAME_spans, jsonArray); return jsonObject; } @@ -295,5 +318,5 @@ public class StyleItem extends IntegerArray implements JSONConvert { private static final int INTEGERS_COUNT = 3; private static final int END_VALUE=0xFFFFFFFF; - private static final String NAME_spans="spans"; + public static final String NAME_spans="spans"; } diff --git a/src/main/java/com/reandroid/lib/arsc/item/TypeString.java b/src/main/java/com/reandroid/lib/arsc/item/TypeString.java index 3e087d7..6733928 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/TypeString.java +++ b/src/main/java/com/reandroid/lib/arsc/item/TypeString.java @@ -10,6 +10,7 @@ public class TypeString extends StringItem { } @Override public StyleItem getStyle(){ + // Type don't have style unless to obfuscate/confuse other decompilers return null; } } diff --git a/src/main/java/com/reandroid/lib/arsc/model/StyleSpanInfo.java b/src/main/java/com/reandroid/lib/arsc/model/StyleSpanInfo.java index 72d0e87..ca21131 100755 --- a/src/main/java/com/reandroid/lib/arsc/model/StyleSpanInfo.java +++ b/src/main/java/com/reandroid/lib/arsc/model/StyleSpanInfo.java @@ -78,7 +78,7 @@ public class StyleSpanInfo implements JSONConvert { return mTag +" ("+ mFirst +", "+ mLast +")"; } - private static final String NAME_tag="tag"; - private static final String NAME_first="first"; - private static final String NAME_last="last"; + public static final String NAME_tag="tag"; + public static final String NAME_first="first"; + public static final String NAME_last="last"; } diff --git a/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java b/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java index b750bc7..21b3f68 100755 --- a/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java +++ b/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java @@ -9,9 +9,10 @@ import com.reandroid.lib.arsc.group.StringGroup; import com.reandroid.lib.arsc.io.BlockLoad; import com.reandroid.lib.arsc.io.BlockReader; import com.reandroid.lib.arsc.item.*; -import com.reandroid.lib.arsc.pool.builder.StyleBuilder; +import com.reandroid.lib.arsc.model.StyleSpanInfo; import com.reandroid.lib.json.JSONConvert; import com.reandroid.lib.json.JSONArray; +import com.reandroid.lib.json.JSONObject; import java.io.IOException; import java.util.*; @@ -66,31 +67,60 @@ public abstract class BaseStringPool extends BaseChunk imp mUniqueMap=new HashMap<>(); } - public void addAllStrings(Collection stringList){ + public List toStringList(){ + return getStringsArray().toStringList(); + } + public void addStrings(Collection stringList){ + if(stringList==null || stringList.size()==0){ + return; + } + Set uniqueSet; + if(stringList instanceof HashSet){ + uniqueSet=(HashSet)stringList; + }else { + uniqueSet=new HashSet<>(stringList); + } + refreshUniqueIdMap(); + Set keySet=mUniqueMap.keySet(); + for(String key:keySet){ + uniqueSet.remove(key); + } List sortedList=new ArrayList<>(stringList); sortedList.sort(this); - addAllStrings(sortedList); + insertStrings(sortedList); } - public void addAllStrings(List sortedStringList){ - // call refreshUniqueIdMap() just in case elements modified somewhere - refreshUniqueIdMap(); - - for(String str:sortedStringList){ - getOrCreate(str); + private void insertStrings(List stringList){ + StringArray stringsArray = getStringsArray(); + int initialSize=stringsArray.childesCount(); + stringsArray.ensureSize(initialSize + stringList.size()); + int size=stringsArray.childesCount(); + int j=0; + for (int i=initialSize;i stringArray=getStringsArray(); - for(T stringItem:stringArray.listItems()){ - if(StyleBuilder.hasStyle(stringItem)){ - StyleBuilder.buildStyle(stringItem); + mUniqueMap.clear(); + T[] allChildes=getStrings(); + if(allChildes==null){ + return; + } + int max=allChildes.length; + for(int i=0;i group= getOrCreateGroup(str); + group.add(item); } } public List removeUnusedStrings(){ @@ -175,30 +205,6 @@ public abstract class BaseStringPool extends BaseChunk imp mCountStrings.set(mArrayStrings.childesCount()); return item; } - private void reUpdateUniqueMap(){ - mUniqueMap.clear(); - T[] allChildes=getStrings(); - if(allChildes==null){ - return; - } - int max=allChildes.length; - for(int i=0;i group= getOrCreateGroup(str); - group.add(item); - } public final StyleItem getStyle(int index){ return mArrayStyles.get(index); } @@ -259,7 +265,7 @@ public abstract class BaseStringPool extends BaseChunk imp } @Override public void onChunkLoaded() { - reUpdateUniqueMap(); + refreshUniqueIdMap(); } @Override @@ -272,16 +278,121 @@ public abstract class BaseStringPool extends BaseChunk imp public JSONArray toJson() { return getStringsArray().toJson(); } + //Only for styled strings @Override public void fromJson(JSONArray json) { - getStringsArray().fromJson(json); - refreshUniqueIdMap(); + if(json==null){ + return; + } + loadStyledStrings(json); refresh(); } + public void loadStyledStrings(JSONArray jsonArray) { + //Styled strings should be at first rows of string pool thus we clear all before adding + getStringsArray().clearChildes(); + getStyleArray().clearChildes(); + + List styledStringList = StyledString.fromJson(jsonArray); + loadText(styledStringList); + Map tagIndexMap = loadStyleTags(styledStringList); + loadStyles(styledStringList, tagIndexMap); + refreshUniqueIdMap(); + } + private void loadText(List styledStringList) { + StringArray stringsArray = getStringsArray(); + int size=styledStringList.size(); + stringsArray.ensureSize(size); + for(int i=0;i loadStyleTags(List styledStringList) { + Map indexMap=new HashMap<>(); + List tagList=new ArrayList<>(getStyleTags(styledStringList)); + tagList.sort(this); + StringArray stringsArray = getStringsArray(); + int tagsSize = tagList.size(); + int initialSize = stringsArray.childesCount(); + stringsArray.ensureSize(initialSize + tagsSize); + for(int i=0;i styledStringList, Map tagIndexMap){ + StyleArray styleArray = getStyleArray(); + int size=styledStringList.size(); + styleArray.ensureSize(size); + for(int i=0;i getStyleTags(List styledStringList){ + Set results=new HashSet<>(); + for(StyledString ss:styledStringList){ + for(StyleSpanInfo spanInfo:ss.spanInfoList){ + results.add(spanInfo.getTag()); + } + } + return results; + } @Override public int compare(String s1, String s2) { return s1.compareTo(s2); } + + private static class StyledString{ + final String text; + final List spanInfoList; + StyledString(String text, List spanInfoList){ + this.text=text; + this.spanInfoList=spanInfoList; + } + @Override + public String toString(){ + return text; + } + static List fromJson(JSONArray jsonArray){ + int length = jsonArray.length(); + List results=new ArrayList<>(); + for(int i=0;i]+>)([^<]+)(]+>)(.*)$"); } diff --git a/src/main/java/com/reandroid/lib/arsc/value/BaseResValue.java b/src/main/java/com/reandroid/lib/arsc/value/BaseResValue.java index c8e853a..832984a 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/BaseResValue.java +++ b/src/main/java/com/reandroid/lib/arsc/value/BaseResValue.java @@ -52,6 +52,9 @@ public abstract class BaseResValue extends BlockItem implements JSONConvert { private ShortItem mHeaderSize; - private ShortItem mFlags; + private ByteItem mFlagEntryType; + private ByteItem mByteFlagsB; private IntegerItem mSpecReference; private BaseResValue mResValue; private boolean mUnLocked; public EntryBlock() { super(); } - public ResValueInt setValueAsBoolean(boolean val){ ResValueInt resValueInt; BaseResValue res = getResValue(); @@ -227,12 +227,11 @@ public class EntryBlock extends Block implements JSONConvert { removeTableReferences(); removeSpecReferences(); } - - public short getFlags(){ - return mFlags.get(); + private void setEntryTypeFlag(byte b){ + mFlagEntryType.set(b); } - public void setFlags(short sh){ - mFlags.set(sh); + private void setByteFlagsB(byte b){ + mByteFlagsB.set(b); } public IntegerItem getSpecReferenceBlock(){ return mSpecReference; @@ -260,8 +259,8 @@ public class EntryBlock extends Block implements JSONConvert { setNull(true); }else { setNull(false); - boolean is_complex=(resValue instanceof ResValueBag); - setFlagComplex(is_complex); + boolean is_bag=(resValue instanceof ResValueBag); + setEntryTypeBag(is_bag); updatePackage(); updateSpecRef(); } @@ -271,7 +270,7 @@ public class EntryBlock extends Block implements JSONConvert { return; } if(resValue!=null){ - resValue.setIndex(3); + resValue.setIndex(4); resValue.setParent(this); } if(mResValue!=null){ @@ -284,14 +283,14 @@ public class EntryBlock extends Block implements JSONConvert { mResValue=resValue; } - public void setFlagComplex(boolean is_complex){ + public void setEntryTypeBag(boolean is_complex){ if(is_complex){ - if(!isFlagsComplex()){ - setFlags(FLAG_COMPLEX); + if(!isEntryTypeBag()){ + setEntryTypeFlag(FLAG_VALUE_BAG); } }else { - if(isFlagsComplex()){ - setFlags(FLAG_INT); + if(isEntryTypeBag()){ + setEntryTypeFlag(FLAG_VALUE_INT); } } refreshHeaderSize(); @@ -314,7 +313,13 @@ public class EntryBlock extends Block implements JSONConvert { private void setName(String name){ PackageBlock packageBlock=getPackageBlock(); EntryGroup entryGroup = packageBlock.getEntryGroup(getResourceId()); - entryGroup.renameSpec(name); + if(entryGroup!=null){ + entryGroup.renameSpec(name); + return; + } + SpecStringPool specStringPool= packageBlock.getSpecStringPool(); + SpecString specString=specStringPool.getOrCreate(name); + setSpecReference(specString.getIndex()); } public String getTypeName(){ TypeString typeString=getTypeString(); @@ -437,15 +442,18 @@ public class EntryBlock extends Block implements JSONConvert { } mUnLocked =true; this.mHeaderSize =new ShortItem(); - this.mFlags =new ShortItem(); + this.mFlagEntryType =new ByteItem(); + this.mByteFlagsB=new ByteItem(); this.mSpecReference = new IntegerItem(); mHeaderSize.setIndex(0); - mFlags.setIndex(1); - mSpecReference.setIndex(2); + mFlagEntryType.setIndex(1); + mByteFlagsB.setIndex(2); + mSpecReference.setIndex(3); mHeaderSize.setParent(this); - mFlags.setParent(this); + mFlagEntryType.setParent(this); + mByteFlagsB.setParent(this); mSpecReference.setParent(this); } @@ -456,16 +464,19 @@ public class EntryBlock extends Block implements JSONConvert { removeAllReferences(); mUnLocked =false; mHeaderSize.setParent(null); - mFlags.setParent(null); + mFlagEntryType.setParent(null); + mByteFlagsB.setParent(null); mSpecReference.setParent(null); mHeaderSize.setIndex(-1); - mFlags.setIndex(-1); + mFlagEntryType.setIndex(-1); + mByteFlagsB.setIndex(-1); mSpecReference.setIndex(-1); removeResValue(); this.mHeaderSize =null; - this.mFlags =null; + this.mFlagEntryType =null; + this.mByteFlagsB =null; this.mSpecReference =null; } private void removeResValue(){ @@ -476,7 +487,7 @@ public class EntryBlock extends Block implements JSONConvert { } } private void refreshHeaderSize(){ - if(isFlagsComplex()){ + if(isEntryTypeBag()){ mHeaderSize.set(HEADER_COMPLEX); }else { mHeaderSize.set(HEADER_INT); @@ -487,21 +498,15 @@ public class EntryBlock extends Block implements JSONConvert { return; } BaseResValue resValue; - if(isFlagsComplex()){ + if(isEntryTypeBag()){ resValue=new ResValueBag(); }else { resValue=new ResValueInt(); } setResValueInternal(resValue); } - private boolean isFlagsComplex(){ - return ((mFlags.get() & FLAG_COMPLEX_MASK) != 0); - } - public boolean isArray(){ - return ((mFlags.get() & FLAG_COMPLEX_MASK) != 0); - } - public void setIsArray(boolean is_array){ - setFlagComplex(is_array); + private boolean isEntryTypeBag(){ + return ((mFlagEntryType.get() & FLAG_BAG_ENTRY) != 0); } @Override public void setNull(boolean is_null){ @@ -525,7 +530,8 @@ public class EntryBlock extends Block implements JSONConvert { return null; } byte[] results=mHeaderSize.getBytes(); - results=addBytes(results, mFlags.getBytes()); + results=addBytes(results, mFlagEntryType.getBytes()); + results=addBytes(results, mByteFlagsB.getBytes()); results=addBytes(results, mSpecReference.getBytes()); results=addBytes(results, mResValue.getBytes()); return results; @@ -558,7 +564,8 @@ public class EntryBlock extends Block implements JSONConvert { } counter.addCount(countBytes()); mHeaderSize.onCountUpTo(counter); - mFlags.onCountUpTo(counter); + mFlagEntryType.onCountUpTo(counter); + mByteFlagsB.onCountUpTo(counter); mSpecReference.onCountUpTo(counter); mResValue.onCountUpTo(counter); } @@ -568,7 +575,8 @@ public class EntryBlock extends Block implements JSONConvert { return 0; } int result=mHeaderSize.writeBytes(stream); - result+=mFlags.writeBytes(stream); + result+= mFlagEntryType.writeBytes(stream); + result+=mByteFlagsB.writeBytes(stream); result+= mSpecReference.writeBytes(stream); result+=mResValue.writeBytes(stream); return result; @@ -612,12 +620,14 @@ public class EntryBlock extends Block implements JSONConvert { setNull(false); removeResValue(); mHeaderSize.readBytes(reader); - mFlags.readBytes(reader); + mFlagEntryType.readBytes(reader); + mByteFlagsB.readBytes(reader); mSpecReference.readBytes(reader); createResValue(); mResValue.readBytes(reader); updatePackage(); updateSpecRef(); + mResValue.onDataLoaded(); } @Override public JSONObject toJson() { @@ -626,7 +636,7 @@ public class EntryBlock extends Block implements JSONConvert { } JSONObject jsonObject=new JSONObject(); jsonObject.put(NAME_name, getSpecString().get()); - jsonObject.put(NAME_is_array, isArray()); + jsonObject.put(NAME_is_array, isEntryTypeBag()); jsonObject.put(NAME_value, getResValue().toJson()); return jsonObject; } @@ -636,16 +646,16 @@ public class EntryBlock extends Block implements JSONConvert { setNull(true); return; } - setName(json.getString(NAME_name)); - setNull(false); BaseResValue baseResValue; if(json.getBoolean(NAME_is_array)){ - baseResValue=new ResValueInt(); - }else { baseResValue=new ResValueBag(); + }else { + baseResValue=new ResValueInt(); } setResValue(baseResValue); + setName(json.getString(NAME_name)); baseResValue.fromJson(json.getJSONObject(NAME_value)); + mResValue.onDataLoaded(); } @Override public String toString(){ @@ -698,10 +708,10 @@ public class EntryBlock extends Block implements JSONConvert { return builder.toString(); } - private final static short FLAG_COMPLEX_MASK = 0x0001; + private final static byte FLAG_BAG_ENTRY = 0x01; - private final static short FLAG_COMPLEX = 0x0001; - private final static short FLAG_INT = 0x0000; + private final static byte FLAG_VALUE_BAG = 0x0001; + private final static byte FLAG_VALUE_INT = 0x00; private final static short HEADER_COMPLEX=0x0010; private final static short HEADER_INT=0x0008; diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResConfig.java b/src/main/java/com/reandroid/lib/arsc/value/ResConfig.java index 4feceb3..fc03c1e 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/ResConfig.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResConfig.java @@ -28,6 +28,13 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon this.configSize.setBlockLoad(this); this.mValuesContainer.setBlockLoad(this); } + public void copyFrom(ResConfig resConfig){ + if(resConfig==this||resConfig==null){ + return; + } + setConfigSize(resConfig.getConfigSize()); + mValuesContainer.putByteArray(0, resConfig.mValuesContainer.toArray()); + } @Override public void onBlockLoaded(BlockReader reader, Block sender) throws IOException { if(sender==configSize){ @@ -980,11 +987,11 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon return keyboard; } } - return NOKEYS; + return NONE; } public static Keyboard fromName(String name){ if(name==null){ - return NOKEYS; + return NONE; } name=name.trim().toUpperCase(); if(name.equals("12KEY")){ @@ -995,7 +1002,7 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon return keyboard; } } - return NOKEYS; + return NONE; } } public enum Navigation{ diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResValueBag.java b/src/main/java/com/reandroid/lib/arsc/value/ResValueBag.java index adf2d33..14a6c3d 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/ResValueBag.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResValueBag.java @@ -129,6 +129,12 @@ public class ResValueBag extends BaseResValue { mResValueBagItemArray.readBytes(reader); } @Override + void onDataLoaded() { + for(ResValueBagItem item: mResValueBagItemArray.listItems()){ + item.refreshTableReference(); + } + } + @Override public JSONObject toJson() { if(isNull()){ return null; @@ -147,9 +153,6 @@ public class ResValueBag extends BaseResValue { @Override public String toString(){ - if(getParent()!=null){ - return toJson().toString(4); - } StringBuilder builder=new StringBuilder(); builder.append(getClass().getSimpleName()); builder.append(": parent="); diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResValueBagItem.java b/src/main/java/com/reandroid/lib/arsc/value/ResValueBagItem.java index 912068a..16c4521 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/ResValueBagItem.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResValueBagItem.java @@ -102,14 +102,23 @@ public class ResValueBagItem extends BaseResValueItem{ setInt(OFFSET_DATA, data); afterDataValueChanged(); } + @Override + public void onSetReference(int data){ + setInt(OFFSET_DATA, data); + } private void beforeDataValueChanged(){ if(getValueType()==ValueType.STRING){ removeTableReference(); } } private void afterDataValueChanged(){ + refreshTableReference(); + } + void refreshTableReference(){ if(getValueType()==ValueType.STRING){ addTableReference(getTableStringReference()); + }else { + removeTableReference(); } } public short getIdHigh(){ @@ -195,6 +204,14 @@ public class ResValueBagItem extends BaseResValueItem{ builder.append(String.format("0x%08x", getId())); builder.append(", data="); builder.append(String.format("0x%08x", getData())); + if(vt==ValueType.STRING){ + TableString ts=getTableString(getData()); + if(ts==null){ + builder.append(" Null table string"); + }else { + builder.append(" \"").append(ts.getHtml()).append("\""); + } + } return builder.toString(); } private static final int OFFSET_ID=0; diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResValueInt.java b/src/main/java/com/reandroid/lib/arsc/value/ResValueInt.java index 84af73e..42a86d6 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/ResValueInt.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResValueInt.java @@ -26,6 +26,16 @@ public class ResValueInt extends BaseResValueItem { return getData()!=0; } @Override + void onDataLoaded() { + if(getValueType()==ValueType.STRING){ + if(!hasTableReference()){ + addTableReference(getTableStringReference()); + } + }else { + removeTableReference(); + } + } + @Override public void setHeaderSize(short size) { setShort(OFFSET_SIZE, size); } @@ -74,6 +84,10 @@ public class ResValueInt extends BaseResValueItem { } } @Override + public void onSetReference(int data){ + setInt(OFFSET_DATA, data); + } + @Override public JSONObject toJson() { if(isNull()){ return null; diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResValueItem.java b/src/main/java/com/reandroid/lib/arsc/value/ResValueItem.java index cba06ce..1e997e6 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/ResValueItem.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResValueItem.java @@ -13,4 +13,5 @@ public interface ResValueItem { int getData(); void setData(int data); + void onSetReference(int data); } diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResValueReference.java b/src/main/java/com/reandroid/lib/arsc/value/ResValueReference.java index 3fd22dd..3ceb312 100644 --- a/src/main/java/com/reandroid/lib/arsc/value/ResValueReference.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResValueReference.java @@ -2,6 +2,8 @@ package com.reandroid.lib.arsc.value; import com.reandroid.lib.arsc.item.ReferenceItem; +import java.util.Objects; + public class ResValueReference implements ReferenceItem { private final BaseResValueItem resValueItem; public ResValueReference(BaseResValueItem resValueItem){ @@ -12,10 +14,21 @@ public class ResValueReference implements ReferenceItem { } @Override public void set(int val) { - resValueItem.setData(val); + resValueItem.onSetReference(val); } @Override public int get() { return resValueItem.getData(); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResValueReference that = (ResValueReference) o; + return Objects.equals(resValueItem, that.resValueItem); + } + @Override + public int hashCode() { + return Objects.hash(resValueItem); + } } diff --git a/src/main/java/com/reandroid/lib/common/ROArrayList.java b/src/main/java/com/reandroid/lib/common/ROArrayList.java deleted file mode 100755 index 968eae8..0000000 --- a/src/main/java/com/reandroid/lib/common/ROArrayList.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.reandroid.lib.common; - -import java.util.AbstractList; - -public class ROArrayList extends AbstractList { - private final T[] elementData; - public ROArrayList(T[] elementData){ - this.elementData=elementData; - } - @Override - public T get(int i) { - return this.elementData[i]; - } - @Override - public int size() { - if(this.elementData==null){ - return 0; - } - return this.elementData.length; - } -} diff --git a/src/main/java/com/reandroid/lib/common/ROSingleList.java b/src/main/java/com/reandroid/lib/common/ROSingleList.java deleted file mode 100755 index ee21c40..0000000 --- a/src/main/java/com/reandroid/lib/common/ROSingleList.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.reandroid.lib.common; - -import java.util.AbstractList; - -public class ROSingleList extends AbstractList { - private final T item; - public ROSingleList(T item){ - this.item=item; - } - @Override - public T get(int i) { - if(i==0 && this.item!=null){ - return this.item; - } - throw new ArrayIndexOutOfBoundsException(getClass().getSimpleName()+": "+i); - } - - @Override - public int size() { - if(this.item==null){ - return 0; - } - return 1; - } -} diff --git a/src/main/java/com/reandroid/lib/json/JSONArray.java b/src/main/java/com/reandroid/lib/json/JSONArray.java index 8d52c70..f216dc4 100644 --- a/src/main/java/com/reandroid/lib/json/JSONArray.java +++ b/src/main/java/com/reandroid/lib/json/JSONArray.java @@ -1,6 +1,7 @@ package com.reandroid.lib.json; import java.io.IOException; +import java.io.InputStream; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Array; @@ -112,6 +113,16 @@ public class JSONArray extends JSONItem implements Iterable { } this.myArrayList = new ArrayList(initialCapacity); } + public JSONArray(InputStream inputStream) throws JSONException { + this(new JSONTokener(inputStream)); + try { + inputStream.close(); + } catch (IOException ignored) { + } + } + public ArrayList getArrayList(){ + return myArrayList; + } @Override public Iterator iterator() { diff --git a/src/main/java/com/reandroid/lib/json/JSONObject.java b/src/main/java/com/reandroid/lib/json/JSONObject.java index 963a369..4d40253 100644 --- a/src/main/java/com/reandroid/lib/json/JSONObject.java +++ b/src/main/java/com/reandroid/lib/json/JSONObject.java @@ -1,10 +1,7 @@ package com.reandroid.lib.json; -import java.io.Closeable; +import java.io.*; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -211,6 +208,14 @@ public class JSONObject extends JSONItem{ this.map = new HashMap(initialCapacity); } + + public JSONObject(InputStream inputStream) throws JSONException { + this(new JSONTokener(inputStream)); + try { + inputStream.close(); + } catch (IOException ignored) { + } + } public JSONObject accumulate(String key, Object value) throws JSONException { testValidity(value); Object object = this.opt(key); diff --git a/src/main/resources/fwk/android_resources_30.arsc b/src/main/resources/fwk/android_resources_30.arsc old mode 100755 new mode 100644 index bc18771..e24b958 Binary files a/src/main/resources/fwk/android_resources_30.arsc and b/src/main/resources/fwk/android_resources_30.arsc differ