mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-05-19 22:27:06 +02:00
V1.0.7
This commit is contained in:
parent
9d8421b0c7
commit
8bac03ca9b
@ -2,7 +2,7 @@
|
|||||||
apply plugin: 'java-library'
|
apply plugin: 'java-library'
|
||||||
|
|
||||||
group 'com.reandroid.lib.arsc'
|
group 'com.reandroid.lib.arsc'
|
||||||
version '1.0.6'
|
version '1.0.7'
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
129
src/main/java/com/reandroid/lib/apk/ApkBundle.java
Normal file
129
src/main/java/com/reandroid/lib/apk/ApkBundle.java
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package com.reandroid.lib.apk;
|
||||||
|
|
||||||
|
import com.reandroid.archive.APKArchive;
|
||||||
|
import com.reandroid.lib.arsc.chunk.TableBlock;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ApkBundle {
|
||||||
|
private final Map<String, ApkModule> mModulesMap;
|
||||||
|
private APKLogger apkLogger;
|
||||||
|
public ApkBundle(){
|
||||||
|
this.mModulesMap=new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApkModule mergeModules() throws IOException {
|
||||||
|
List<ApkModule> moduleList=getApkModuleList();
|
||||||
|
if(moduleList.size()==0){
|
||||||
|
throw new FileNotFoundException("Nothing to merge, empty modules");
|
||||||
|
}
|
||||||
|
ApkModule result=new ApkModule("merged", new APKArchive());
|
||||||
|
result.setAPKLogger(apkLogger);
|
||||||
|
ApkModule base=getBaseModule();
|
||||||
|
if(base==null){
|
||||||
|
base=getLargestTableModule();
|
||||||
|
}
|
||||||
|
result.merge(base);
|
||||||
|
for(ApkModule module:moduleList){
|
||||||
|
if(module==base){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.merge(module);
|
||||||
|
}
|
||||||
|
if(result.hasTableBlock()){
|
||||||
|
TableBlock tableBlock=result.getTableBlock();
|
||||||
|
tableBlock.sortPackages();
|
||||||
|
tableBlock.refresh();
|
||||||
|
}
|
||||||
|
result.sortApkFiles();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
private ApkModule getLargestTableModule() throws IOException {
|
||||||
|
ApkModule apkModule=null;
|
||||||
|
int chunkSize=0;
|
||||||
|
for(ApkModule module:getApkModuleList()){
|
||||||
|
if(!module.hasTableBlock()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TableBlock tableBlock=module.getTableBlock();
|
||||||
|
int size=tableBlock.getHeaderBlock().getChunkSize();
|
||||||
|
if(apkModule==null || size>chunkSize){
|
||||||
|
chunkSize=size;
|
||||||
|
apkModule=module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return apkModule;
|
||||||
|
}
|
||||||
|
public ApkModule getBaseModule(){
|
||||||
|
for(ApkModule module:getApkModuleList()){
|
||||||
|
if(module.isBaseModule()){
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public List<ApkModule> getApkModuleList(){
|
||||||
|
return new ArrayList<>(mModulesMap.values());
|
||||||
|
}
|
||||||
|
public void loadApkDirectory(File dir) throws IOException {
|
||||||
|
if(!dir.isDirectory()){
|
||||||
|
throw new FileNotFoundException("No such directory: "+dir);
|
||||||
|
}
|
||||||
|
List<File> apkList=ApkUtil.listFiles(dir, ".apk");
|
||||||
|
if(apkList.size()==0){
|
||||||
|
throw new FileNotFoundException("No '*.apk' files in directory: "+dir);
|
||||||
|
}
|
||||||
|
logMessage("Found apk files: "+apkList.size());
|
||||||
|
for(File file:apkList){
|
||||||
|
logVerbose("Loading: "+file.getName());
|
||||||
|
String name=ApkUtil.toModuleName(file);
|
||||||
|
ApkModule module=ApkModule.loadApkFile(file, name);
|
||||||
|
module.setAPKLogger(apkLogger);
|
||||||
|
addModule(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void addModule(ApkModule apkModule){
|
||||||
|
String name=apkModule.getModuleName();
|
||||||
|
mModulesMap.remove(name);
|
||||||
|
mModulesMap.put(name, apkModule);
|
||||||
|
}
|
||||||
|
public boolean containsApkModule(String moduleName){
|
||||||
|
return mModulesMap.containsKey(moduleName);
|
||||||
|
}
|
||||||
|
public ApkModule removeApkModule(String moduleName){
|
||||||
|
return mModulesMap.remove(moduleName);
|
||||||
|
}
|
||||||
|
public ApkModule getApkModule(String moduleName){
|
||||||
|
return mModulesMap.get(moduleName);
|
||||||
|
}
|
||||||
|
public List<String> listModuleNames(){
|
||||||
|
return new ArrayList<>(mModulesMap.keySet());
|
||||||
|
}
|
||||||
|
public int countModules(){
|
||||||
|
return mModulesMap.size();
|
||||||
|
}
|
||||||
|
public Collection<ApkModule> getModules(){
|
||||||
|
return mModulesMap.values();
|
||||||
|
}
|
||||||
|
public void setAPKLogger(APKLogger logger) {
|
||||||
|
this.apkLogger = logger;
|
||||||
|
}
|
||||||
|
private void logMessage(String msg) {
|
||||||
|
if(apkLogger!=null){
|
||||||
|
apkLogger.logMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void logError(String msg, Throwable tr) {
|
||||||
|
if(apkLogger!=null){
|
||||||
|
apkLogger.logError(msg, tr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void logVerbose(String msg) {
|
||||||
|
if(apkLogger!=null){
|
||||||
|
apkLogger.logVerbose(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package com.reandroid.lib.apk;
|
|||||||
|
|
||||||
import com.reandroid.archive.*;
|
import com.reandroid.archive.*;
|
||||||
import com.reandroid.lib.arsc.array.PackageArray;
|
import com.reandroid.lib.arsc.array.PackageArray;
|
||||||
|
import com.reandroid.lib.arsc.chunk.BaseChunk;
|
||||||
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;
|
||||||
import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
|
import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
|
||||||
@ -30,6 +31,16 @@ public class ApkModule {
|
|||||||
this.mUncompressedFiles=new UncompressedFiles();
|
this.mUncompressedFiles=new UncompressedFiles();
|
||||||
this.mUncompressedFiles.addPath(apkArchive);
|
this.mUncompressedFiles.addPath(apkArchive);
|
||||||
}
|
}
|
||||||
|
public List<DexFileInputSource> listDexFiles(){
|
||||||
|
List<DexFileInputSource> results=new ArrayList<>();
|
||||||
|
for(InputSource source:getApkArchive().listInputSources()){
|
||||||
|
if(DexFileInputSource.isDexName(source.getAlias())){
|
||||||
|
results.add(new DexFileInputSource(source.getAlias(), source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DexFileInputSource.sort(results);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
public boolean isBaseModule(){
|
public boolean isBaseModule(){
|
||||||
if(!hasAndroidManifestBlock()){
|
if(!hasAndroidManifestBlock()){
|
||||||
return false;
|
return false;
|
||||||
@ -223,6 +234,9 @@ public class ApkModule {
|
|||||||
tableBlock=((SplitJsonTableInputSource)inputSource).getTableBlock();
|
tableBlock=((SplitJsonTableInputSource)inputSource).getTableBlock();
|
||||||
}else if(inputSource instanceof SingleJsonTableInputSource){
|
}else if(inputSource instanceof SingleJsonTableInputSource){
|
||||||
tableBlock=((SingleJsonTableInputSource)inputSource).getTableBlock();
|
tableBlock=((SingleJsonTableInputSource)inputSource).getTableBlock();
|
||||||
|
}else if(inputSource instanceof BlockInputSource){
|
||||||
|
BaseChunk block = ((BlockInputSource<?>) inputSource).getBlock();
|
||||||
|
tableBlock=(TableBlock) block;
|
||||||
}else {
|
}else {
|
||||||
InputStream inputStream = inputSource.openStream();
|
InputStream inputStream = inputSource.openStream();
|
||||||
if(loadDefaultFramework){
|
if(loadDefaultFramework){
|
||||||
@ -246,6 +260,78 @@ public class ApkModule {
|
|||||||
this.loadDefaultFramework = loadDefaultFramework;
|
this.loadDefaultFramework = loadDefaultFramework;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void merge(ApkModule module) throws IOException {
|
||||||
|
if(module==null||module==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logMessage("Merging: "+module.getModuleName());
|
||||||
|
mergeDexFiles(module);
|
||||||
|
mergeTable(module);
|
||||||
|
mergeFiles(module);
|
||||||
|
getUncompressedFiles().merge(module.getUncompressedFiles());
|
||||||
|
}
|
||||||
|
private void mergeTable(ApkModule module) throws IOException {
|
||||||
|
if(!module.hasTableBlock()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logMessage("Merging resource table: "+module.getModuleName());
|
||||||
|
TableBlock exist;
|
||||||
|
if(!hasTableBlock()){
|
||||||
|
exist=new TableBlock();
|
||||||
|
BlockInputSource<TableBlock> inputSource=new BlockInputSource<>(TableBlock.FILE_NAME, exist);
|
||||||
|
getApkArchive().add(inputSource);
|
||||||
|
}else{
|
||||||
|
exist=getTableBlock();
|
||||||
|
}
|
||||||
|
TableBlock coming=module.getTableBlock();
|
||||||
|
exist.merge(coming);
|
||||||
|
}
|
||||||
|
private void mergeFiles(ApkModule module) throws IOException {
|
||||||
|
APKArchive archiveExist = getApkArchive();
|
||||||
|
APKArchive archiveComing = module.getApkArchive();
|
||||||
|
Map<String, InputSource> comingAlias=ApkUtil.toAliasMap(archiveComing.listInputSources());
|
||||||
|
Map<String, InputSource> existAlias=ApkUtil.toAliasMap(archiveExist.listInputSources());
|
||||||
|
UncompressedFiles uf=getUncompressedFiles();
|
||||||
|
for(InputSource inputSource:comingAlias.values()){
|
||||||
|
if(existAlias.containsKey(inputSource.getAlias())||existAlias.containsKey(inputSource.getName())){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(DexFileInputSource.isDexName(inputSource.getName())){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
logVerbose("Added: "+inputSource.getAlias());
|
||||||
|
archiveExist.add(inputSource);
|
||||||
|
uf.addPath(inputSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void mergeDexFiles(ApkModule module){
|
||||||
|
List<DexFileInputSource> existList=listDexFiles();
|
||||||
|
List<DexFileInputSource> comingList=module.listDexFiles();
|
||||||
|
APKArchive archive=getApkArchive();
|
||||||
|
int index=0;
|
||||||
|
if(existList.size()>0){
|
||||||
|
index=existList.get(existList.size()-1).getDexNumber();
|
||||||
|
if(index==0){
|
||||||
|
index=2;
|
||||||
|
}else {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(DexFileInputSource source:comingList){
|
||||||
|
String name=DexFileInputSource.getDexName(index);
|
||||||
|
DexFileInputSource add=new DexFileInputSource(name, source.getInputSource());
|
||||||
|
archive.add(add);
|
||||||
|
logMessage("Added ["+module.getModuleName()+"] "
|
||||||
|
+source.getAlias()+" -> "+name);
|
||||||
|
index++;
|
||||||
|
if(index==1){
|
||||||
|
index=2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void sortApkFiles(){
|
||||||
|
sortApkFiles(new ArrayList<>(getApkArchive().listInputSources()));
|
||||||
|
}
|
||||||
public void setAPKLogger(APKLogger logger) {
|
public void setAPKLogger(APKLogger logger) {
|
||||||
this.apkLogger = logger;
|
this.apkLogger = logger;
|
||||||
}
|
}
|
||||||
@ -264,6 +350,10 @@ public class ApkModule {
|
|||||||
apkLogger.logVerbose(msg);
|
apkLogger.logVerbose(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
return getModuleName();
|
||||||
|
}
|
||||||
public static ApkModule loadApkFile(File apkFile) throws IOException {
|
public static ApkModule loadApkFile(File apkFile) throws IOException {
|
||||||
return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME);
|
return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME);
|
||||||
}
|
}
|
||||||
@ -271,4 +361,39 @@ public class ApkModule {
|
|||||||
APKArchive archive=APKArchive.loadZippedApk(apkFile);
|
APKArchive archive=APKArchive.loadZippedApk(apkFile);
|
||||||
return new ApkModule(moduleName, archive);
|
return new ApkModule(moduleName, archive);
|
||||||
}
|
}
|
||||||
|
private static void sortApkFiles(List<InputSource> sourceList){
|
||||||
|
Comparator<InputSource> cmp=new Comparator<InputSource>() {
|
||||||
|
@Override
|
||||||
|
public int compare(InputSource in1, InputSource in2) {
|
||||||
|
return getSortName(in1).compareTo(getSortName(in2));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sourceList.sort(cmp);
|
||||||
|
int i=0;
|
||||||
|
for(InputSource inputSource:sourceList){
|
||||||
|
inputSource.setSort(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static String getSortName(InputSource inputSource){
|
||||||
|
String name=inputSource.getAlias();
|
||||||
|
StringBuilder builder=new StringBuilder();
|
||||||
|
if(name.equals(AndroidManifestBlock.FILE_NAME)){
|
||||||
|
builder.append("0 ");
|
||||||
|
}else if(name.equals(TableBlock.FILE_NAME)){
|
||||||
|
builder.append("1 ");
|
||||||
|
}else if(name.startsWith("classes")){
|
||||||
|
builder.append("2 ");
|
||||||
|
}else if(name.startsWith("res/")){
|
||||||
|
builder.append("3 ");
|
||||||
|
}else if(name.startsWith("lib/")){
|
||||||
|
builder.append("4 ");
|
||||||
|
}else if(name.startsWith("assets/")){
|
||||||
|
builder.append("5 ");
|
||||||
|
}else {
|
||||||
|
builder.append("6 ");
|
||||||
|
}
|
||||||
|
builder.append(name.toLowerCase());
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package com.reandroid.lib.apk;
|
package com.reandroid.lib.apk;
|
||||||
|
|
||||||
|
import com.reandroid.archive.InputSource;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
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){
|
||||||
@ -106,6 +107,21 @@ public class ApkUtil {
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
public static String toModuleName(File file){
|
||||||
|
String name=file.getName();
|
||||||
|
int i=name.lastIndexOf('.');
|
||||||
|
if(i>0){
|
||||||
|
name=name.substring(0,i);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
public static Map<String, InputSource> toAliasMap(Collection<InputSource> sourceList){
|
||||||
|
Map<String, InputSource> results=new HashMap<>();
|
||||||
|
for(InputSource inputSource:sourceList){
|
||||||
|
results.put(inputSource.getAlias(), inputSource);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
public static final String JSON_FILE_EXTENSION=".json";
|
public static final String JSON_FILE_EXTENSION=".json";
|
||||||
public static final String RES_JSON_NAME="res-json";
|
public static final String RES_JSON_NAME="res-json";
|
||||||
public static final String ROOT_NAME="root";
|
public static final String ROOT_NAME="root";
|
||||||
|
51
src/main/java/com/reandroid/lib/apk/DexFileInputSource.java
Normal file
51
src/main/java/com/reandroid/lib/apk/DexFileInputSource.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package com.reandroid.lib.apk;
|
||||||
|
|
||||||
|
import com.reandroid.archive.InputSource;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class DexFileInputSource extends RenamedInputSource<InputSource> implements Comparable<DexFileInputSource>{
|
||||||
|
public DexFileInputSource(String name, InputSource inputSource){
|
||||||
|
super(name, inputSource);
|
||||||
|
}
|
||||||
|
public int getDexNumber(){
|
||||||
|
return getDexNumber(getAlias());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int compareTo(DexFileInputSource source) {
|
||||||
|
return Integer.compare(getDexNumber(), source.getDexNumber());
|
||||||
|
}
|
||||||
|
public static void sort(List<DexFileInputSource> sourceList){
|
||||||
|
sourceList.sort(new Comparator<DexFileInputSource>() {
|
||||||
|
@Override
|
||||||
|
public int compare(DexFileInputSource s1, DexFileInputSource s2) {
|
||||||
|
return s1.compareTo(s2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public static boolean isDexName(String name){
|
||||||
|
return getDexNumber(name)>=0;
|
||||||
|
}
|
||||||
|
static String getDexName(int i){
|
||||||
|
if(i==0){
|
||||||
|
return "classes.dex";
|
||||||
|
}
|
||||||
|
return "classes"+i+".dex";
|
||||||
|
}
|
||||||
|
static int getDexNumber(String name){
|
||||||
|
Matcher matcher=PATTERN.matcher(name);
|
||||||
|
if(!matcher.find()){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
String num=matcher.group(1);
|
||||||
|
if(num.length()==0){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(num);
|
||||||
|
}
|
||||||
|
private static final Pattern PATTERN=Pattern.compile("^classes([0-9]*)\\.dex$");
|
||||||
|
|
||||||
|
}
|
40
src/main/java/com/reandroid/lib/apk/RenamedInputSource.java
Normal file
40
src/main/java/com/reandroid/lib/apk/RenamedInputSource.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package com.reandroid.lib.apk;
|
||||||
|
|
||||||
|
import com.reandroid.archive.InputSource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class RenamedInputSource<T extends InputSource> extends InputSource {
|
||||||
|
private final T inputSource;
|
||||||
|
public RenamedInputSource(String name, T input){
|
||||||
|
super(name);
|
||||||
|
this.inputSource=input;
|
||||||
|
super.setMethod(input.getMethod());
|
||||||
|
super.setSort(input.getSort());
|
||||||
|
}
|
||||||
|
public T getInputSource() {
|
||||||
|
return inputSource;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void close(InputStream inputStream) throws IOException {
|
||||||
|
getInputSource().close(inputStream);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public long getLength() throws IOException {
|
||||||
|
return getInputSource().getLength();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public long getCrc() throws IOException {
|
||||||
|
return getInputSource().getCrc();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public long write(OutputStream outputStream) throws IOException {
|
||||||
|
return getInputSource().write(outputStream);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public InputStream openStream() throws IOException {
|
||||||
|
return getInputSource().openStream();
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,7 @@ public class TableBlockJsonBuilder {
|
|||||||
for(File pkgDir:pkgDirList){
|
for(File pkgDir:pkgDirList){
|
||||||
scanPackageDirectory(tableBlock, pkgDir);
|
scanPackageDirectory(tableBlock, pkgDir);
|
||||||
}
|
}
|
||||||
|
tableBlock.sortPackages();
|
||||||
tableBlock.refresh();
|
tableBlock.refresh();
|
||||||
return tableBlock;
|
return tableBlock;
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,17 @@ public class UncompressedFiles implements JSONConvert<JSONObject> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(UncompressedFiles uf){
|
||||||
|
if(uf==null||uf==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(String path: uf.mPathList){
|
||||||
|
addPath(path);
|
||||||
|
}
|
||||||
|
for(String ext:uf.mExtensionList){
|
||||||
|
addExtension(ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
public void fromJson(File jsonFile) throws IOException {
|
public void fromJson(File jsonFile) throws IOException {
|
||||||
if(!jsonFile.isFile()){
|
if(!jsonFile.isFile()){
|
||||||
return;
|
return;
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
package com.reandroid.lib.arsc.array;
|
package com.reandroid.lib.arsc.array;
|
||||||
|
|
||||||
|
import com.reandroid.lib.arsc.chunk.TypeBlock;
|
||||||
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.value.BaseResValue;
|
||||||
import com.reandroid.lib.arsc.value.EntryBlock;
|
import com.reandroid.lib.arsc.value.EntryBlock;
|
||||||
|
import com.reandroid.lib.arsc.value.ResValueInt;
|
||||||
|
import com.reandroid.lib.arsc.value.ValueType;
|
||||||
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;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
|
||||||
public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSONConvert<JSONArray> {
|
public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSONConvert<JSONArray> {
|
||||||
public EntryBlockArray(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart){
|
public EntryBlockArray(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart){
|
||||||
@ -56,7 +62,6 @@ public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSO
|
|||||||
}
|
}
|
||||||
return jsonArray;
|
return jsonArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fromJson(JSONArray json) {
|
public void fromJson(JSONArray json) {
|
||||||
clearChildes();
|
clearChildes();
|
||||||
@ -74,6 +79,34 @@ public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSO
|
|||||||
}
|
}
|
||||||
refreshCountAndStart();
|
refreshCountAndStart();
|
||||||
}
|
}
|
||||||
|
public void merge(EntryBlockArray entryBlockArray){
|
||||||
|
if(entryBlockArray==null||entryBlockArray==this||entryBlockArray.isEmpty()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ensureSize(entryBlockArray.childesCount());
|
||||||
|
Iterator<EntryBlock> itr=entryBlockArray.iterator(true);
|
||||||
|
while (itr.hasNext()){
|
||||||
|
EntryBlock comingBlock=itr.next();
|
||||||
|
EntryBlock existingBlock=get(comingBlock.getIndex());
|
||||||
|
if(shouldMerge(existingBlock, comingBlock)){
|
||||||
|
existingBlock.merge(comingBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private boolean shouldMerge(EntryBlock exist, EntryBlock coming){
|
||||||
|
if(exist.isNull()){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(coming.isNull()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BaseResValue resVal = coming.getResValue();
|
||||||
|
if(resVal instanceof ResValueInt){
|
||||||
|
ValueType valueType=((ResValueInt)resVal).getValueType();
|
||||||
|
return valueType!=ValueType.INT_BOOLEAN;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return getClass().getSimpleName()+": size="+childesCount();
|
return getClass().getSimpleName()+": size="+childesCount();
|
||||||
|
@ -15,6 +15,25 @@ public class LibraryInfoArray extends BlockArray<LibraryInfo> implements JSONCon
|
|||||||
public LibraryInfoArray(IntegerItem infoCount){
|
public LibraryInfoArray(IntegerItem infoCount){
|
||||||
this.mInfoCount=infoCount;
|
this.mInfoCount=infoCount;
|
||||||
}
|
}
|
||||||
|
public LibraryInfo getOrCreate(int pkgId){
|
||||||
|
LibraryInfo info=getById(pkgId);
|
||||||
|
if(info!=null){
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
int index=childesCount();
|
||||||
|
ensureSize(index+1);
|
||||||
|
info=get(index);
|
||||||
|
info.setPackageId(pkgId);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
public LibraryInfo getById(int pkgId){
|
||||||
|
for(LibraryInfo info:listItems()){
|
||||||
|
if(pkgId==info.getPackageId()){
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public LibraryInfo newInstance() {
|
public LibraryInfo newInstance() {
|
||||||
return new LibraryInfo();
|
return new LibraryInfo();
|
||||||
@ -61,4 +80,13 @@ public class LibraryInfoArray extends BlockArray<LibraryInfo> implements JSONCon
|
|||||||
libraryInfo.fromJson(jsonObject);
|
libraryInfo.fromJson(jsonObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(LibraryInfoArray infoArray){
|
||||||
|
if(infoArray==null||infoArray==this||infoArray.childesCount()==0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(LibraryInfo info:infoArray.listItems()){
|
||||||
|
LibraryInfo exist=getOrCreate(info.getPackageId());
|
||||||
|
exist.merge(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,22 @@ import com.reandroid.lib.json.JSONArray;
|
|||||||
import com.reandroid.lib.json.JSONObject;
|
import com.reandroid.lib.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
public class PackageArray extends BlockArray<PackageBlock> implements BlockLoad, JSONConvert<JSONArray> {
|
public class PackageArray extends BlockArray<PackageBlock>
|
||||||
|
implements BlockLoad, JSONConvert<JSONArray>, Comparator<PackageBlock> {
|
||||||
private final IntegerItem mPackageCount;
|
private final IntegerItem mPackageCount;
|
||||||
public PackageArray(IntegerItem packageCount){
|
public PackageArray(IntegerItem packageCount){
|
||||||
this.mPackageCount=packageCount;
|
this.mPackageCount=packageCount;
|
||||||
mPackageCount.setBlockLoad(this);
|
mPackageCount.setBlockLoad(this);
|
||||||
}
|
}
|
||||||
|
public void sort(){
|
||||||
|
for(PackageBlock packageBlock:listItems()){
|
||||||
|
packageBlock.sortTypes();
|
||||||
|
}
|
||||||
|
sort(this);
|
||||||
|
}
|
||||||
public PackageBlock getOrCreate(byte pkgId){
|
public PackageBlock getOrCreate(byte pkgId){
|
||||||
PackageBlock packageBlock=getPackageBlockById(pkgId);
|
PackageBlock packageBlock=getPackageBlockById(pkgId);
|
||||||
if(packageBlock!=null){
|
if(packageBlock!=null){
|
||||||
@ -88,4 +96,17 @@ public class PackageArray extends BlockArray<PackageBlock> implements BlockLoad,
|
|||||||
packageBlock.fromJson(jsonObject);
|
packageBlock.fromJson(jsonObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(PackageArray packageArray){
|
||||||
|
if(packageArray==null||packageArray==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(PackageBlock packageBlock:packageArray.listItems()){
|
||||||
|
PackageBlock exist=getOrCreate((byte) packageBlock.getId());
|
||||||
|
exist.merge(packageBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int compare(PackageBlock p1, PackageBlock p2) {
|
||||||
|
return p1.compareTo(p2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.reandroid.lib.arsc.array;
|
package com.reandroid.lib.arsc.array;
|
||||||
|
|
||||||
import com.reandroid.lib.arsc.base.BlockArray;
|
import com.reandroid.lib.arsc.base.BlockArray;
|
||||||
|
import com.reandroid.lib.arsc.value.ResValueBag;
|
||||||
import com.reandroid.lib.arsc.value.ResValueBagItem;
|
import com.reandroid.lib.arsc.value.ResValueBagItem;
|
||||||
import com.reandroid.lib.json.JSONConvert;
|
import com.reandroid.lib.json.JSONConvert;
|
||||||
import com.reandroid.lib.json.JSONArray;
|
import com.reandroid.lib.json.JSONArray;
|
||||||
@ -22,6 +23,13 @@ public class ResValueBagItemArray extends BlockArray<ResValueBagItem> implements
|
|||||||
@Override
|
@Override
|
||||||
protected void onRefreshed() {
|
protected void onRefreshed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void clearChildes(){
|
||||||
|
for(ResValueBagItem bagItem:listItems()){
|
||||||
|
bagItem.onRemoved();
|
||||||
|
}
|
||||||
|
super.clearChildes();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public JSONArray toJson() {
|
public JSONArray toJson() {
|
||||||
@ -47,4 +55,17 @@ public class ResValueBagItemArray extends BlockArray<ResValueBagItem> implements
|
|||||||
get(i).fromJson(json.getJSONObject(i));
|
get(i).fromJson(json.getJSONObject(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(ResValueBagItemArray bagItemArray){
|
||||||
|
if(bagItemArray==null||bagItemArray==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearChildes();
|
||||||
|
int count=bagItemArray.childesCount();
|
||||||
|
ensureSize(count);
|
||||||
|
for(int i=0;i<count;i++){
|
||||||
|
ResValueBagItem coming=bagItemArray.get(i);
|
||||||
|
ResValueBagItem exist=get(i);
|
||||||
|
exist.merge(coming);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.reandroid.lib.arsc.array;
|
package com.reandroid.lib.arsc.array;
|
||||||
|
|
||||||
import com.reandroid.lib.arsc.base.BlockArray;
|
import com.reandroid.lib.arsc.base.BlockArray;
|
||||||
|
import com.reandroid.lib.arsc.chunk.PackageBlock;
|
||||||
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;
|
||||||
@ -226,6 +227,18 @@ public class SpecTypePairArray extends BlockArray<SpecTypePair>
|
|||||||
specTypePair.fromJson(jsonObject);
|
specTypePair.fromJson(jsonObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(SpecTypePairArray pairArray){
|
||||||
|
if(pairArray==null || pairArray==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(SpecTypePair typePair:pairArray.listItems()){
|
||||||
|
if(typePair.isEmpty()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SpecTypePair exist=getOrCreate(typePair.getTypeId());
|
||||||
|
exist.merge(typePair);
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public int compare(SpecTypePair typePair1, SpecTypePair typePair2) {
|
public int compare(SpecTypePair typePair1, SpecTypePair typePair2) {
|
||||||
return typePair1.compareTo(typePair2);
|
return typePair1.compareTo(typePair2);
|
||||||
|
@ -58,16 +58,27 @@ public class StyleArray extends OffsetBlockArray<StyleItem> implements JSONConve
|
|||||||
public JSONArray toJson() {
|
public JSONArray toJson() {
|
||||||
if(childesCount()==0){
|
if(childesCount()==0){
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
int i=0;
|
|
||||||
for(StyleItem styleItem:listItems()){
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void fromJson(JSONArray json) {
|
public void fromJson(JSONArray json) {
|
||||||
|
|
||||||
|
}
|
||||||
|
public void merge(StyleArray styleArray){
|
||||||
|
if(styleArray==null||styleArray==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(childesCount()!=0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int count=styleArray.childesCount();
|
||||||
|
ensureSize(count);
|
||||||
|
for(int i=0;i<count;i++){
|
||||||
|
StyleItem exist=get(i);
|
||||||
|
StyleItem coming=styleArray.get(i);
|
||||||
|
exist.merge(coming);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private static final byte END_BYTE= (byte) 0xFF;
|
private static final byte END_BYTE= (byte) 0xFF;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import com.reandroid.lib.arsc.base.Block;
|
|||||||
import com.reandroid.lib.arsc.base.BlockArray;
|
import com.reandroid.lib.arsc.base.BlockArray;
|
||||||
import com.reandroid.lib.arsc.chunk.SpecBlock;
|
import com.reandroid.lib.arsc.chunk.SpecBlock;
|
||||||
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.header.HeaderBlock;
|
import com.reandroid.lib.arsc.header.HeaderBlock;
|
||||||
import com.reandroid.lib.arsc.io.BlockReader;
|
import com.reandroid.lib.arsc.io.BlockReader;
|
||||||
import com.reandroid.lib.arsc.item.TypeString;
|
import com.reandroid.lib.arsc.item.TypeString;
|
||||||
@ -277,6 +278,15 @@ public class TypeBlockArray extends BlockArray<TypeBlock>
|
|||||||
typeBlock.fromJson(jsonObject);
|
typeBlock.fromJson(jsonObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(TypeBlockArray typeBlockArray){
|
||||||
|
if(typeBlockArray==null||typeBlockArray==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(TypeBlock typeBlock:typeBlockArray.listItems()){
|
||||||
|
TypeBlock block=getOrCreate(typeBlock.getResConfig());
|
||||||
|
block.merge(typeBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public int compare(TypeBlock typeBlock1, TypeBlock typeBlock2) {
|
public int compare(TypeBlock typeBlock1, TypeBlock typeBlock2) {
|
||||||
return typeBlock1.compareTo(typeBlock2);
|
return typeBlock1.compareTo(typeBlock2);
|
||||||
|
@ -50,10 +50,15 @@ public class LibraryBlock extends BaseChunk {
|
|||||||
mLibCount.set(count);
|
mLibCount.set(count);
|
||||||
mLibraryInfoArray.setChildesCount(count);
|
mLibraryInfoArray.setChildesCount(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onChunkRefreshed() {
|
protected void onChunkRefreshed() {
|
||||||
mLibCount.set(mLibraryInfoArray.childesCount());
|
mLibCount.set(mLibraryInfoArray.childesCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void merge(LibraryBlock libraryBlock){
|
||||||
|
if(libraryBlock==null||libraryBlock==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getLibraryInfoArray().merge(libraryBlock.getLibraryInfoArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.reandroid.lib.arsc.chunk;
|
package com.reandroid.lib.arsc.chunk;
|
||||||
|
|
||||||
import com.reandroid.lib.arsc.array.LibraryInfoArray;
|
import com.reandroid.lib.arsc.array.LibraryInfoArray;
|
||||||
|
import com.reandroid.lib.arsc.array.PackageArray;
|
||||||
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;
|
||||||
@ -24,7 +25,8 @@ import java.io.IOException;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JSONObject> {
|
public class PackageBlock extends BaseChunk
|
||||||
|
implements BlockLoad, JSONConvert<JSONObject>, Comparable<PackageBlock> {
|
||||||
private final IntegerItem mPackageId;
|
private final IntegerItem mPackageId;
|
||||||
private final PackageName mPackageName;
|
private final PackageName mPackageName;
|
||||||
|
|
||||||
@ -136,7 +138,7 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JS
|
|||||||
return mSpecTypePairArray;
|
return mSpecTypePairArray;
|
||||||
}
|
}
|
||||||
public Collection<LibraryInfo> listLibraryInfo(){
|
public Collection<LibraryInfo> listLibraryInfo(){
|
||||||
return mLibraryBlock.listLibraryInfo();
|
return getLibraryBlock().listLibraryInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLibrary(LibraryBlock libraryBlock){
|
public void addLibrary(LibraryBlock libraryBlock){
|
||||||
@ -148,7 +150,10 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void addLibraryInfo(LibraryInfo info){
|
public void addLibraryInfo(LibraryInfo info){
|
||||||
mLibraryBlock.addLibraryInfo(info);
|
getLibraryBlock().addLibraryInfo(info);
|
||||||
|
}
|
||||||
|
private LibraryBlock getLibraryBlock(){
|
||||||
|
return mLibraryBlock;
|
||||||
}
|
}
|
||||||
public Set<Integer> listResourceIds(){
|
public Set<Integer> listResourceIds(){
|
||||||
return mEntriesGroup.keySet();
|
return mEntriesGroup.keySet();
|
||||||
@ -291,6 +296,22 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JS
|
|||||||
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
|
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
|
||||||
libraryInfoArray.fromJson(json.optJSONArray(NAME_libraries));
|
libraryInfoArray.fromJson(json.optJSONArray(NAME_libraries));
|
||||||
}
|
}
|
||||||
|
public void merge(PackageBlock packageBlock){
|
||||||
|
if(packageBlock==null||packageBlock==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(getId()!=packageBlock.getId()){
|
||||||
|
throw new IllegalArgumentException("Can not merge different id packages: "
|
||||||
|
+getId()+"!="+packageBlock.getId());
|
||||||
|
}
|
||||||
|
setName(packageBlock.getName());
|
||||||
|
getLibraryBlock().merge(packageBlock.getLibraryBlock());
|
||||||
|
getSpecTypePairArray().merge(packageBlock.getSpecTypePairArray());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int compareTo(PackageBlock pkg) {
|
||||||
|
return Integer.compare(getId(), pkg.getId());
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
StringBuilder builder=new StringBuilder();
|
StringBuilder builder=new StringBuilder();
|
||||||
|
@ -30,6 +30,9 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
|
|||||||
addChild(mTableStringPool);
|
addChild(mTableStringPool);
|
||||||
addChild(mPackageArray);
|
addChild(mPackageArray);
|
||||||
}
|
}
|
||||||
|
public void sortPackages(){
|
||||||
|
getPackageArray().sort();
|
||||||
|
}
|
||||||
public Collection<PackageBlock> listPackages(){
|
public Collection<PackageBlock> listPackages(){
|
||||||
return getPackageArray().listItems();
|
return getPackageArray().listItems();
|
||||||
}
|
}
|
||||||
@ -138,6 +141,16 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
|
|||||||
getPackageArray().fromJson(json.getJSONArray(NAME_packages));
|
getPackageArray().fromJson(json.getJSONArray(NAME_packages));
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
public void merge(TableBlock tableBlock){
|
||||||
|
if(tableBlock==null||tableBlock==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(getPackageArray().childesCount()==0){
|
||||||
|
getTableStringPool().merge(tableBlock.getTableStringPool());
|
||||||
|
}
|
||||||
|
getPackageArray().merge(tableBlock.getPackageArray());
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
public static TableBlock loadWithAndroidFramework(InputStream inputStream) throws IOException{
|
public static TableBlock loadWithAndroidFramework(InputStream inputStream) throws IOException{
|
||||||
TableBlock tableBlock=load(inputStream);
|
TableBlock tableBlock=load(inputStream);
|
||||||
tableBlock.addFramework(Frameworks.getAndroid());
|
tableBlock.addFramework(Frameworks.getAndroid());
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.reandroid.lib.arsc.chunk;
|
package com.reandroid.lib.arsc.chunk;
|
||||||
|
|
||||||
import com.reandroid.lib.arsc.array.EntryBlockArray;
|
import com.reandroid.lib.arsc.array.EntryBlockArray;
|
||||||
|
import com.reandroid.lib.arsc.array.TypeBlockArray;
|
||||||
import com.reandroid.lib.arsc.base.Block;
|
import com.reandroid.lib.arsc.base.Block;
|
||||||
import com.reandroid.lib.arsc.container.SpecTypePair;
|
import com.reandroid.lib.arsc.container.SpecTypePair;
|
||||||
import com.reandroid.lib.arsc.item.IntegerArray;
|
import com.reandroid.lib.arsc.item.IntegerArray;
|
||||||
@ -138,6 +139,17 @@ public class TypeBlock extends BaseTypeBlock
|
|||||||
getEntryBlockArray().fromJson(json.getJSONArray("entries"));
|
getEntryBlockArray().fromJson(json.getJSONArray("entries"));
|
||||||
getResConfig().fromJson(json.getJSONObject("config"));
|
getResConfig().fromJson(json.getJSONObject("config"));
|
||||||
}
|
}
|
||||||
|
public void merge(TypeBlock typeBlock){
|
||||||
|
if(typeBlock==null||typeBlock==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(getTypeId() != typeBlock.getTypeId()){
|
||||||
|
throw new IllegalArgumentException("Can not merge different id types: "
|
||||||
|
+getTypeId()+"!="+typeBlock.getTypeId());
|
||||||
|
}
|
||||||
|
setTypeName(typeBlock.getTypeName());
|
||||||
|
getEntryBlockArray().merge(typeBlock.getEntryBlockArray());
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(TypeBlock typeBlock) {
|
public int compareTo(TypeBlock typeBlock) {
|
||||||
int id1=getTypeId();
|
int id1=getTypeId();
|
||||||
|
@ -18,12 +18,17 @@ public class AndroidManifestBlock extends ResXmlBlock{
|
|||||||
public ResXmlElement getMainActivity(){
|
public ResXmlElement getMainActivity(){
|
||||||
for(ResXmlElement activity:listActivities()){
|
for(ResXmlElement activity:listActivities()){
|
||||||
for(ResXmlElement intentFilter:activity.listElements(TAG_intent_filter)){
|
for(ResXmlElement intentFilter:activity.listElements(TAG_intent_filter)){
|
||||||
ResXmlAttribute attribute = intentFilter.searchAttributeByResourceId(ID_name);
|
for(ResXmlElement action:intentFilter.listElements(TAG_action)){
|
||||||
|
ResXmlAttribute attribute = action.searchAttributeByResourceId(ID_name);
|
||||||
|
if(attribute==null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if(VALUE_android_intent_action_MAIN.equals(attribute.getValueAsString())){
|
if(VALUE_android_intent_action_MAIN.equals(attribute.getValueAsString())){
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public List<ResXmlElement> listActivities(){
|
public List<ResXmlElement> listActivities(){
|
||||||
@ -294,6 +299,8 @@ public class AndroidManifestBlock extends ResXmlBlock{
|
|||||||
public static final String NAME_versionCode = "versionCode";
|
public static final String NAME_versionCode = "versionCode";
|
||||||
public static final String NAME_versionName = "versionName";
|
public static final String NAME_versionName = "versionName";
|
||||||
public static final String NAME_name = "name";
|
public static final String NAME_name = "name";
|
||||||
|
public static final String NAME_extractNativeLibs = "extractNativeLibs";
|
||||||
|
public static final String NAME_isSplitRequired = "isSplitRequired";
|
||||||
|
|
||||||
public static final int ID_name = 0x01010003;
|
public static final int ID_name = 0x01010003;
|
||||||
public static final int ID_compileSdkVersion = 0x01010572;
|
public static final int ID_compileSdkVersion = 0x01010572;
|
||||||
@ -302,6 +309,8 @@ public class AndroidManifestBlock extends ResXmlBlock{
|
|||||||
public static final int ID_host = 0x01010028;
|
public static final int ID_host = 0x01010028;
|
||||||
public static final int ID_configChanges = 0x0101001f;
|
public static final int ID_configChanges = 0x0101001f;
|
||||||
public static final int ID_screenOrientation = 0x0101001e;
|
public static final int ID_screenOrientation = 0x0101001e;
|
||||||
|
public static final int ID_extractNativeLibs = 0x010104ea;
|
||||||
|
public static final int ID_isSplitRequired = 0x01010591;
|
||||||
|
|
||||||
public static final String VALUE_android_intent_action_MAIN = "android.intent.action.MAIN";
|
public static final String VALUE_android_intent_action_MAIN = "android.intent.action.MAIN";
|
||||||
|
|
||||||
|
@ -159,6 +159,16 @@ public class SpecTypePair extends BlockContainer<Block>
|
|||||||
getSpecBlock().setTypeId((byte) json.getInt("id"));
|
getSpecBlock().setTypeId((byte) json.getInt("id"));
|
||||||
getTypeBlockArray().fromJson(json.getJSONArray("types"));
|
getTypeBlockArray().fromJson(json.getJSONArray("types"));
|
||||||
}
|
}
|
||||||
|
public void merge(SpecTypePair typePair){
|
||||||
|
if(typePair==null||typePair==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(getTypeId() != typePair.getTypeId()){
|
||||||
|
throw new IllegalArgumentException("Can not merge different id types: "
|
||||||
|
+getTypeId()+"!="+typePair.getTypeId());
|
||||||
|
}
|
||||||
|
getTypeBlockArray().merge(typePair.getTypeBlockArray());
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(SpecTypePair specTypePair) {
|
public int compareTo(SpecTypePair specTypePair) {
|
||||||
return Integer.compare(getTypeId(), specTypePair.getTypeId());
|
return Integer.compare(getTypeId(), specTypePair.getTypeId());
|
||||||
|
@ -5,6 +5,23 @@ public class ByteItem extends BlockItem {
|
|||||||
public ByteItem() {
|
public ByteItem() {
|
||||||
super(1);
|
super(1);
|
||||||
}
|
}
|
||||||
|
public boolean getBit(int index){
|
||||||
|
return ((get()>>index) & 0x1) == 1;
|
||||||
|
}
|
||||||
|
public void putBit(int index, boolean bit){
|
||||||
|
int val=get();
|
||||||
|
int left=val>>index;
|
||||||
|
if(bit){
|
||||||
|
left=left|0x1;
|
||||||
|
}else {
|
||||||
|
left=left & 0xFE;
|
||||||
|
}
|
||||||
|
left=left<<index;
|
||||||
|
index=8-index;
|
||||||
|
int right=(0xFF>>index) & val;
|
||||||
|
val=left|right;
|
||||||
|
set((byte) val);
|
||||||
|
}
|
||||||
public void set(byte b){
|
public void set(byte b){
|
||||||
getBytesInternal()[0]=b;
|
getBytesInternal()[0]=b;
|
||||||
}
|
}
|
||||||
|
@ -307,6 +307,14 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
|
|||||||
addSpanInfo(spanInfo.getTag(), spanInfo.getFirst(), spanInfo.getLast());
|
addSpanInfo(spanInfo.getTag(), spanInfo.getFirst(), spanInfo.getLast());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(StyleItem styleItem){
|
||||||
|
if(styleItem==null||styleItem==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(int[] info:styleItem.getIntSpanInfoList()){
|
||||||
|
addStylePiece(info[0], info[1], info[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "Spans count = "+getSpanInfoList().size();
|
return "Spans count = "+getSpanInfoList().size();
|
||||||
|
@ -15,4 +15,22 @@ public class TableStringPool extends BaseStringPool<TableString> {
|
|||||||
StringArray<TableString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
|
StringArray<TableString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
|
||||||
return new TableStringArray(offsets, itemCount, itemStart, is_utf8);
|
return new TableStringArray(offsets, itemCount, itemStart, is_utf8);
|
||||||
}
|
}
|
||||||
|
public void merge(TableStringPool stringPool){
|
||||||
|
if(stringPool==null||stringPool==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StringArray<TableString> existArray = getStringsArray();
|
||||||
|
if(existArray.childesCount()!=0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StringArray<TableString> comingArray = stringPool.getStringsArray();
|
||||||
|
int count=comingArray.childesCount();
|
||||||
|
existArray.ensureSize(count);
|
||||||
|
for(int i=0;i<count;i++){
|
||||||
|
TableString exist = existArray.get(i);
|
||||||
|
TableString coming = comingArray.get(i);
|
||||||
|
exist.set(coming.get());
|
||||||
|
}
|
||||||
|
getStyleArray().merge(stringPool.getStyleArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,9 @@ public abstract class BaseResValue extends BlockItem implements JSONConvert<JSON
|
|||||||
super(bytesLength);
|
super(bytesLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onRemoved(){
|
||||||
|
}
|
||||||
|
|
||||||
public EntryBlock getEntryBlock(){
|
public EntryBlock getEntryBlock(){
|
||||||
Block parent=getParent();
|
Block parent=getParent();
|
||||||
while(parent!=null){
|
while(parent!=null){
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package com.reandroid.lib.arsc.value;
|
package com.reandroid.lib.arsc.value;
|
||||||
|
|
||||||
|
import com.reandroid.lib.arsc.array.EntryBlockArray;
|
||||||
import com.reandroid.lib.arsc.base.Block;
|
import com.reandroid.lib.arsc.base.Block;
|
||||||
import com.reandroid.lib.arsc.base.BlockCounter;
|
import com.reandroid.lib.arsc.base.BlockCounter;
|
||||||
import com.reandroid.lib.arsc.chunk.PackageBlock;
|
import com.reandroid.lib.arsc.chunk.PackageBlock;
|
||||||
|
import com.reandroid.lib.arsc.chunk.SpecBlock;
|
||||||
import com.reandroid.lib.arsc.chunk.TableBlock;
|
import com.reandroid.lib.arsc.chunk.TableBlock;
|
||||||
import com.reandroid.lib.arsc.chunk.TypeBlock;
|
import com.reandroid.lib.arsc.chunk.TypeBlock;
|
||||||
import com.reandroid.lib.arsc.group.EntryGroup;
|
import com.reandroid.lib.arsc.group.EntryGroup;
|
||||||
@ -17,6 +19,7 @@ import com.reandroid.lib.json.JSONObject;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
||||||
@ -228,41 +231,23 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
removeSpecReferences();
|
removeSpecReferences();
|
||||||
}
|
}
|
||||||
public void setEntryTypeBag(boolean b){
|
public void setEntryTypeBag(boolean b){
|
||||||
int val=mFlagEntryType.get();
|
mFlagEntryType.putBit(0, b);
|
||||||
if(b){
|
|
||||||
val=val|0x1;
|
|
||||||
}else {
|
|
||||||
val=val&0xFE;
|
|
||||||
}
|
|
||||||
mFlagEntryType.set((byte) val);
|
|
||||||
refreshHeaderSize();
|
refreshHeaderSize();
|
||||||
}
|
}
|
||||||
public boolean isEntryTypeBag(){
|
public boolean isEntryTypeBag(){
|
||||||
return ((mFlagEntryType.get() & 0x1) != 0);
|
return mFlagEntryType.getBit(0);
|
||||||
}
|
}
|
||||||
public void setEntryTypeShared(boolean b){
|
public void setEntryTypeShared(boolean b){
|
||||||
int val=mFlagEntryType.get();
|
mFlagEntryType.putBit(1, b);
|
||||||
if(b){
|
|
||||||
val=val|0x2;
|
|
||||||
}else {
|
|
||||||
val=val&0xFD;
|
|
||||||
}
|
|
||||||
mFlagEntryType.set((byte) val);
|
|
||||||
}
|
}
|
||||||
public boolean isEntryTypeShared(){
|
public boolean isEntryTypeShared(){
|
||||||
return (mFlagEntryType.get() & 0x2) !=0;
|
return mFlagEntryType.getBit(1);
|
||||||
}
|
}
|
||||||
public void setEntryTypePublic(boolean b){
|
public void setEntryTypePublic(boolean b){
|
||||||
int val=mFlagEntryType.get();
|
mFlagEntryType.putBit(2, b);
|
||||||
if(b){
|
|
||||||
val=val|0x4;
|
|
||||||
}else {
|
|
||||||
val=val&0xFB;
|
|
||||||
}
|
|
||||||
mFlagEntryType.set((byte) val);
|
|
||||||
}
|
}
|
||||||
public boolean isEntryTypePublic(){
|
public boolean isEntryTypePublic(){
|
||||||
return (mFlagEntryType.get() & 0x4) !=0;
|
return mFlagEntryType.getBit(2);
|
||||||
}
|
}
|
||||||
private void setByteFlagsB(byte b){
|
private void setByteFlagsB(byte b){
|
||||||
mByteFlagsB.set(b);
|
mByteFlagsB.set(b);
|
||||||
@ -310,6 +295,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
if(mResValue!=null){
|
if(mResValue!=null){
|
||||||
mResValue.setIndex(-1);
|
mResValue.setIndex(-1);
|
||||||
mResValue.setParent(null);
|
mResValue.setParent(null);
|
||||||
|
mResValue.onRemoved();
|
||||||
}
|
}
|
||||||
if(resValue!=null){
|
if(resValue!=null){
|
||||||
setNull(false);
|
setNull(false);
|
||||||
@ -685,6 +671,41 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
|
|||||||
baseResValue.fromJson(json.getJSONObject(NAME_value));
|
baseResValue.fromJson(json.getJSONObject(NAME_value));
|
||||||
mResValue.onDataLoaded();
|
mResValue.onDataLoaded();
|
||||||
}
|
}
|
||||||
|
public void merge(EntryBlock entryBlock){
|
||||||
|
if(entryBlock==null||entryBlock==this||entryBlock.isNull()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String name=entryBlock.getName();
|
||||||
|
if(name==null){
|
||||||
|
name="";
|
||||||
|
}
|
||||||
|
if(!entryBlock.isEntryTypeBag()){
|
||||||
|
ResValueInt comingValue = (ResValueInt) entryBlock.getResValue();
|
||||||
|
ResValueInt exist = getOrCreateResValueInt();
|
||||||
|
setResValue(exist);
|
||||||
|
exist.merge(comingValue);
|
||||||
|
}else {
|
||||||
|
ResValueBag comingValue = (ResValueBag) entryBlock.getResValue();
|
||||||
|
ResValueBag exist=getOrCreateResValueBag();
|
||||||
|
setResValue(exist);
|
||||||
|
exist.merge(comingValue);
|
||||||
|
}
|
||||||
|
SpecString spec = getPackageBlock()
|
||||||
|
.getSpecStringPool().getOrCreate(name);
|
||||||
|
setSpecReference(spec.getIndex());
|
||||||
|
}
|
||||||
|
private ResValueBag getOrCreateResValueBag(){
|
||||||
|
if(mResValue instanceof ResValueBag){
|
||||||
|
return (ResValueBag) mResValue;
|
||||||
|
}
|
||||||
|
return new ResValueBag();
|
||||||
|
}
|
||||||
|
private ResValueInt getOrCreateResValueInt(){
|
||||||
|
if(mResValue instanceof ResValueInt){
|
||||||
|
return (ResValueInt) mResValue;
|
||||||
|
}
|
||||||
|
return new ResValueInt();
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
StringBuilder builder=new StringBuilder();
|
StringBuilder builder=new StringBuilder();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.reandroid.lib.arsc.value;
|
package com.reandroid.lib.arsc.value;
|
||||||
|
|
||||||
|
import com.reandroid.lib.arsc.array.LibraryInfoArray;
|
||||||
import com.reandroid.lib.arsc.base.Block;
|
import com.reandroid.lib.arsc.base.Block;
|
||||||
import com.reandroid.lib.arsc.base.BlockCounter;
|
import com.reandroid.lib.arsc.base.BlockCounter;
|
||||||
import com.reandroid.lib.arsc.io.BlockReader;
|
import com.reandroid.lib.arsc.io.BlockReader;
|
||||||
@ -88,6 +89,16 @@ public class LibraryInfo extends Block implements JSONConvert<JSONObject> {
|
|||||||
setPackageId(json.getInt("id"));
|
setPackageId(json.getInt("id"));
|
||||||
setPackageName(json.getString("name"));
|
setPackageName(json.getString("name"));
|
||||||
}
|
}
|
||||||
|
public void merge(LibraryInfo info){
|
||||||
|
if(info==null||info==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(getPackageId()!=info.getPackageId()){
|
||||||
|
throw new IllegalArgumentException("Can not add different id libraries: "
|
||||||
|
+getPackageId()+"!="+info.getPackageId());
|
||||||
|
}
|
||||||
|
setPackageName(info.getPackageName());
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
StringBuilder builder=new StringBuilder();
|
StringBuilder builder=new StringBuilder();
|
||||||
|
@ -113,7 +113,6 @@ public class ResValueBag extends BaseResValue {
|
|||||||
if(isNull()){
|
if(isNull()){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
refreshCount();
|
|
||||||
int result;
|
int result;
|
||||||
result=mParentId.writeBytes(writer);
|
result=mParentId.writeBytes(writer);
|
||||||
result+=mCount.writeBytes(writer);
|
result+=mCount.writeBytes(writer);
|
||||||
@ -150,6 +149,14 @@ public class ResValueBag extends BaseResValue {
|
|||||||
getResValueBagItemArray().fromJson(json.getJSONArray(NAME_items));
|
getResValueBagItemArray().fromJson(json.getJSONArray(NAME_items));
|
||||||
refreshCount();
|
refreshCount();
|
||||||
}
|
}
|
||||||
|
public void merge(ResValueBag resValueBag){
|
||||||
|
if(resValueBag==null||resValueBag==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setParentId(resValueBag.getParentId());
|
||||||
|
getResValueBagItemArray().merge(resValueBag.getResValueBagItemArray());
|
||||||
|
refreshCount();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
|
@ -11,6 +11,10 @@ public class ResValueBagItem extends BaseResValueItem{
|
|||||||
super(BYTES_COUNT);
|
super(BYTES_COUNT);
|
||||||
setHeaderSize(BYTES_SIZE);
|
setHeaderSize(BYTES_SIZE);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public void onRemoved(){
|
||||||
|
removeTableReference();
|
||||||
|
}
|
||||||
public String getValueAsString(){
|
public String getValueAsString(){
|
||||||
return getTableString(getData()).getHtml();
|
return getTableString(getData()).getHtml();
|
||||||
}
|
}
|
||||||
@ -186,6 +190,19 @@ public class ResValueBagItem extends BaseResValueItem{
|
|||||||
setData(json.getInt(NAME_data));
|
setData(json.getInt(NAME_data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(ResValueBagItem bagItem){
|
||||||
|
if(bagItem==null||bagItem==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setId(bagItem.getId());
|
||||||
|
ValueType valueType=bagItem.getValueType();
|
||||||
|
if(valueType==ValueType.STRING){
|
||||||
|
setValueAsString(bagItem.getValueAsString());
|
||||||
|
}else {
|
||||||
|
setType(valueType);
|
||||||
|
setData(bagItem.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.reandroid.lib.arsc.value;
|
package com.reandroid.lib.arsc.value;
|
||||||
|
|
||||||
import com.reandroid.lib.arsc.decoder.ValueDecoder;
|
import com.reandroid.lib.arsc.decoder.ValueDecoder;
|
||||||
|
import com.reandroid.lib.arsc.item.SpecString;
|
||||||
import com.reandroid.lib.arsc.item.TableString;
|
import com.reandroid.lib.arsc.item.TableString;
|
||||||
import com.reandroid.lib.json.JSONObject;
|
import com.reandroid.lib.json.JSONObject;
|
||||||
|
|
||||||
@ -9,6 +10,9 @@ public class ResValueInt extends BaseResValueItem {
|
|||||||
super(BYTES_COUNT);
|
super(BYTES_COUNT);
|
||||||
setHeaderSize(BYTES_SIZE);
|
setHeaderSize(BYTES_SIZE);
|
||||||
}
|
}
|
||||||
|
protected void onRemoved(){
|
||||||
|
removeTableReference();
|
||||||
|
}
|
||||||
public String getValueAsString(){
|
public String getValueAsString(){
|
||||||
return getString(getData());
|
return getString(getData());
|
||||||
}
|
}
|
||||||
@ -116,6 +120,18 @@ public class ResValueInt extends BaseResValueItem {
|
|||||||
setData(json.getInt(NAME_data));
|
setData(json.getInt(NAME_data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void merge(ResValueInt resValueInt){
|
||||||
|
if(resValueInt==null||resValueInt==this){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ValueType valueType=resValueInt.getValueType();
|
||||||
|
if(valueType==ValueType.STRING){
|
||||||
|
setValueAsString(resValueInt.getValueAsString());
|
||||||
|
}else {
|
||||||
|
setType(valueType);
|
||||||
|
setData(resValueInt.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
StringBuilder builder=new StringBuilder();
|
StringBuilder builder=new StringBuilder();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user