mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-05-02 23:24:26 +02:00
V1.0.3
This commit is contained in:
parent
30bfe4b763
commit
6656786ecf
68
README.md
68
README.md
@ -5,59 +5,27 @@
|
|||||||
import com.reandroid.lib.arsc.chunk.TableBlock;
|
import com.reandroid.lib.arsc.chunk.TableBlock;
|
||||||
import com.reandroid.lib.arsc.io.BlockReader;
|
import com.reandroid.lib.arsc.io.BlockReader;
|
||||||
|
|
||||||
public static void example() throws IOException {
|
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 inFile=new File("test.apk");
|
||||||
File outDir=new File("test_out");
|
File outDir=new File("test_out");
|
||||||
|
|
||||||
ApkModule apkModule=ApkModule.loadApkFile(inFile);
|
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.APKArchive;
|
||||||
import com.reandroid.archive.InputSource;
|
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.array.PackageArray;
|
||||||
import com.reandroid.lib.arsc.chunk.PackageBlock;
|
import com.reandroid.lib.arsc.chunk.PackageBlock;
|
||||||
import com.reandroid.lib.arsc.chunk.TableBlock;
|
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 com.reandroid.lib.arsc.value.EntryBlock;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ApkModule {
|
public class ApkModule {
|
||||||
|
private final String moduleName;
|
||||||
private final APKArchive apkArchive;
|
private final APKArchive apkArchive;
|
||||||
private boolean loadDefaultFramework = true;
|
private boolean loadDefaultFramework = true;
|
||||||
private TableBlock mTableBlock;
|
private TableBlock mTableBlock;
|
||||||
private AndroidManifestBlock mManifestBlock;
|
private AndroidManifestBlock mManifestBlock;
|
||||||
private ApkModule(APKArchive apkArchive){
|
private final UncompressedFiles mUncompressedFiles;
|
||||||
|
ApkModule(String moduleName, APKArchive apkArchive){
|
||||||
|
this.moduleName=moduleName;
|
||||||
this.apkArchive=apkArchive;
|
this.apkArchive=apkArchive;
|
||||||
|
this.mUncompressedFiles=new UncompressedFiles();
|
||||||
|
this.mUncompressedFiles.add(apkArchive);
|
||||||
}
|
}
|
||||||
public void writeTo(File file) throws IOException {
|
public String getModuleName(){
|
||||||
APKArchive archive=getApkArchive();
|
return moduleName;
|
||||||
archive.writeApk(file);
|
}
|
||||||
|
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){
|
public void removeDir(String dirName){
|
||||||
getApkArchive().removeDir(dirName);
|
getApkArchive().removeDir(dirName);
|
||||||
@ -164,48 +180,12 @@ public class ApkModule {
|
|||||||
public void setLoadDefaultFramework(boolean loadDefaultFramework) {
|
public void setLoadDefaultFramework(boolean loadDefaultFramework) {
|
||||||
this.loadDefaultFramework = 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 {
|
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);
|
APKArchive archive=APKArchive.loadZippedApk(apkFile);
|
||||||
return new ApkModule(archive);
|
return new ApkModule(moduleName, archive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package com.reandroid.lib.apk;
|
package com.reandroid.lib.apk;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ApkUtil {
|
public class ApkUtil {
|
||||||
public static String replaceRootDir(String path, String dirName){
|
public static String replaceRootDir(String path, String dirName){
|
||||||
int i=path.indexOf('/')+1;
|
int i=path.indexOf('/')+1;
|
||||||
@ -12,5 +16,100 @@ public class ApkUtil {
|
|||||||
}
|
}
|
||||||
return path;
|
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;
|
package com.reandroid.lib.apk;
|
||||||
|
|
||||||
|
import com.reandroid.archive.ByteInputSource;
|
||||||
import com.reandroid.archive.InputSource;
|
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.BaseChunk;
|
||||||
|
import com.reandroid.lib.arsc.chunk.TableBlock;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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;
|
private final T mBlock;
|
||||||
public BlockInputSource(String name, T block) {
|
public BlockInputSource(String name, T block) {
|
||||||
super(name);
|
super(new byte[0], name);
|
||||||
this.mBlock=block;
|
this.mBlock=block;
|
||||||
}
|
}
|
||||||
public T getBlock() {
|
public T getBlock() {
|
||||||
|
mBlock.refresh();
|
||||||
return mBlock;
|
return mBlock;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public InputStream openStream(){
|
public long getLength() throws IOException{
|
||||||
T block=getBlock();
|
Block block = getBlock();
|
||||||
block.refresh();
|
return block.countBytes();
|
||||||
return new ByteArrayInputStream(block.getBytes());
|
}
|
||||||
|
@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
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return toJson().toString(4);
|
return getClass().getSimpleName()+": size="+childesCount();
|
||||||
}
|
}
|
||||||
private static final String NAME_id="id";
|
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.chunk.TypeBlock;
|
||||||
import com.reandroid.lib.arsc.container.SpecTypePair;
|
import com.reandroid.lib.arsc.container.SpecTypePair;
|
||||||
import com.reandroid.lib.arsc.value.EntryBlock;
|
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.JSONConvert;
|
||||||
import com.reandroid.lib.json.JSONArray;
|
import com.reandroid.lib.json.JSONArray;
|
||||||
import com.reandroid.lib.json.JSONObject;
|
import com.reandroid.lib.json.JSONObject;
|
||||||
@ -61,6 +62,10 @@ public class SpecTypePairArray extends BlockArray<SpecTypePair> implements JSONC
|
|||||||
}
|
}
|
||||||
return pair.getTypeBlock(qualifiers);
|
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){
|
public SpecTypePair getOrCreate(byte typeId){
|
||||||
SpecTypePair pair=getPair(typeId);
|
SpecTypePair pair=getPair(typeId);
|
||||||
if(pair!=null){
|
if(pair!=null){
|
||||||
@ -143,6 +148,52 @@ public class SpecTypePairArray extends BlockArray<SpecTypePair> implements JSONC
|
|||||||
}
|
}
|
||||||
return results;
|
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
|
@Override
|
||||||
public JSONArray toJson() {
|
public JSONArray toJson() {
|
||||||
JSONArray jsonArray=new JSONArray();
|
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.JSONArray;
|
||||||
import com.reandroid.lib.json.JSONObject;
|
import com.reandroid.lib.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.AbstractList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -18,6 +19,22 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
|
|||||||
this.mUtf8=is_utf8;
|
this.mUtf8=is_utf8;
|
||||||
setEndBytes((byte)0x00);
|
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(){
|
public List<T> removeUnusedStrings(){
|
||||||
List<T> allUnused=listUnusedStrings();
|
List<T> allUnused=listUnusedStrings();
|
||||||
remove(allUnused);
|
remove(allUnused);
|
||||||
@ -53,6 +70,7 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
|
|||||||
protected void refreshChildes(){
|
protected void refreshChildes(){
|
||||||
// Not required
|
// Not required
|
||||||
}
|
}
|
||||||
|
// Only styled strings
|
||||||
@Override
|
@Override
|
||||||
public JSONArray toJson() {
|
public JSONArray toJson() {
|
||||||
return toJson(true);
|
return toJson(true);
|
||||||
@ -74,6 +92,9 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
|
|||||||
if(jsonObject==null){
|
if(jsonObject==null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if(i>750){
|
||||||
|
i=i+0;
|
||||||
|
}
|
||||||
jsonArray.put(i, jsonObject);
|
jsonArray.put(i, jsonObject);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@ -82,19 +103,9 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
|
|||||||
}
|
}
|
||||||
return jsonArray;
|
return jsonArray;
|
||||||
}
|
}
|
||||||
|
// Only styled strings
|
||||||
@Override
|
@Override
|
||||||
public void fromJson(JSONArray json) {
|
public void fromJson(JSONArray json) {
|
||||||
clearChildes();
|
throw new IllegalArgumentException(getClass().getSimpleName()+".fromJson() NOT implemented");
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,18 @@ public class TypeBlockArray extends BlockArray<TypeBlock> implements JSONConvert
|
|||||||
}
|
}
|
||||||
return typeBlock.getEntry(entryId);
|
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){
|
public TypeBlock getOrCreate(String qualifiers){
|
||||||
TypeBlock typeBlock=getTypeBlock(qualifiers);
|
TypeBlock typeBlock=getTypeBlock(qualifiers);
|
||||||
if(typeBlock!=null){
|
if(typeBlock!=null){
|
||||||
|
@ -183,8 +183,23 @@ public abstract class BlockArray<T extends Block> extends BlockContainer<T> impl
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
public void remove(Collection<T> blockList){
|
public void remove(Collection<T> blockList){
|
||||||
|
T[] items=elementData;
|
||||||
|
if(items==null || items.length==0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int len=items.length;
|
||||||
for(T block:blockList){
|
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();
|
trimNullBlocks();
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,13 @@ abstract class BaseTypeBlock extends BaseChunk {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
public String getTypeName(){
|
||||||
|
TypeString typeString=getTypeString();
|
||||||
|
if(typeString==null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return typeString.get();
|
||||||
|
}
|
||||||
public TypeString getTypeString(){
|
public TypeString getTypeString(){
|
||||||
if(mTypeString!=null){
|
if(mTypeString!=null){
|
||||||
if(mTypeString.getId()==getTypeId()){
|
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.array.SpecTypePairArray;
|
||||||
import com.reandroid.lib.arsc.base.Block;
|
import com.reandroid.lib.arsc.base.Block;
|
||||||
import com.reandroid.lib.arsc.container.PackageLastBlocks;
|
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.container.SpecTypePair;
|
||||||
import com.reandroid.lib.arsc.group.EntryGroup;
|
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.IntegerItem;
|
||||||
import com.reandroid.lib.arsc.item.PackageName;
|
import com.reandroid.lib.arsc.item.PackageName;
|
||||||
import com.reandroid.lib.arsc.item.ReferenceItem;
|
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.JSONConvert;
|
||||||
import com.reandroid.lib.json.JSONObject;
|
import com.reandroid.lib.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
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 IntegerItem mPackageId;
|
||||||
private final PackageName mPackageName;
|
private final PackageName mPackageName;
|
||||||
|
|
||||||
private final IntegerItem mTypeStrings;
|
private final IntegerItem mTypeStringPoolOffset;
|
||||||
private final IntegerItem mLastPublicType;
|
private final IntegerItem mTypeStringPoolCount;
|
||||||
private final IntegerItem mKeyStrings;
|
private final IntegerItem mSpecStringPoolOffset;
|
||||||
private final IntegerItem mLastPublicKey;
|
private final IntegerItem mSpecStringPoolCount;
|
||||||
|
private final SingleBlockContainer<IntegerItem> mTypeIdOffsetContainer;
|
||||||
private final IntegerItem mTypeIdOffset;
|
private final IntegerItem mTypeIdOffset;
|
||||||
|
|
||||||
private final TypeStringPool mTypeStringPool;
|
private final TypeStringPool mTypeStringPool;
|
||||||
@ -44,13 +49,18 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
|||||||
super(ChunkType.PACKAGE, 3);
|
super(ChunkType.PACKAGE, 3);
|
||||||
this.mPackageId=new IntegerItem();
|
this.mPackageId=new IntegerItem();
|
||||||
this.mPackageName=new PackageName();
|
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.mSpecStringPool=new SpecStringPool(true);
|
||||||
|
|
||||||
this.mSpecTypePairArray=new SpecTypePairArray();
|
this.mSpecTypePairArray=new SpecTypePairArray();
|
||||||
@ -59,13 +69,15 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
|||||||
|
|
||||||
this.mEntriesGroup=new HashMap<>();
|
this.mEntriesGroup=new HashMap<>();
|
||||||
|
|
||||||
|
mPackageId.setBlockLoad(this);
|
||||||
|
|
||||||
addToHeader(mPackageId);
|
addToHeader(mPackageId);
|
||||||
addToHeader(mPackageName);
|
addToHeader(mPackageName);
|
||||||
addToHeader(mTypeStrings);
|
addToHeader(mTypeStringPoolOffset);
|
||||||
addToHeader(mLastPublicType);
|
addToHeader(mTypeStringPoolCount);
|
||||||
addToHeader(mKeyStrings);
|
addToHeader(mSpecStringPoolOffset);
|
||||||
addToHeader(mLastPublicKey);
|
addToHeader(mSpecStringPoolCount);
|
||||||
addToHeader(mTypeIdOffset);
|
addToHeader(mTypeIdOffsetContainer);
|
||||||
|
|
||||||
addChild(mTypeStringPool);
|
addChild(mTypeStringPool);
|
||||||
addChild(mSpecStringPool);
|
addChild(mSpecStringPool);
|
||||||
@ -73,6 +85,16 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
|||||||
addChild(mPackageLastBlocks);
|
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(){
|
public void removeEmpty(){
|
||||||
getSpecTypePairArray().removeEmptyPairs();
|
getSpecTypePairArray().removeEmptyPairs();
|
||||||
}
|
}
|
||||||
@ -115,33 +137,7 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
|||||||
public void setPackageName(String name){
|
public void setPackageName(String name){
|
||||||
mPackageName.set(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(){
|
public TypeStringPool getTypeStringPool(){
|
||||||
return mTypeStringPool;
|
return mTypeStringPool;
|
||||||
}
|
}
|
||||||
@ -250,9 +246,26 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
|||||||
return getSpecTypePairArray().listItems();
|
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);
|
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){
|
public void onEntryAdded(EntryBlock entryBlock){
|
||||||
updateEntry(entryBlock);
|
updateEntry(entryBlock);
|
||||||
@ -263,14 +276,18 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onChunkRefreshed() {
|
protected void onChunkRefreshed() {
|
||||||
refreshKeyStrings();
|
refreshTypeStringPoolOffset();
|
||||||
|
refreshTypeStringPoolCount();
|
||||||
|
refreshSpecStringPoolOffset();
|
||||||
|
refreshSpecStringCount();
|
||||||
|
refreshTypeIdOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSONObject toJson() {
|
public JSONObject toJson() {
|
||||||
JSONObject jsonObject=new JSONObject();
|
JSONObject jsonObject=new JSONObject();
|
||||||
jsonObject.put(NAME_id, getId());
|
jsonObject.put(NAME_package_id, getId());
|
||||||
jsonObject.put(NAME_name, getName());
|
jsonObject.put(NAME_package_name, getName());
|
||||||
jsonObject.put(NAME_specs, getSpecTypePairArray().toJson());
|
jsonObject.put(NAME_specs, getSpecTypePairArray().toJson());
|
||||||
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
|
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
|
||||||
if(libraryInfoArray.childesCount()>0){
|
if(libraryInfoArray.childesCount()>0){
|
||||||
@ -280,8 +297,8 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void fromJson(JSONObject json) {
|
public void fromJson(JSONObject json) {
|
||||||
setId(json.getInt(NAME_id));
|
setId(json.getInt(NAME_package_id));
|
||||||
setName(json.getString(NAME_name));
|
setName(json.getString(NAME_package_name));
|
||||||
getSpecTypePairArray().fromJson(json.getJSONArray(NAME_specs));
|
getSpecTypePairArray().fromJson(json.getJSONArray(NAME_specs));
|
||||||
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
|
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
|
||||||
libraryInfoArray.fromJson(json.optJSONArray(NAME_libraries));
|
libraryInfoArray.fromJson(json.optJSONArray(NAME_libraries));
|
||||||
@ -302,8 +319,9 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String NAME_id="id";
|
public static final String NAME_package_id = "package_id";
|
||||||
private static final String NAME_name="name";
|
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_specs="specs";
|
||||||
private static final String NAME_libraries="libraries";
|
private static final String NAME_libraries="libraries";
|
||||||
}
|
}
|
||||||
|
@ -130,10 +130,6 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void fromJson(JSONObject json) {
|
public void fromJson(JSONObject json) {
|
||||||
JSONArray jsonArray= json.optJSONArray(NAME_styled_strings);
|
|
||||||
if(jsonArray!=null){
|
|
||||||
getTableStringPool().fromJson(jsonArray);
|
|
||||||
}
|
|
||||||
getPackageArray().fromJson(json.getJSONArray(NAME_packages));
|
getPackageArray().fromJson(json.getJSONArray(NAME_packages));
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
@ -190,5 +186,5 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
|
|||||||
public static final String FILE_NAME="resources.arsc";
|
public static final String FILE_NAME="resources.arsc";
|
||||||
|
|
||||||
private static final String NAME_packages="packages";
|
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();
|
return mLineNumber.get();
|
||||||
}
|
}
|
||||||
public void setCommentReference(int val){
|
public void setCommentReference(int val){
|
||||||
mLineNumber.set(val);
|
mCommentReference.set(val);
|
||||||
}
|
}
|
||||||
public int getCommentReference(){
|
public int getCommentReference(){
|
||||||
return mCommentReference.get();
|
return mCommentReference.get();
|
||||||
@ -119,6 +119,21 @@ public class BaseXmlChunk extends BaseChunk {
|
|||||||
public String getUri(){
|
public String getUri(){
|
||||||
return getString(getNamespaceReference());
|
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(){
|
public ResXmlElement getParentResXmlElement(){
|
||||||
Block parent=getParent();
|
Block parent=getParent();
|
||||||
@ -137,6 +152,14 @@ public class BaseXmlChunk extends BaseChunk {
|
|||||||
@Override
|
@Override
|
||||||
protected void onChunkRefreshed() {
|
protected void onChunkRefreshed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onChunkLoaded(){
|
||||||
|
super.onChunkLoaded();
|
||||||
|
if(mCommentReference.get()!=-1){
|
||||||
|
String junk=getString(mCommentReference.get());
|
||||||
|
System.out.println(junk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
|
@ -160,7 +160,7 @@ public class ResXmlBlock extends BaseChunk implements JSONConvert<JSONObject> {
|
|||||||
buildResourceIds(attributeList);
|
buildResourceIds(attributeList);
|
||||||
Set<String> allStrings=recursiveStrings(json.optJSONObject(ResXmlBlock.NAME_element));
|
Set<String> allStrings=recursiveStrings(json.optJSONObject(ResXmlBlock.NAME_element));
|
||||||
ResXmlStringPool stringPool = getStringPool();
|
ResXmlStringPool stringPool = getStringPool();
|
||||||
stringPool.addAllStrings(allStrings);
|
stringPool.addStrings(allStrings);
|
||||||
stringPool.refresh();
|
stringPool.refresh();
|
||||||
}
|
}
|
||||||
private void buildResourceIds(List<JSONObject> attributeList){
|
private void buildResourceIds(List<JSONObject> attributeList){
|
||||||
|
@ -316,6 +316,20 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
|
|||||||
public void setResXmlText(ResXmlText xmlText){
|
public void setResXmlText(ResXmlText xmlText){
|
||||||
mResXmlTextContainer.setItem(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(){
|
private boolean isBalanced(){
|
||||||
return isElementBalanced() && isNamespaceBalanced();
|
return isElementBalanced() && isNamespaceBalanced();
|
||||||
@ -523,6 +537,17 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
|
|||||||
jsonObject.put(NAME_namespaces, nsList);
|
jsonObject.put(NAME_namespaces, nsList);
|
||||||
}
|
}
|
||||||
jsonObject.put(NAME_name, start.getName());
|
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();
|
String uri=start.getUri();
|
||||||
if(uri!=null){
|
if(uri!=null){
|
||||||
jsonObject.put(NAME_namespace_uri, uri);
|
jsonObject.put(NAME_namespace_uri, uri);
|
||||||
@ -560,6 +585,11 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTag(json.getString(NAME_name));
|
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);
|
String uri = json.optString(NAME_namespace_uri, null);
|
||||||
if(uri!=null){
|
if(uri!=null){
|
||||||
ResXmlStartNamespace ns = getStartNamespaceByUri(uri);
|
ResXmlStartNamespace ns = getStartNamespaceByUri(uri);
|
||||||
@ -618,6 +648,8 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
|
|||||||
public static final String NS_ANDROID_PREFIX = "android";
|
public static final String NS_ANDROID_PREFIX = "android";
|
||||||
|
|
||||||
static final String NAME_name = "name";
|
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_namespaces = "namespaces";
|
||||||
static final String NAME_namespace_uri = "namespace_uri";
|
static final String NAME_namespace_uri = "namespace_uri";
|
||||||
static final String NAME_namespace_prefix = "namespace_prefix";
|
static final String NAME_namespace_prefix = "namespace_prefix";
|
||||||
|
@ -11,6 +11,7 @@ public class ResXmlText extends BaseXmlChunk {
|
|||||||
super(ChunkType.XML_CDATA, 1);
|
super(ChunkType.XML_CDATA, 1);
|
||||||
this.mReserved=new IntegerItem();
|
this.mReserved=new IntegerItem();
|
||||||
addChild(mReserved);
|
addChild(mReserved);
|
||||||
|
setStringReference(0);
|
||||||
}
|
}
|
||||||
public String getText(){
|
public String getText(){
|
||||||
ResXmlString xmlString=getResXmlString(getTextReference());
|
ResXmlString xmlString=getResXmlString(getTextReference());
|
||||||
@ -38,7 +39,7 @@ public class ResXmlText extends BaseXmlChunk {
|
|||||||
public String toString(){
|
public String toString(){
|
||||||
String txt=getText();
|
String txt=getText();
|
||||||
if(txt!=null){
|
if(txt!=null){
|
||||||
return "TEXT: line="+getLineNumber()+" >"+txt+"<";
|
return txt;
|
||||||
}
|
}
|
||||||
return super.toString();
|
return super.toString();
|
||||||
}
|
}
|
||||||
|
@ -89,13 +89,6 @@ public class IntegerArray extends BlockItem {
|
|||||||
public final int size(){
|
public final int size(){
|
||||||
return getBytesLength()/4;
|
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){
|
public final void put(int index, int value){
|
||||||
int i=index*4;
|
int i=index*4;
|
||||||
byte[] bts = getBytesInternal();
|
byte[] bts = getBytesInternal();
|
||||||
@ -104,7 +97,6 @@ public class IntegerArray extends BlockItem {
|
|||||||
bts[i+1]= (byte) (value >>> 8 & 0xff);
|
bts[i+1]= (byte) (value >>> 8 & 0xff);
|
||||||
bts[i]= (byte) (value & 0xff);
|
bts[i]= (byte) (value & 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBytesChanged() {
|
public void onBytesChanged() {
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ public class SpecString extends StringItem {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public StyleItem getStyle(){
|
public StyleItem getStyle(){
|
||||||
|
// Spec (resource name) don't have style unless to obfuscate/confuse other decompilers
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ public class StringItem extends BlockItem implements JSONConvert<JSONObject> {
|
|||||||
if(styleItem==null){
|
if(styleItem==null){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !styleItem.isNull();
|
return styleItem.getSpanInfoList().size()>0;
|
||||||
}
|
}
|
||||||
public StyleItem getStyle(){
|
public StyleItem getStyle(){
|
||||||
BaseStringPool<?> stringPool=getStringPool();
|
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 UTF16LE_DECODER = StandardCharsets.UTF_16LE.newDecoder();
|
||||||
private final CharsetDecoder UTF8_DECODER = StandardCharsets.UTF_8.newDecoder();
|
private final CharsetDecoder UTF8_DECODER = StandardCharsets.UTF_8.newDecoder();
|
||||||
|
|
||||||
private static final String NAME_string="string";
|
public static final String NAME_string="string";
|
||||||
private static final String NAME_style="style";
|
public static final String NAME_style="style";
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,9 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
|
|||||||
@Override
|
@Override
|
||||||
public StyleSpanInfo get(int i) {
|
public StyleSpanInfo get(int i) {
|
||||||
int ref=getStringRef(i);
|
int ref=getStringRef(i);
|
||||||
|
if(ref<=0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return new StyleSpanInfo(
|
return new StyleSpanInfo(
|
||||||
getStringFromPool(ref),
|
getStringFromPool(ref),
|
||||||
getFirstChar(i),
|
getFirstChar(i),
|
||||||
@ -192,7 +195,7 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<StyleSpanInfo> spanInfoList = getSpanInfoList();
|
List<StyleSpanInfo> spanInfoList = getSpanInfoList();
|
||||||
if(spanInfoList.size()==0){
|
if(isEmpty(spanInfoList)){
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
StringBuilder builder=new StringBuilder();
|
StringBuilder builder=new StringBuilder();
|
||||||
@ -202,6 +205,9 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
|
|||||||
char ch=allChars[i];
|
char ch=allChars[i];
|
||||||
boolean lastAppend=false;
|
boolean lastAppend=false;
|
||||||
for(StyleSpanInfo info:spanInfoList){
|
for(StyleSpanInfo info:spanInfoList){
|
||||||
|
if(info==null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
boolean isLast=(info.getLast()==i);
|
boolean isLast=(info.getLast()==i);
|
||||||
if(info.getFirst()==i || isLast){
|
if(info.getFirst()==i || isLast){
|
||||||
if(isLast && !lastAppend){
|
if(isLast && !lastAppend){
|
||||||
@ -221,6 +227,17 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
|
|||||||
}
|
}
|
||||||
return builder.toString();
|
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
|
@Override
|
||||||
public void onBytesChanged() {
|
public void onBytesChanged() {
|
||||||
}
|
}
|
||||||
@ -262,10 +279,16 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
|
|||||||
JSONArray jsonArray=new JSONArray();
|
JSONArray jsonArray=new JSONArray();
|
||||||
int i=0;
|
int i=0;
|
||||||
for(StyleSpanInfo spanInfo:getSpanInfoList()){
|
for(StyleSpanInfo spanInfo:getSpanInfoList()){
|
||||||
|
if(spanInfo==null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
JSONObject jsonObjectSpan=spanInfo.toJson();
|
JSONObject jsonObjectSpan=spanInfo.toJson();
|
||||||
jsonArray.put(i, jsonObjectSpan);
|
jsonArray.put(i, jsonObjectSpan);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
if(i==0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
jsonObject.put(NAME_spans, jsonArray);
|
jsonObject.put(NAME_spans, jsonArray);
|
||||||
return jsonObject;
|
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 INTEGERS_COUNT = 3;
|
||||||
|
|
||||||
private static final int END_VALUE=0xFFFFFFFF;
|
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
|
@Override
|
||||||
public StyleItem getStyle(){
|
public StyleItem getStyle(){
|
||||||
|
// Type don't have style unless to obfuscate/confuse other decompilers
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ public class StyleSpanInfo implements JSONConvert<JSONObject> {
|
|||||||
return mTag +" ("+ mFirst +", "+ mLast +")";
|
return mTag +" ("+ mFirst +", "+ mLast +")";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String NAME_tag="tag";
|
public static final String NAME_tag="tag";
|
||||||
private static final String NAME_first="first";
|
public static final String NAME_first="first";
|
||||||
private static final String NAME_last="last";
|
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.BlockLoad;
|
||||||
import com.reandroid.lib.arsc.io.BlockReader;
|
import com.reandroid.lib.arsc.io.BlockReader;
|
||||||
import com.reandroid.lib.arsc.item.*;
|
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.JSONConvert;
|
||||||
import com.reandroid.lib.json.JSONArray;
|
import com.reandroid.lib.json.JSONArray;
|
||||||
|
import com.reandroid.lib.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -66,31 +67,60 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
|
|||||||
mUniqueMap=new HashMap<>();
|
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);
|
List<String> sortedList=new ArrayList<>(stringList);
|
||||||
sortedList.sort(this);
|
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();
|
refreshUniqueIdMap();
|
||||||
|
|
||||||
for(String str:sortedStringList){
|
|
||||||
getOrCreate(str);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// call this after modifying string values
|
// call this after modifying string values
|
||||||
public void refreshUniqueIdMap(){
|
public void refreshUniqueIdMap(){
|
||||||
reUpdateUniqueMap();
|
mUniqueMap.clear();
|
||||||
|
T[] allChildes=getStrings();
|
||||||
|
if(allChildes==null){
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
public void recreateStyles(){
|
int max=allChildes.length;
|
||||||
StyleArray styleArray = getStyleArray();
|
for(int i=0;i<max;i++){
|
||||||
//styleArray.clearChildes();
|
T item=allChildes[i];
|
||||||
StringArray<T> stringArray=getStringsArray();
|
if(item==null){
|
||||||
for(T stringItem:stringArray.listItems()){
|
continue;
|
||||||
if(StyleBuilder.hasStyle(stringItem)){
|
|
||||||
StyleBuilder.buildStyle(stringItem);
|
|
||||||
}
|
}
|
||||||
|
String str=item.getHtml();
|
||||||
|
if(str==null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StringGroup<T> group= getOrCreateGroup(str);
|
||||||
|
group.add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<T> removeUnusedStrings(){
|
public List<T> removeUnusedStrings(){
|
||||||
@ -175,30 +205,6 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
|
|||||||
mCountStrings.set(mArrayStrings.childesCount());
|
mCountStrings.set(mArrayStrings.childesCount());
|
||||||
return item;
|
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){
|
public final StyleItem getStyle(int index){
|
||||||
return mArrayStyles.get(index);
|
return mArrayStyles.get(index);
|
||||||
}
|
}
|
||||||
@ -259,7 +265,7 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onChunkLoaded() {
|
public void onChunkLoaded() {
|
||||||
reUpdateUniqueMap();
|
refreshUniqueIdMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -272,16 +278,121 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
|
|||||||
public JSONArray toJson() {
|
public JSONArray toJson() {
|
||||||
return getStringsArray().toJson();
|
return getStringsArray().toJson();
|
||||||
}
|
}
|
||||||
|
//Only for styled strings
|
||||||
@Override
|
@Override
|
||||||
public void fromJson(JSONArray json) {
|
public void fromJson(JSONArray json) {
|
||||||
getStringsArray().fromJson(json);
|
if(json==null){
|
||||||
refreshUniqueIdMap();
|
return;
|
||||||
|
}
|
||||||
|
loadStyledStrings(json);
|
||||||
refresh();
|
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
|
@Override
|
||||||
public int compare(String s1, String s2) {
|
public int compare(String s1, String s2) {
|
||||||
return s1.compareTo(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 UTF8_FLAG_VALUE=0x0100;
|
||||||
|
|
||||||
private static final short FLAG_UTF8 = 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.SpecStringArray;
|
||||||
import com.reandroid.lib.arsc.array.StringArray;
|
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.IntegerArray;
|
||||||
import com.reandroid.lib.arsc.item.IntegerItem;
|
import com.reandroid.lib.arsc.item.IntegerItem;
|
||||||
import com.reandroid.lib.arsc.item.SpecString;
|
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) {
|
StringArray<SpecString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
|
||||||
return new SpecStringArray(offsets, itemCount, itemStart, is_utf8);
|
return new SpecStringArray(offsets, itemCount, itemStart, is_utf8);
|
||||||
}
|
}
|
||||||
@Override
|
public PackageBlock getPackageBlock(){
|
||||||
public void recreateStyles(){
|
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.StringArray;
|
||||||
import com.reandroid.lib.arsc.array.TypeStringArray;
|
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.IntegerArray;
|
||||||
import com.reandroid.lib.arsc.item.IntegerItem;
|
import com.reandroid.lib.arsc.item.IntegerItem;
|
||||||
import com.reandroid.lib.arsc.item.TypeString;
|
import com.reandroid.lib.arsc.item.TypeString;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class TypeStringPool extends BaseStringPool<TypeString> {
|
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);
|
super(is_utf8);
|
||||||
|
this.mTypeIdOffset = typeIdOffset;
|
||||||
}
|
}
|
||||||
public TypeString getById(int id){
|
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){
|
public TypeString getOrCreate(int typeId, String typeName){
|
||||||
getStringsArray().ensureSize(typeId);
|
int size=typeId-mTypeIdOffset.get();
|
||||||
|
getStringsArray().ensureSize(size);
|
||||||
TypeString typeString=getById(typeId);
|
TypeString typeString=getById(typeId);
|
||||||
typeString.set(typeName);
|
typeString.set(typeName);
|
||||||
return typeString;
|
return typeString;
|
||||||
@ -24,7 +31,4 @@ public class TypeStringPool extends BaseStringPool<TypeString> {
|
|||||||
StringArray<TypeString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
|
StringArray<TypeString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
|
||||||
return new TypeStringArray(offsets, itemCount, itemStart, 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;
|
package com.reandroid.lib.arsc.pool.builder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class SpannedText {
|
public class SpannedText {
|
||||||
private String mLeftText;
|
|
||||||
private String mTag;
|
private String mTag;
|
||||||
private String mText;
|
private String mText;
|
||||||
private String mRightText;
|
private int mStart;
|
||||||
private List<SpannedText> mChildes;
|
private int mEnd;
|
||||||
|
private List<SpannedText> mChildes=new ArrayList<>();
|
||||||
public SpannedText(){
|
public SpannedText(){
|
||||||
}
|
}
|
||||||
public void parse(int start, String text){
|
public void parse(String text){
|
||||||
int i=text.indexOf('<', start);
|
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
|
@Override
|
||||||
public void onBytesChanged() {
|
public void onBytesChanged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
void onDataLoaded() {
|
||||||
|
|
||||||
}
|
}
|
||||||
int getInt(int offset){
|
int getInt(int offset){
|
||||||
byte[] bts = getBytesInternal();
|
byte[] bts = getBytesInternal();
|
||||||
|
@ -50,6 +50,9 @@ public abstract class BaseResValueItem extends BaseResValue implements ResValueI
|
|||||||
}
|
}
|
||||||
return mReferenceItem;
|
return mReferenceItem;
|
||||||
}
|
}
|
||||||
|
boolean hasTableReference(){
|
||||||
|
return mReferenceItem!=null;
|
||||||
|
}
|
||||||
boolean removeTableReference(){
|
boolean removeTableReference(){
|
||||||
ReferenceItem ref=mReferenceItem;
|
ReferenceItem ref=mReferenceItem;
|
||||||
if(ref==null){
|
if(ref==null){
|
||||||
|
@ -21,14 +21,14 @@ import java.util.List;
|
|||||||
|
|
||||||
public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
||||||
private ShortItem mHeaderSize;
|
private ShortItem mHeaderSize;
|
||||||
private ShortItem mFlags;
|
private ByteItem mFlagEntryType;
|
||||||
|
private ByteItem mByteFlagsB;
|
||||||
private IntegerItem mSpecReference;
|
private IntegerItem mSpecReference;
|
||||||
private BaseResValue mResValue;
|
private BaseResValue mResValue;
|
||||||
private boolean mUnLocked;
|
private boolean mUnLocked;
|
||||||
public EntryBlock() {
|
public EntryBlock() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResValueInt setValueAsBoolean(boolean val){
|
public ResValueInt setValueAsBoolean(boolean val){
|
||||||
ResValueInt resValueInt;
|
ResValueInt resValueInt;
|
||||||
BaseResValue res = getResValue();
|
BaseResValue res = getResValue();
|
||||||
@ -227,12 +227,11 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
removeTableReferences();
|
removeTableReferences();
|
||||||
removeSpecReferences();
|
removeSpecReferences();
|
||||||
}
|
}
|
||||||
|
private void setEntryTypeFlag(byte b){
|
||||||
public short getFlags(){
|
mFlagEntryType.set(b);
|
||||||
return mFlags.get();
|
|
||||||
}
|
}
|
||||||
public void setFlags(short sh){
|
private void setByteFlagsB(byte b){
|
||||||
mFlags.set(sh);
|
mByteFlagsB.set(b);
|
||||||
}
|
}
|
||||||
public IntegerItem getSpecReferenceBlock(){
|
public IntegerItem getSpecReferenceBlock(){
|
||||||
return mSpecReference;
|
return mSpecReference;
|
||||||
@ -260,8 +259,8 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
setNull(true);
|
setNull(true);
|
||||||
}else {
|
}else {
|
||||||
setNull(false);
|
setNull(false);
|
||||||
boolean is_complex=(resValue instanceof ResValueBag);
|
boolean is_bag=(resValue instanceof ResValueBag);
|
||||||
setFlagComplex(is_complex);
|
setEntryTypeBag(is_bag);
|
||||||
updatePackage();
|
updatePackage();
|
||||||
updateSpecRef();
|
updateSpecRef();
|
||||||
}
|
}
|
||||||
@ -271,7 +270,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(resValue!=null){
|
if(resValue!=null){
|
||||||
resValue.setIndex(3);
|
resValue.setIndex(4);
|
||||||
resValue.setParent(this);
|
resValue.setParent(this);
|
||||||
}
|
}
|
||||||
if(mResValue!=null){
|
if(mResValue!=null){
|
||||||
@ -284,14 +283,14 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
mResValue=resValue;
|
mResValue=resValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlagComplex(boolean is_complex){
|
public void setEntryTypeBag(boolean is_complex){
|
||||||
if(is_complex){
|
if(is_complex){
|
||||||
if(!isFlagsComplex()){
|
if(!isEntryTypeBag()){
|
||||||
setFlags(FLAG_COMPLEX);
|
setEntryTypeFlag(FLAG_VALUE_BAG);
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
if(isFlagsComplex()){
|
if(isEntryTypeBag()){
|
||||||
setFlags(FLAG_INT);
|
setEntryTypeFlag(FLAG_VALUE_INT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshHeaderSize();
|
refreshHeaderSize();
|
||||||
@ -314,7 +313,13 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
private void setName(String name){
|
private void setName(String name){
|
||||||
PackageBlock packageBlock=getPackageBlock();
|
PackageBlock packageBlock=getPackageBlock();
|
||||||
EntryGroup entryGroup = packageBlock.getEntryGroup(getResourceId());
|
EntryGroup entryGroup = packageBlock.getEntryGroup(getResourceId());
|
||||||
|
if(entryGroup!=null){
|
||||||
entryGroup.renameSpec(name);
|
entryGroup.renameSpec(name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SpecStringPool specStringPool= packageBlock.getSpecStringPool();
|
||||||
|
SpecString specString=specStringPool.getOrCreate(name);
|
||||||
|
setSpecReference(specString.getIndex());
|
||||||
}
|
}
|
||||||
public String getTypeName(){
|
public String getTypeName(){
|
||||||
TypeString typeString=getTypeString();
|
TypeString typeString=getTypeString();
|
||||||
@ -437,15 +442,18 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
}
|
}
|
||||||
mUnLocked =true;
|
mUnLocked =true;
|
||||||
this.mHeaderSize =new ShortItem();
|
this.mHeaderSize =new ShortItem();
|
||||||
this.mFlags =new ShortItem();
|
this.mFlagEntryType =new ByteItem();
|
||||||
|
this.mByteFlagsB=new ByteItem();
|
||||||
this.mSpecReference = new IntegerItem();
|
this.mSpecReference = new IntegerItem();
|
||||||
|
|
||||||
mHeaderSize.setIndex(0);
|
mHeaderSize.setIndex(0);
|
||||||
mFlags.setIndex(1);
|
mFlagEntryType.setIndex(1);
|
||||||
mSpecReference.setIndex(2);
|
mByteFlagsB.setIndex(2);
|
||||||
|
mSpecReference.setIndex(3);
|
||||||
|
|
||||||
mHeaderSize.setParent(this);
|
mHeaderSize.setParent(this);
|
||||||
mFlags.setParent(this);
|
mFlagEntryType.setParent(this);
|
||||||
|
mByteFlagsB.setParent(this);
|
||||||
mSpecReference.setParent(this);
|
mSpecReference.setParent(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -456,16 +464,19 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
removeAllReferences();
|
removeAllReferences();
|
||||||
mUnLocked =false;
|
mUnLocked =false;
|
||||||
mHeaderSize.setParent(null);
|
mHeaderSize.setParent(null);
|
||||||
mFlags.setParent(null);
|
mFlagEntryType.setParent(null);
|
||||||
|
mByteFlagsB.setParent(null);
|
||||||
mSpecReference.setParent(null);
|
mSpecReference.setParent(null);
|
||||||
|
|
||||||
mHeaderSize.setIndex(-1);
|
mHeaderSize.setIndex(-1);
|
||||||
mFlags.setIndex(-1);
|
mFlagEntryType.setIndex(-1);
|
||||||
|
mByteFlagsB.setIndex(-1);
|
||||||
mSpecReference.setIndex(-1);
|
mSpecReference.setIndex(-1);
|
||||||
removeResValue();
|
removeResValue();
|
||||||
|
|
||||||
this.mHeaderSize =null;
|
this.mHeaderSize =null;
|
||||||
this.mFlags =null;
|
this.mFlagEntryType =null;
|
||||||
|
this.mByteFlagsB =null;
|
||||||
this.mSpecReference =null;
|
this.mSpecReference =null;
|
||||||
}
|
}
|
||||||
private void removeResValue(){
|
private void removeResValue(){
|
||||||
@ -476,7 +487,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void refreshHeaderSize(){
|
private void refreshHeaderSize(){
|
||||||
if(isFlagsComplex()){
|
if(isEntryTypeBag()){
|
||||||
mHeaderSize.set(HEADER_COMPLEX);
|
mHeaderSize.set(HEADER_COMPLEX);
|
||||||
}else {
|
}else {
|
||||||
mHeaderSize.set(HEADER_INT);
|
mHeaderSize.set(HEADER_INT);
|
||||||
@ -487,21 +498,15 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BaseResValue resValue;
|
BaseResValue resValue;
|
||||||
if(isFlagsComplex()){
|
if(isEntryTypeBag()){
|
||||||
resValue=new ResValueBag();
|
resValue=new ResValueBag();
|
||||||
}else {
|
}else {
|
||||||
resValue=new ResValueInt();
|
resValue=new ResValueInt();
|
||||||
}
|
}
|
||||||
setResValueInternal(resValue);
|
setResValueInternal(resValue);
|
||||||
}
|
}
|
||||||
private boolean isFlagsComplex(){
|
private boolean isEntryTypeBag(){
|
||||||
return ((mFlags.get() & FLAG_COMPLEX_MASK) != 0);
|
return ((mFlagEntryType.get() & FLAG_BAG_ENTRY) != 0);
|
||||||
}
|
|
||||||
public boolean isArray(){
|
|
||||||
return ((mFlags.get() & FLAG_COMPLEX_MASK) != 0);
|
|
||||||
}
|
|
||||||
public void setIsArray(boolean is_array){
|
|
||||||
setFlagComplex(is_array);
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void setNull(boolean is_null){
|
public void setNull(boolean is_null){
|
||||||
@ -525,7 +530,8 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
byte[] results=mHeaderSize.getBytes();
|
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, mSpecReference.getBytes());
|
||||||
results=addBytes(results, mResValue.getBytes());
|
results=addBytes(results, mResValue.getBytes());
|
||||||
return results;
|
return results;
|
||||||
@ -558,7 +564,8 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
}
|
}
|
||||||
counter.addCount(countBytes());
|
counter.addCount(countBytes());
|
||||||
mHeaderSize.onCountUpTo(counter);
|
mHeaderSize.onCountUpTo(counter);
|
||||||
mFlags.onCountUpTo(counter);
|
mFlagEntryType.onCountUpTo(counter);
|
||||||
|
mByteFlagsB.onCountUpTo(counter);
|
||||||
mSpecReference.onCountUpTo(counter);
|
mSpecReference.onCountUpTo(counter);
|
||||||
mResValue.onCountUpTo(counter);
|
mResValue.onCountUpTo(counter);
|
||||||
}
|
}
|
||||||
@ -568,7 +575,8 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int result=mHeaderSize.writeBytes(stream);
|
int result=mHeaderSize.writeBytes(stream);
|
||||||
result+=mFlags.writeBytes(stream);
|
result+= mFlagEntryType.writeBytes(stream);
|
||||||
|
result+=mByteFlagsB.writeBytes(stream);
|
||||||
result+= mSpecReference.writeBytes(stream);
|
result+= mSpecReference.writeBytes(stream);
|
||||||
result+=mResValue.writeBytes(stream);
|
result+=mResValue.writeBytes(stream);
|
||||||
return result;
|
return result;
|
||||||
@ -612,12 +620,14 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
setNull(false);
|
setNull(false);
|
||||||
removeResValue();
|
removeResValue();
|
||||||
mHeaderSize.readBytes(reader);
|
mHeaderSize.readBytes(reader);
|
||||||
mFlags.readBytes(reader);
|
mFlagEntryType.readBytes(reader);
|
||||||
|
mByteFlagsB.readBytes(reader);
|
||||||
mSpecReference.readBytes(reader);
|
mSpecReference.readBytes(reader);
|
||||||
createResValue();
|
createResValue();
|
||||||
mResValue.readBytes(reader);
|
mResValue.readBytes(reader);
|
||||||
updatePackage();
|
updatePackage();
|
||||||
updateSpecRef();
|
updateSpecRef();
|
||||||
|
mResValue.onDataLoaded();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public JSONObject toJson() {
|
public JSONObject toJson() {
|
||||||
@ -626,7 +636,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
}
|
}
|
||||||
JSONObject jsonObject=new JSONObject();
|
JSONObject jsonObject=new JSONObject();
|
||||||
jsonObject.put(NAME_name, getSpecString().get());
|
jsonObject.put(NAME_name, getSpecString().get());
|
||||||
jsonObject.put(NAME_is_array, isArray());
|
jsonObject.put(NAME_is_array, isEntryTypeBag());
|
||||||
jsonObject.put(NAME_value, getResValue().toJson());
|
jsonObject.put(NAME_value, getResValue().toJson());
|
||||||
return jsonObject;
|
return jsonObject;
|
||||||
}
|
}
|
||||||
@ -636,16 +646,16 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
setNull(true);
|
setNull(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setName(json.getString(NAME_name));
|
|
||||||
setNull(false);
|
|
||||||
BaseResValue baseResValue;
|
BaseResValue baseResValue;
|
||||||
if(json.getBoolean(NAME_is_array)){
|
if(json.getBoolean(NAME_is_array)){
|
||||||
baseResValue=new ResValueInt();
|
|
||||||
}else {
|
|
||||||
baseResValue=new ResValueBag();
|
baseResValue=new ResValueBag();
|
||||||
|
}else {
|
||||||
|
baseResValue=new ResValueInt();
|
||||||
}
|
}
|
||||||
setResValue(baseResValue);
|
setResValue(baseResValue);
|
||||||
|
setName(json.getString(NAME_name));
|
||||||
baseResValue.fromJson(json.getJSONObject(NAME_value));
|
baseResValue.fromJson(json.getJSONObject(NAME_value));
|
||||||
|
mResValue.onDataLoaded();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
@ -698,10 +708,10 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
return builder.toString();
|
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 byte FLAG_VALUE_BAG = 0x0001;
|
||||||
private final static short FLAG_INT = 0x0000;
|
private final static byte FLAG_VALUE_INT = 0x00;
|
||||||
|
|
||||||
private final static short HEADER_COMPLEX=0x0010;
|
private final static short HEADER_COMPLEX=0x0010;
|
||||||
private final static short HEADER_INT=0x0008;
|
private final static short HEADER_INT=0x0008;
|
||||||
|
@ -28,6 +28,13 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon
|
|||||||
this.configSize.setBlockLoad(this);
|
this.configSize.setBlockLoad(this);
|
||||||
this.mValuesContainer.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
|
@Override
|
||||||
public void onBlockLoaded(BlockReader reader, Block sender) throws IOException {
|
public void onBlockLoaded(BlockReader reader, Block sender) throws IOException {
|
||||||
if(sender==configSize){
|
if(sender==configSize){
|
||||||
@ -980,11 +987,11 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon
|
|||||||
return keyboard;
|
return keyboard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NOKEYS;
|
return NONE;
|
||||||
}
|
}
|
||||||
public static Keyboard fromName(String name){
|
public static Keyboard fromName(String name){
|
||||||
if(name==null){
|
if(name==null){
|
||||||
return NOKEYS;
|
return NONE;
|
||||||
}
|
}
|
||||||
name=name.trim().toUpperCase();
|
name=name.trim().toUpperCase();
|
||||||
if(name.equals("12KEY")){
|
if(name.equals("12KEY")){
|
||||||
@ -995,7 +1002,7 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon
|
|||||||
return keyboard;
|
return keyboard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NOKEYS;
|
return NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public enum Navigation{
|
public enum Navigation{
|
||||||
|
@ -129,6 +129,12 @@ public class ResValueBag extends BaseResValue {
|
|||||||
mResValueBagItemArray.readBytes(reader);
|
mResValueBagItemArray.readBytes(reader);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
|
void onDataLoaded() {
|
||||||
|
for(ResValueBagItem item: mResValueBagItemArray.listItems()){
|
||||||
|
item.refreshTableReference();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
public JSONObject toJson() {
|
public JSONObject toJson() {
|
||||||
if(isNull()){
|
if(isNull()){
|
||||||
return null;
|
return null;
|
||||||
@ -147,9 +153,6 @@ public class ResValueBag extends BaseResValue {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
if(getParent()!=null){
|
|
||||||
return toJson().toString(4);
|
|
||||||
}
|
|
||||||
StringBuilder builder=new StringBuilder();
|
StringBuilder builder=new StringBuilder();
|
||||||
builder.append(getClass().getSimpleName());
|
builder.append(getClass().getSimpleName());
|
||||||
builder.append(": parent=");
|
builder.append(": parent=");
|
||||||
|
@ -102,14 +102,23 @@ public class ResValueBagItem extends BaseResValueItem{
|
|||||||
setInt(OFFSET_DATA, data);
|
setInt(OFFSET_DATA, data);
|
||||||
afterDataValueChanged();
|
afterDataValueChanged();
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public void onSetReference(int data){
|
||||||
|
setInt(OFFSET_DATA, data);
|
||||||
|
}
|
||||||
private void beforeDataValueChanged(){
|
private void beforeDataValueChanged(){
|
||||||
if(getValueType()==ValueType.STRING){
|
if(getValueType()==ValueType.STRING){
|
||||||
removeTableReference();
|
removeTableReference();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void afterDataValueChanged(){
|
private void afterDataValueChanged(){
|
||||||
|
refreshTableReference();
|
||||||
|
}
|
||||||
|
void refreshTableReference(){
|
||||||
if(getValueType()==ValueType.STRING){
|
if(getValueType()==ValueType.STRING){
|
||||||
addTableReference(getTableStringReference());
|
addTableReference(getTableStringReference());
|
||||||
|
}else {
|
||||||
|
removeTableReference();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public short getIdHigh(){
|
public short getIdHigh(){
|
||||||
@ -195,6 +204,14 @@ public class ResValueBagItem extends BaseResValueItem{
|
|||||||
builder.append(String.format("0x%08x", getId()));
|
builder.append(String.format("0x%08x", getId()));
|
||||||
builder.append(", data=");
|
builder.append(", data=");
|
||||||
builder.append(String.format("0x%08x", getData()));
|
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();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
private static final int OFFSET_ID=0;
|
private static final int OFFSET_ID=0;
|
||||||
|
@ -26,6 +26,16 @@ public class ResValueInt extends BaseResValueItem {
|
|||||||
return getData()!=0;
|
return getData()!=0;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
|
void onDataLoaded() {
|
||||||
|
if(getValueType()==ValueType.STRING){
|
||||||
|
if(!hasTableReference()){
|
||||||
|
addTableReference(getTableStringReference());
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
removeTableReference();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
public void setHeaderSize(short size) {
|
public void setHeaderSize(short size) {
|
||||||
setShort(OFFSET_SIZE, size);
|
setShort(OFFSET_SIZE, size);
|
||||||
}
|
}
|
||||||
@ -74,6 +84,10 @@ public class ResValueInt extends BaseResValueItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
|
public void onSetReference(int data){
|
||||||
|
setInt(OFFSET_DATA, data);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
public JSONObject toJson() {
|
public JSONObject toJson() {
|
||||||
if(isNull()){
|
if(isNull()){
|
||||||
return null;
|
return null;
|
||||||
|
@ -13,4 +13,5 @@ public interface ResValueItem {
|
|||||||
|
|
||||||
int getData();
|
int getData();
|
||||||
void setData(int data);
|
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 com.reandroid.lib.arsc.item.ReferenceItem;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class ResValueReference implements ReferenceItem {
|
public class ResValueReference implements ReferenceItem {
|
||||||
private final BaseResValueItem resValueItem;
|
private final BaseResValueItem resValueItem;
|
||||||
public ResValueReference(BaseResValueItem resValueItem){
|
public ResValueReference(BaseResValueItem resValueItem){
|
||||||
@ -12,10 +14,21 @@ public class ResValueReference implements ReferenceItem {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void set(int val) {
|
public void set(int val) {
|
||||||
resValueItem.setData(val);
|
resValueItem.onSetReference(val);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public int get() {
|
public int get() {
|
||||||
return resValueItem.getData();
|
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;
|
package com.reandroid.lib.json;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
@ -112,6 +113,16 @@ public class JSONArray extends JSONItem implements Iterable<Object> {
|
|||||||
}
|
}
|
||||||
this.myArrayList = new ArrayList<Object>(initialCapacity);
|
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
|
@Override
|
||||||
public Iterator<Object> iterator() {
|
public Iterator<Object> iterator() {
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package com.reandroid.lib.json;
|
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.annotation.Annotation;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@ -211,6 +208,14 @@ public class JSONObject extends JSONItem{
|
|||||||
this.map = new HashMap<String, Object>(initialCapacity);
|
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 {
|
public JSONObject accumulate(String key, Object value) throws JSONException {
|
||||||
testValidity(value);
|
testValidity(value);
|
||||||
Object object = this.opt(key);
|
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