mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-05-02 07:04:27 +02:00
V1.0.3
This commit is contained in:
parent
30bfe4b763
commit
6656786ecf
66
README.md
66
README.md
@ -6,58 +6,26 @@
|
||||
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<String> 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{
|
||||
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);
|
||||
}
|
||||
|
||||
```
|
||||
|
Binary file not shown.
@ -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;
|
||||
}
|
||||
}
|
171
src/main/java/com/reandroid/lib/apk/ApkJsonDecoder.java
Normal file
171
src/main/java/com/reandroid/lib/apk/ApkJsonDecoder.java
Normal file
@ -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<String> 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);
|
||||
}
|
||||
}
|
110
src/main/java/com/reandroid/lib/apk/ApkJsonEncoder.java
Normal file
110
src/main/java/com/reandroid/lib/apk/ApkJsonEncoder.java
Normal file
@ -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<File> 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<File> 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);
|
||||
}
|
||||
}
|
@ -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<String> 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<ResFile> resFileList=listResFiles();
|
||||
for(ResFile resFile:resFileList){
|
||||
boolean convertOk=resFile.dumpToJson(outDir);
|
||||
if(convertOk){
|
||||
convertedFiles.add(resFile.getFilePath());
|
||||
}
|
||||
}
|
||||
List<InputSource> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<File> recursiveFiles(File dir, String ext){
|
||||
List<File> 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<File> recursiveFiles(File dir){
|
||||
List<File> 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<File> listDirectories(File dir){
|
||||
List<File> 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<File> listFiles(File dir, String ext){
|
||||
List<File> 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";
|
||||
}
|
||||
|
@ -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<T extends BaseChunk> extends InputSource {
|
||||
public class BlockInputSource<T extends BaseChunk> 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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
47
src/main/java/com/reandroid/lib/apk/JsonXmlInputSource.java
Normal file
47
src/main/java/com/reandroid/lib/apk/JsonXmlInputSource.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
472
src/main/java/com/reandroid/lib/apk/ResourceIds.java
Normal file
472
src/main/java/com/reandroid/lib/apk/ResourceIds.java
Normal file
@ -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<EntryGroup> 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<Byte, Package> 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<length;i++){
|
||||
table.add(Package.fromJson(jsonArray.getJSONObject(i)));
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
public static class Package {
|
||||
public final byte id;
|
||||
public String name;
|
||||
public final Map<Byte, Type> 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<length;i++){
|
||||
Type type=Type.fromJson(jsonArray.getJSONObject(i));
|
||||
pkg.add(type);
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public static class Type{
|
||||
public final byte id;
|
||||
public String name;
|
||||
public Package mPackage;
|
||||
public final Map<Short, Entry> 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<length;i++){
|
||||
Entry entry=Entry.fromJson(jsonArray.getJSONObject(i));
|
||||
type.add(entry);
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public static class Entry implements Comparable<Entry>{
|
||||
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("<public");
|
||||
builder.append(" id=\"").append(getHexId()).append("\"");
|
||||
String tn=getTypeName();
|
||||
if(tn !=null){
|
||||
builder.append(" type=\"").append(tn).append("\"");
|
||||
}
|
||||
if(name!=null){
|
||||
builder.append(" name=\"").append(name).append("\"");
|
||||
}
|
||||
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="<public ";
|
||||
if(!element.startsWith(start) || !element.endsWith(">")){
|
||||
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*(?<Attr>[^\\s=\"]+)\\s*=\\s*\"(?<Value>[^\"]+)\"(?<Next>.*)$");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static final String JSON_FILE_NAME ="resource-ids.json";
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
135
src/main/java/com/reandroid/lib/apk/StringPoolBuilder.java
Normal file
135
src/main/java/com/reandroid/lib/apk/StringPoolBuilder.java
Normal file
@ -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<Byte, Set<String>> mSpecNameMap;
|
||||
private final Set<String> 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<File> pkgDirList=ApkUtil.listDirectories(resourcesDir);
|
||||
for(File dir:pkgDirList){
|
||||
File pkgFile=new File(dir, ApkUtil.PACKAGE_JSON_FILE);
|
||||
scanFile(pkgFile);
|
||||
List<File> 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<String> getTableString(){
|
||||
return mTableStrings;
|
||||
}
|
||||
public Set<String> 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<String> 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<String> specNames=mSpecNameMap.get(pkgId);
|
||||
if(specNames==null){
|
||||
specNames=new HashSet<>();
|
||||
mSpecNameMap.put(pkgId, specNames);
|
||||
}
|
||||
specNames.add(name);
|
||||
}
|
||||
}
|
62
src/main/java/com/reandroid/lib/apk/TableBlockJson.java
Normal file
62
src/main/java/com/reandroid/lib/apk/TableBlockJson.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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<File> 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<File> 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);
|
||||
}
|
||||
}
|
81
src/main/java/com/reandroid/lib/apk/UncompressedFiles.java
Normal file
81
src/main/java/com/reandroid/lib/apk/UncompressedFiles.java
Normal file
@ -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<JSONArray> {
|
||||
private final Set<String> 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<length;i++){
|
||||
this.add(json.getString(i));
|
||||
}
|
||||
}
|
||||
public void fromJson(File jsonFile) throws IOException {
|
||||
if(!jsonFile.isFile()){
|
||||
return;
|
||||
}
|
||||
JSONArray jsonArray=new JSONArray(new FileInputStream(jsonFile));
|
||||
fromJson(jsonArray);
|
||||
}
|
||||
public static final String JSON_FILE="uncompressed-files.json";
|
||||
}
|
@ -76,7 +76,7 @@ public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSO
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
return toJson().toString(4);
|
||||
return getClass().getSimpleName()+": size="+childesCount();
|
||||
}
|
||||
private static final String NAME_id="id";
|
||||
}
|
||||
|
@ -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<SpecTypePair> 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<SpecTypePair> 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<childes.length;i++){
|
||||
SpecTypePair pair=childes[i];
|
||||
if(pair==null){
|
||||
continue;
|
||||
}
|
||||
int id=pair.getTypeId();
|
||||
if(!firstFound){
|
||||
result=id;
|
||||
}
|
||||
firstFound=true;
|
||||
if(id<result){
|
||||
result=id;
|
||||
}
|
||||
}
|
||||
return (byte) result;
|
||||
}
|
||||
public byte getHighestTypeId(){
|
||||
SpecTypePair[] childes=getChildes();
|
||||
if(childes==null){
|
||||
return 0;
|
||||
}
|
||||
int result=0;
|
||||
boolean firstFound=false;
|
||||
for (int i=0;i<childes.length;i++){
|
||||
SpecTypePair pair=childes[i];
|
||||
if(pair==null){
|
||||
continue;
|
||||
}
|
||||
int id=pair.getTypeId();
|
||||
if(!firstFound){
|
||||
result=id;
|
||||
}
|
||||
firstFound=true;
|
||||
if(id<result){
|
||||
result=id;
|
||||
}
|
||||
}
|
||||
return (byte) result;
|
||||
}
|
||||
@Override
|
||||
public JSONArray toJson() {
|
||||
JSONArray jsonArray=new JSONArray();
|
||||
|
@ -7,6 +7,7 @@ import com.reandroid.lib.json.JSONConvert;
|
||||
import com.reandroid.lib.json.JSONArray;
|
||||
import com.reandroid.lib.json.JSONObject;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -18,6 +19,22 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
|
||||
this.mUtf8=is_utf8;
|
||||
setEndBytes((byte)0x00);
|
||||
}
|
||||
public List<String> toStringList(){
|
||||
return new AbstractList<String>() {
|
||||
@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<T> removeUnusedStrings(){
|
||||
List<T> allUnused=listUnusedStrings();
|
||||
remove(allUnused);
|
||||
@ -53,6 +70,7 @@ public abstract class StringArray<T extends StringItem> 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<T extends StringItem> 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<T extends StringItem> 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<length;i++){
|
||||
T item=get(i);
|
||||
JSONObject jsonObject = json.getJSONObject(i);
|
||||
item.fromJson(jsonObject);
|
||||
throw new IllegalArgumentException(getClass().getSimpleName()+".fromJson() NOT implemented");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -56,6 +56,18 @@ public class TypeBlockArray extends BlockArray<TypeBlock> 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){
|
||||
|
@ -183,8 +183,23 @@ public abstract class BlockArray<T extends Block> extends BlockContainer<T> impl
|
||||
return false;
|
||||
}
|
||||
public void remove(Collection<T> 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();
|
||||
}
|
||||
|
@ -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()){
|
||||
|
@ -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<JSONObject> {
|
||||
public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JSONObject> {
|
||||
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<IntegerItem> mTypeIdOffsetContainer;
|
||||
private final IntegerItem mTypeIdOffset;
|
||||
|
||||
private final TypeStringPool mTypeStringPool;
|
||||
@ -44,13 +49,18 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
||||
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<JSONObject>
|
||||
|
||||
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<JSONObject>
|
||||
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<JSONObject>
|
||||
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<JSONObject>
|
||||
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<JSONObject>
|
||||
|
||||
@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<JSONObject>
|
||||
}
|
||||
@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<JSONObject>
|
||||
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";
|
||||
}
|
||||
|
@ -130,10 +130,6 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
|
||||
}
|
||||
@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<JSONObject> {
|
||||
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";
|
||||
}
|
||||
|
@ -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(){
|
||||
|
@ -160,7 +160,7 @@ public class ResXmlBlock extends BaseChunk implements JSONConvert<JSONObject> {
|
||||
buildResourceIds(attributeList);
|
||||
Set<String> allStrings=recursiveStrings(json.optJSONObject(ResXmlBlock.NAME_element));
|
||||
ResXmlStringPool stringPool = getStringPool();
|
||||
stringPool.addAllStrings(allStrings);
|
||||
stringPool.addStrings(allStrings);
|
||||
stringPool.refresh();
|
||||
}
|
||||
private void buildResourceIds(List<JSONObject> attributeList){
|
||||
|
@ -316,6 +316,20 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
|
||||
public void setResXmlText(ResXmlText xmlText){
|
||||
mResXmlTextContainer.setItem(xmlText);
|
||||
}
|
||||
public void setResXmlText(String text){
|
||||
if(text==null){
|
||||
mResXmlTextContainer.setItem(null);
|
||||
}else {
|
||||
ResXmlText xmlText=mResXmlTextContainer.getItem();
|
||||
if(xmlText==null){
|
||||
xmlText=new ResXmlText();
|
||||
mResXmlTextContainer.setItem(xmlText);
|
||||
ResXmlStartElement start = getStartElement();
|
||||
xmlText.setLineNumber(start.getLineNumber());
|
||||
}
|
||||
xmlText.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBalanced(){
|
||||
return isElementBalanced() && isNamespaceBalanced();
|
||||
@ -523,6 +537,17 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
|
||||
jsonObject.put(NAME_namespaces, nsList);
|
||||
}
|
||||
jsonObject.put(NAME_name, start.getName());
|
||||
String comment=start.getComment();
|
||||
if(comment!=null){
|
||||
jsonObject.put(NAME_comment, comment);
|
||||
}
|
||||
ResXmlText xmlText=getResXmlText();
|
||||
if(xmlText!=null){
|
||||
String text=xmlText.getText();
|
||||
if(text!=null){
|
||||
jsonObject.put(NAME_text, text);
|
||||
}
|
||||
}
|
||||
String uri=start.getUri();
|
||||
if(uri!=null){
|
||||
jsonObject.put(NAME_namespace_uri, uri);
|
||||
@ -560,6 +585,11 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
|
||||
}
|
||||
}
|
||||
setTag(json.getString(NAME_name));
|
||||
start.setComment(json.optString(NAME_comment, null));
|
||||
String text= json.optString(NAME_text, null);
|
||||
if(text!=null){
|
||||
setResXmlText(text);
|
||||
}
|
||||
String uri = json.optString(NAME_namespace_uri, null);
|
||||
if(uri!=null){
|
||||
ResXmlStartNamespace ns = getStartNamespaceByUri(uri);
|
||||
@ -618,6 +648,8 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
|
||||
public static final String NS_ANDROID_PREFIX = "android";
|
||||
|
||||
static final String NAME_name = "name";
|
||||
static final String NAME_comment = "comment";
|
||||
static final String NAME_text = "text";
|
||||
static final String NAME_namespaces = "namespaces";
|
||||
static final String NAME_namespace_uri = "namespace_uri";
|
||||
static final String NAME_namespace_prefix = "namespace_prefix";
|
||||
|
@ -11,6 +11,7 @@ public class ResXmlText extends BaseXmlChunk {
|
||||
super(ChunkType.XML_CDATA, 1);
|
||||
this.mReserved=new IntegerItem();
|
||||
addChild(mReserved);
|
||||
setStringReference(0);
|
||||
}
|
||||
public String getText(){
|
||||
ResXmlString xmlString=getResXmlString(getTextReference());
|
||||
@ -38,7 +39,7 @@ public class ResXmlText extends BaseXmlChunk {
|
||||
public String toString(){
|
||||
String txt=getText();
|
||||
if(txt!=null){
|
||||
return "TEXT: line="+getLineNumber()+" >"+txt+"<";
|
||||
return txt;
|
||||
}
|
||||
return super.toString();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ public class StringItem extends BlockItem implements JSONConvert<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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";
|
||||
}
|
||||
|
@ -153,6 +153,9 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
|
||||
@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<JSONObject> {
|
||||
return null;
|
||||
}
|
||||
List<StyleSpanInfo> 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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
private boolean isEmpty(List<StyleSpanInfo> 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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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";
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ public class StyleSpanInfo implements JSONConvert<JSONObject> {
|
||||
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";
|
||||
}
|
||||
|
@ -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<T extends StringItem> extends BaseChunk imp
|
||||
mUniqueMap=new HashMap<>();
|
||||
|
||||
}
|
||||
public void addAllStrings(Collection<String> stringList){
|
||||
public List<String> toStringList(){
|
||||
return getStringsArray().toStringList();
|
||||
}
|
||||
public void addStrings(Collection<String> stringList){
|
||||
if(stringList==null || stringList.size()==0){
|
||||
return;
|
||||
}
|
||||
Set<String> uniqueSet;
|
||||
if(stringList instanceof HashSet){
|
||||
uniqueSet=(HashSet<String>)stringList;
|
||||
}else {
|
||||
uniqueSet=new HashSet<>(stringList);
|
||||
}
|
||||
refreshUniqueIdMap();
|
||||
Set<String> keySet=mUniqueMap.keySet();
|
||||
for(String key:keySet){
|
||||
uniqueSet.remove(key);
|
||||
}
|
||||
List<String> sortedList=new ArrayList<>(stringList);
|
||||
sortedList.sort(this);
|
||||
addAllStrings(sortedList);
|
||||
insertStrings(sortedList);
|
||||
}
|
||||
private void insertStrings(List<String> stringList){
|
||||
StringArray<T> stringsArray = getStringsArray();
|
||||
int initialSize=stringsArray.childesCount();
|
||||
stringsArray.ensureSize(initialSize + stringList.size());
|
||||
int size=stringsArray.childesCount();
|
||||
int j=0;
|
||||
for (int i=initialSize;i<size;i++){
|
||||
T item=stringsArray.get(i);
|
||||
item.set(stringList.get(j));
|
||||
j++;
|
||||
}
|
||||
public void addAllStrings(List<String> sortedStringList){
|
||||
// call refreshUniqueIdMap() just in case elements modified somewhere
|
||||
refreshUniqueIdMap();
|
||||
|
||||
for(String str:sortedStringList){
|
||||
getOrCreate(str);
|
||||
}
|
||||
}
|
||||
// call this after modifying string values
|
||||
public void refreshUniqueIdMap(){
|
||||
reUpdateUniqueMap();
|
||||
mUniqueMap.clear();
|
||||
T[] allChildes=getStrings();
|
||||
if(allChildes==null){
|
||||
return;
|
||||
}
|
||||
public void recreateStyles(){
|
||||
StyleArray styleArray = getStyleArray();
|
||||
//styleArray.clearChildes();
|
||||
StringArray<T> stringArray=getStringsArray();
|
||||
for(T stringItem:stringArray.listItems()){
|
||||
if(StyleBuilder.hasStyle(stringItem)){
|
||||
StyleBuilder.buildStyle(stringItem);
|
||||
int max=allChildes.length;
|
||||
for(int i=0;i<max;i++){
|
||||
T item=allChildes[i];
|
||||
if(item==null){
|
||||
continue;
|
||||
}
|
||||
String str=item.getHtml();
|
||||
if(str==null){
|
||||
continue;
|
||||
}
|
||||
StringGroup<T> group= getOrCreateGroup(str);
|
||||
group.add(item);
|
||||
}
|
||||
}
|
||||
public List<T> removeUnusedStrings(){
|
||||
@ -175,30 +205,6 @@ public abstract class BaseStringPool<T extends StringItem> 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<max;i++){
|
||||
T item=allChildes[i];
|
||||
if(item==null){
|
||||
continue;
|
||||
}
|
||||
String str=item.get();
|
||||
if(str==null){
|
||||
continue;
|
||||
}
|
||||
updateUniqueMap(item);
|
||||
}
|
||||
}
|
||||
private void updateUniqueMap(T item){
|
||||
String str=item.get();
|
||||
StringGroup<T> group= getOrCreateGroup(str);
|
||||
group.add(item);
|
||||
}
|
||||
public final StyleItem getStyle(int index){
|
||||
return mArrayStyles.get(index);
|
||||
}
|
||||
@ -259,7 +265,7 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
|
||||
}
|
||||
@Override
|
||||
public void onChunkLoaded() {
|
||||
reUpdateUniqueMap();
|
||||
refreshUniqueIdMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -272,16 +278,121 @@ public abstract class BaseStringPool<T extends StringItem> 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<StyledString> styledStringList = StyledString.fromJson(jsonArray);
|
||||
loadText(styledStringList);
|
||||
Map<String, Integer> tagIndexMap = loadStyleTags(styledStringList);
|
||||
loadStyles(styledStringList, tagIndexMap);
|
||||
refreshUniqueIdMap();
|
||||
}
|
||||
private void loadText(List<StyledString> styledStringList) {
|
||||
StringArray<T> stringsArray = getStringsArray();
|
||||
int size=styledStringList.size();
|
||||
stringsArray.ensureSize(size);
|
||||
for(int i=0;i<size;i++){
|
||||
StyledString styledString=styledStringList.get(i);
|
||||
T item=stringsArray.get(i);
|
||||
item.set(styledString.text);
|
||||
}
|
||||
}
|
||||
private Map<String, Integer> loadStyleTags(List<StyledString> styledStringList) {
|
||||
Map<String, Integer> indexMap=new HashMap<>();
|
||||
List<String> tagList=new ArrayList<>(getStyleTags(styledStringList));
|
||||
tagList.sort(this);
|
||||
StringArray<T> stringsArray = getStringsArray();
|
||||
int tagsSize = tagList.size();
|
||||
int initialSize = stringsArray.childesCount();
|
||||
stringsArray.ensureSize(initialSize + tagsSize);
|
||||
for(int i=0;i<tagsSize;i++){
|
||||
String tag = tagList.get(i);
|
||||
T item = stringsArray.get(initialSize + i);
|
||||
item.set(tag);
|
||||
indexMap.put(tag, item.getIndex());
|
||||
}
|
||||
return indexMap;
|
||||
}
|
||||
private void loadStyles(List<StyledString> styledStringList, Map<String, Integer> tagIndexMap){
|
||||
StyleArray styleArray = getStyleArray();
|
||||
int size=styledStringList.size();
|
||||
styleArray.ensureSize(size);
|
||||
for(int i=0;i<size;i++){
|
||||
StyledString ss = styledStringList.get(i);
|
||||
StyleItem styleItem = styleArray.get(i);
|
||||
for(StyleSpanInfo spanInfo:ss.spanInfoList){
|
||||
int tagIndex=tagIndexMap.get(spanInfo.getTag());
|
||||
styleItem.addStylePiece(tagIndex, spanInfo.getFirst(), spanInfo.getLast());
|
||||
}
|
||||
}
|
||||
}
|
||||
private static Set<String> getStyleTags(List<StyledString> styledStringList){
|
||||
Set<String> 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<StyleSpanInfo> spanInfoList;
|
||||
StyledString(String text, List<StyleSpanInfo> spanInfoList){
|
||||
this.text=text;
|
||||
this.spanInfoList=spanInfoList;
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
return text;
|
||||
}
|
||||
static List<StyledString> fromJson(JSONArray jsonArray){
|
||||
int length = jsonArray.length();
|
||||
List<StyledString> results=new ArrayList<>();
|
||||
for(int i=0;i<length;i++){
|
||||
if(!jsonArray.getJSONObject(i).has(StringItem.NAME_style)){
|
||||
System.out.println("Dont have style at i="+i);
|
||||
}
|
||||
StyledString styledString=fromJson(jsonArray.getJSONObject(i));
|
||||
results.add(styledString);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
static StyledString fromJson(JSONObject jsonObject){
|
||||
String text= jsonObject.getString(StringItem.NAME_string);
|
||||
JSONObject style=jsonObject.getJSONObject(StringItem.NAME_style);
|
||||
JSONArray spansArray=style.getJSONArray(StyleItem.NAME_spans);
|
||||
List<StyleSpanInfo> spanInfoList = toSpanInfoList(spansArray);
|
||||
return new StyledString(text, spanInfoList);
|
||||
}
|
||||
private static List<StyleSpanInfo> toSpanInfoList(JSONArray jsonArray){
|
||||
int length = jsonArray.length();
|
||||
List<StyleSpanInfo> results=new ArrayList<>(length);
|
||||
for(int i=0;i<length;i++){
|
||||
JSONObject jsonObject = jsonArray.getJSONObject(i);
|
||||
StyleSpanInfo spanInfo=new StyleSpanInfo(null, 0,0);
|
||||
spanInfo.fromJson(jsonObject);
|
||||
results.add(spanInfo);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
private static final short UTF8_FLAG_VALUE=0x0100;
|
||||
|
||||
private static final short FLAG_UTF8 = 0x0100;
|
||||
|
@ -2,6 +2,8 @@ package com.reandroid.lib.arsc.pool;
|
||||
|
||||
import com.reandroid.lib.arsc.array.SpecStringArray;
|
||||
import com.reandroid.lib.arsc.array.StringArray;
|
||||
import com.reandroid.lib.arsc.base.Block;
|
||||
import com.reandroid.lib.arsc.chunk.PackageBlock;
|
||||
import com.reandroid.lib.arsc.item.IntegerArray;
|
||||
import com.reandroid.lib.arsc.item.IntegerItem;
|
||||
import com.reandroid.lib.arsc.item.SpecString;
|
||||
@ -15,7 +17,14 @@ public class SpecStringPool extends BaseStringPool<SpecString> {
|
||||
StringArray<SpecString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
|
||||
return new SpecStringArray(offsets, itemCount, itemStart, is_utf8);
|
||||
}
|
||||
@Override
|
||||
public void recreateStyles(){
|
||||
public PackageBlock getPackageBlock(){
|
||||
Block parent=getParent();
|
||||
while (parent!=null){
|
||||
if(parent instanceof PackageBlock){
|
||||
return (PackageBlock) parent;
|
||||
}
|
||||
parent=parent.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,27 @@ package com.reandroid.lib.arsc.pool;
|
||||
|
||||
import com.reandroid.lib.arsc.array.StringArray;
|
||||
import com.reandroid.lib.arsc.array.TypeStringArray;
|
||||
import com.reandroid.lib.arsc.group.StringGroup;
|
||||
import com.reandroid.lib.arsc.header.HeaderBlock;
|
||||
import com.reandroid.lib.arsc.io.BlockReader;
|
||||
import com.reandroid.lib.arsc.item.IntegerArray;
|
||||
import com.reandroid.lib.arsc.item.IntegerItem;
|
||||
import com.reandroid.lib.arsc.item.TypeString;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class TypeStringPool extends BaseStringPool<TypeString> {
|
||||
public TypeStringPool(boolean is_utf8) {
|
||||
private final IntegerItem mTypeIdOffset;
|
||||
public TypeStringPool(boolean is_utf8, IntegerItem typeIdOffset) {
|
||||
super(is_utf8);
|
||||
this.mTypeIdOffset = typeIdOffset;
|
||||
}
|
||||
public TypeString getById(int id){
|
||||
return super.get(id-1);
|
||||
int index=id-mTypeIdOffset.get()-1;
|
||||
return super.get(index);
|
||||
}
|
||||
public TypeString getOrCreate(int typeId, String typeName){
|
||||
getStringsArray().ensureSize(typeId);
|
||||
int size=typeId-mTypeIdOffset.get();
|
||||
getStringsArray().ensureSize(size);
|
||||
TypeString typeString=getById(typeId);
|
||||
typeString.set(typeName);
|
||||
return typeString;
|
||||
@ -24,7 +31,4 @@ public class TypeStringPool extends BaseStringPool<TypeString> {
|
||||
StringArray<TypeString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
|
||||
return new TypeStringArray(offsets, itemCount, itemStart, is_utf8);
|
||||
}
|
||||
@Override
|
||||
public void recreateStyles(){
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,107 @@
|
||||
package com.reandroid.lib.arsc.pool.builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SpannedText {
|
||||
private String mLeftText;
|
||||
private String mTag;
|
||||
private String mText;
|
||||
private String mRightText;
|
||||
private List<SpannedText> mChildes;
|
||||
private int mStart;
|
||||
private int mEnd;
|
||||
private List<SpannedText> mChildes=new ArrayList<>();
|
||||
public SpannedText(){
|
||||
}
|
||||
public void parse(int start, String text){
|
||||
int i=text.indexOf('<', start);
|
||||
public void parse(String text){
|
||||
char[] allChars=text.toCharArray();
|
||||
int len=allChars.length;
|
||||
String firstTag=null;
|
||||
String endTag=null;
|
||||
StringBuilder tag=null;
|
||||
boolean firstTagFound=false;
|
||||
int posFirst=0;
|
||||
int posSecond=0;
|
||||
for(int i=0;i<len;i++){
|
||||
char ch=allChars[i];
|
||||
if(tag!=null){
|
||||
tag.append(ch);
|
||||
if(ch=='>'){
|
||||
if(!firstTagFound){
|
||||
firstTag=tag.toString();
|
||||
firstTagFound=true;
|
||||
tag=null;
|
||||
}else {
|
||||
endTag=tag.toString();
|
||||
if(isTagsMatch(firstTag, endTag)){
|
||||
break;
|
||||
}
|
||||
endTag=null;
|
||||
tag=null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(ch=='<'){
|
||||
if(isClosing(allChars, i+1)){
|
||||
if(!firstTagFound){
|
||||
tag=null;
|
||||
continue;
|
||||
}
|
||||
}else if(firstTagFound){
|
||||
firstTagFound=false;
|
||||
firstTag=null;
|
||||
}
|
||||
tag=new StringBuilder();
|
||||
tag.append(ch);
|
||||
if(!firstTagFound){
|
||||
posFirst=i;
|
||||
}else {
|
||||
posSecond=i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(firstTag==null || endTag==null){
|
||||
return;
|
||||
}
|
||||
mStart=posFirst;
|
||||
mEnd=posSecond;
|
||||
StringBuilder builder=new StringBuilder();
|
||||
builder.append(text, 0, posFirst);
|
||||
builder.append(text, posFirst+firstTag.length(), posSecond);
|
||||
builder.append(text.substring(posSecond+endTag.length()));
|
||||
mText=builder.toString();
|
||||
}
|
||||
private boolean isClosing(char[] allChars, int pos){
|
||||
for(int i=pos;i<allChars.length;i++){
|
||||
char ch=allChars[i];
|
||||
if(ch=='/'){
|
||||
return true;
|
||||
}
|
||||
if(ch!=' '){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private boolean isTagsMatch(String start, String end){
|
||||
start=trimStart(start);
|
||||
end=trimEndTag(end);
|
||||
return start.equals(end);
|
||||
}
|
||||
private String trimStart(String start){
|
||||
start=start.substring(1, start.length()-1);
|
||||
int i=start.indexOf(' ');
|
||||
if(i>0){
|
||||
start=start.substring(0,i);
|
||||
}
|
||||
start=start.trim();
|
||||
return start;
|
||||
}
|
||||
private String trimEndTag(String end){
|
||||
end=end.substring(1, end.length()-1).trim();
|
||||
end=end.substring(1).trim();
|
||||
return end;
|
||||
}
|
||||
private static final Pattern PATTERN_TAG=Pattern.compile("^(.*)(<[^/<>]+>)([^<]+)(</[^<>]+>)(.*)$");
|
||||
}
|
||||
|
@ -52,6 +52,9 @@ public abstract class BaseResValue extends BlockItem implements JSONConvert<JSON
|
||||
@Override
|
||||
public void onBytesChanged() {
|
||||
|
||||
}
|
||||
void onDataLoaded() {
|
||||
|
||||
}
|
||||
int getInt(int offset){
|
||||
byte[] bts = getBytesInternal();
|
||||
|
@ -50,6 +50,9 @@ public abstract class BaseResValueItem extends BaseResValue implements ResValueI
|
||||
}
|
||||
return mReferenceItem;
|
||||
}
|
||||
boolean hasTableReference(){
|
||||
return mReferenceItem!=null;
|
||||
}
|
||||
boolean removeTableReference(){
|
||||
ReferenceItem ref=mReferenceItem;
|
||||
if(ref==null){
|
||||
|
@ -21,14 +21,14 @@ import java.util.List;
|
||||
|
||||
public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
private void setName(String name){
|
||||
PackageBlock packageBlock=getPackageBlock();
|
||||
EntryGroup entryGroup = packageBlock.getEntryGroup(getResourceId());
|
||||
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<JSONObject> {
|
||||
}
|
||||
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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
}
|
||||
}
|
||||
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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
}
|
||||
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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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 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<JSONObject> {
|
||||
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<JSONObject> {
|
||||
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;
|
||||
|
@ -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{
|
||||
|
@ -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=");
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -13,4 +13,5 @@ public interface ResValueItem {
|
||||
|
||||
int getData();
|
||||
void setData(int data);
|
||||
void onSetReference(int data);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
package com.reandroid.lib.common;
|
||||
|
||||
import java.util.AbstractList;
|
||||
|
||||
public class ROArrayList<T> extends AbstractList<T> {
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package com.reandroid.lib.common;
|
||||
|
||||
import java.util.AbstractList;
|
||||
|
||||
public class ROSingleList<T> extends AbstractList<T> {
|
||||
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;
|
||||
}
|
||||
}
|
@ -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<Object> {
|
||||
}
|
||||
this.myArrayList = new ArrayList<Object>(initialCapacity);
|
||||
}
|
||||
public JSONArray(InputStream inputStream) throws JSONException {
|
||||
this(new JSONTokener(inputStream));
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
public ArrayList<Object> getArrayList(){
|
||||
return myArrayList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
|
@ -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<String, Object>(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);
|
||||
|
BIN
src/main/resources/fwk/android_resources_30.arsc
Executable file → Normal file
BIN
src/main/resources/fwk/android_resources_30.arsc
Executable file → Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user