
com.reandroid.dex.model.DexDirectory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ARSCLib Show documentation
Show all versions of ARSCLib Show documentation
Android binary resources read/write library
The newest version!
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.dex.model;
import com.reandroid.archive.ZipEntryMap;
import com.reandroid.dex.common.FullRefresh;
import com.reandroid.dex.common.SectionItem;
import com.reandroid.dex.id.ClassId;
import com.reandroid.dex.id.FieldId;
import com.reandroid.dex.id.MethodId;
import com.reandroid.dex.id.StringId;
import com.reandroid.dex.key.*;
import com.reandroid.dex.sections.*;
import com.reandroid.dex.smali.SmaliWriter;
import com.reandroid.utils.collection.*;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
public class DexDirectory implements Iterable, Closeable,
DexClassRepository, FullRefresh {
private final DexFileSourceSet dexSourceSet;
private Object mTag;
private final ArrayCollection externalTypeKeyReferenceList;
public DexDirectory() {
this.dexSourceSet = new DexFileSourceSet();
this.externalTypeKeyReferenceList = new ArrayCollection<>();
}
public Object getTag() {
return mTag;
}
public void setTag(Object tag) {
this.mTag = tag;
}
public int getVersion(){
DexFile first = getFirst();
if(first != null){
return first.getVersion();
}
return 0;
}
public void setVersion(int version){
for(DexFile dexFile : this){
dexFile.setVersion(version);
}
}
@Override
public Iterator getMarkers() {
return new IterableIterator(iterator()) {
@Override
public Iterator iterator(DexFile element) {
return element.getMarkers();
}
};
}
public int mergeAll(MergeOptions options, Iterable iterable){
return mergeAll(options, iterable.iterator());
}
public int mergeAll(MergeOptions options, Iterator iterator){
int result = 0;
while (iterator.hasNext()){
boolean merged = merge(options, iterator.next());
if(merged){
result ++;
}
}
return result;
}
public boolean merge(DexClass dexClass){
return merge(new DexMergeOptions(), dexClass);
}
public boolean merge(MergeOptions options, DexClass dexClass){
if(dexClass.isInSameDirectory(this)){
return false;
}
if(containsClass(dexClass.getKey())){
options.onDuplicate(dexClass.getId());
return false;
}
boolean startChanged = false;
int start = options.getMergeStartDexFile();
for(int i = start; i < size(); i ++){
DexFile dexFile = get(i);
if(dexFile.merge(options, dexClass)){
if(startChanged){
options.setMergeStartDexFile(i);
}
return true;
}
startChanged = true;
}
return false;
}
public void merge(DexDirectory directory){
merge(new DexMergeOptions(false), directory);
}
public void merge(MergeOptions options, DexDirectory directory){
if(directory == this){
throw new IllegalArgumentException("Cyclic merge");
}
int start = options.getMergeStartDexFile();
int i = start;
while (true){
DexFile dexFile = this.get(i);
DexFile last = directory.getLastNonEmpty(options,0);
if(dexFile == null || last == null){
break;
}
if(!dexFile.merge(options, last)){
i ++;
}
}
if(i != start){
options.setMergeStartDexFile(i);
}
shrink();
directory.merge(options);
getDexSourceSet().merge(directory.getDexSourceSet());
}
public void merge(){
merge(new DexMergeOptions());
}
public void merge(MergeOptions options){
if(size() < 2){
return;
}
int i = 0;
while (true){
DexFile dexFile = get(i);
DexFile last = getLastNonEmpty(options,i + 1);
if(dexFile == null || last == null){
break;
}
if(!dexFile.merge(options, last)){
i ++;
}
}
shrink();
}
private DexFile getLastNonEmpty(MergeOptions options, int limit){
int size = size() - 1;
for(int i = size; i >= limit; i--){
DexFile dexFile = get(i);
if(!options.isEmptyDexFile(dexFile.getDexLayout())){
return dexFile;
}
}
return null;
}
public int shrink() {
int result = 0;
result += DalvikUtil.cleanMissingMembers(this);
for(DexFile dexFile : this){
result += dexFile.shrink();
}
return result;
}
public int clearDuplicateData(){
int result = 0;
for(DexFile dexFile : this){
result += dexFile.clearDuplicateData();
}
return result;
}
public int clearUnused(){
int result = 0;
for(DexFile dexFile : this){
result += dexFile.clearUnused();
}
return result;
}
public void cleanDuplicateDebugLines(){
for(DexFile dexFile : this){
dexFile.fixDebugLineNumbers();
}
}
public Iterator findEquivalentFields(FieldKey fieldKey){
DexClass defining = getDexClass(fieldKey.getDeclaring());
if(defining == null){
return EmptyIterator.of();
}
DexField dexField = defining.getField(fieldKey);
if(dexField == null){
return EmptyIterator.of();
}
defining = dexField.getDexClass();
FieldKey definingKey = dexField.getKey();
Iterator subKeys = ComputeIterator.of(getSubTypes(defining.getKey()),
dexClass -> {
FieldKey key = definingKey.changeDeclaring(dexClass.getKey());
DexField field = dexClass.getField(key);
if(definingKey.equals(field.getKey())){
return key;
}
return null;
}
);
return CombiningIterator.two(SingleIterator.of(definingKey), subKeys);
}
public Iterator getSubTypes(TypeKey typeKey){
return new IterableIterator(iterator()) {
@Override
public Iterator iterator(DexFile element) {
return element.getSubTypes(typeKey);
}
};
}
public Iterator getImplementClasses(TypeKey typeKey){
return new IterableIterator(iterator()) {
@Override
public Iterator iterator(DexFile element) {
return element.getImplementClasses(typeKey);
}
};
}
public void save() throws IOException {
dexSourceSet.saveAll();
}
public void save(File dir) throws IOException {
dexSourceSet.saveAll(dir);
}
public Iterator getClassIds() {
return getItems(SectionType.CLASS_ID);
}
public T1 get(SectionType sectionType, Key key){
for (DexFile dexFile : this) {
T1 item = dexFile.getItem(sectionType, key);
if (item != null) {
return item;
}
}
return null;
}
@Override
public boolean removeEntries(SectionType sectionType, Predicate filter){
Iterator iterator = clonedIterator();
boolean result = false;
while (iterator.hasNext()){
DexFile dexFile = iterator.next();
if(dexFile.removeEntries(sectionType, filter)) {
result = true;
}
}
return result;
}
@Override
public boolean removeEntry(SectionType sectionType, Key key) {
boolean removed = false;
for(DexFile dexFile : this) {
if(dexFile.removeEntry(sectionType, key)) {
removed = true;
}
}
return removed;
}
@Override
public boolean removeEntriesWithKey(SectionType sectionType, Predicate super Key> filter) {
boolean removed = false;
for(DexFile dexFile : this) {
if(dexFile.removeEntriesWithKey(sectionType, filter)) {
removed = true;
}
}
return removed;
}
@Override
public boolean removeClasses(Predicate super DexClass> filter) {
Iterator iterator = clonedIterator();
boolean removed = false;
while (iterator.hasNext()){
DexFile dexFile = iterator.next();
if(dexFile.removeClasses(filter)){
removed = true;
}
}
return removed;
}
@Override
public Iterator getClonedItems(SectionType sectionType) {
return new IterableIterator(clonedIterator()) {
@Override
public Iterator iterator(DexFile element) {
return element.getClonedItems(sectionType);
}
};
}
@Override
public int getDexClassesCount() {
int result = 0;
for(DexFile dexFile : this){
result += dexFile.getDexClassesCount();
}
return result;
}
@Override
public DexClass getDexClass(TypeKey key){
for(DexFile dexFile : this){
DexClass result = dexFile.getDexClass(key);
if(result != null){
return result;
}
}
return null;
}
@Override
public Iterator getDexClasses(Predicate super TypeKey> filter) {
return new IterableIterator(iterator()) {
@Override
public Iterator iterator(DexFile dexFile) {
return dexFile.getDexClasses(filter);
}
};
}
@Override
public Iterator getDexClassesCloned(Predicate super TypeKey> filter) {
return new IterableIterator(clonedIterator()) {
@Override
public Iterator iterator(DexFile dexFile) {
return dexFile.getDexClassesCloned(filter);
}
};
}
@Override
public Iterator> getSections(SectionType sectionType) {
return new IterableIterator>(iterator()) {
@Override
public Iterator> iterator(DexFile element) {
return element.getSections(sectionType);
}
};
}
public Iterator getDexInstructions() {
return new IterableIterator(iterator()) {
@Override
public Iterator iterator(DexFile element) {
return element.getDexInstructions();
}
};
}
public Iterator getDexInstructionsCloned() {
return new IterableIterator(clonedIterator()) {
@Override
public Iterator iterator(DexFile element) {
return element.getDexInstructionsCloned();
}
};
}
@Override
public Iterator iterator() {
return dexSourceSet.getDexFiles();
}
public Iterator clonedIterator() {
return dexSourceSet.getClonedDexFiles();
}
// if types changed, call this before method rename
public void clearMethodPools(){
for(DexFile dexFile : this){
dexFile.clearPoolMap(SectionType.PROTO_ID);
dexFile.clearPoolMap(SectionType.METHOD_ID);
}
}
public void clearPoolMap(SectionType> sectionType){
for(DexFile dexFile : this){
dexFile.clearPoolMap(sectionType);
}
}
@Override
public void clearPoolMap(){
for(DexFile dexFile : this){
dexFile.clearPoolMap();
}
}
public void sortStrings(){
for(DexFile dexFile : this){
dexFile.sortStrings();
}
}
@Override
public void refreshFull() {
for(DexFile dexFile : this){
dexFile.setDexDirectory(this);
dexFile.refreshFull();
}
}
@Override
public void refresh(){
for(DexFile dexFile : this){
dexFile.setDexDirectory(this);
dexFile.refresh();
}
}
public void updateDexFileList(){
for(DexFile dexFile : this){
dexFile.setDexDirectory(this);
}
}
public void addDirectory(File dir) throws IOException {
getDexSourceSet().addAll(dir);
for(DexFile dexFile : this){
dexFile.setDexDirectory(this);
}
}
public void addApk(ZipEntryMap zipEntryMap) throws IOException {
addZip(zipEntryMap, "");
}
public void addZip(ZipEntryMap zipEntryMap, String root) throws IOException {
getDexSourceSet().addAll(zipEntryMap, root);
for(DexFile dexFile : this){
dexFile.setDexDirectory(this);
}
}
public void addFile(File file) throws IOException {
DexSource source = getDexSourceSet().add(file);
if(file.isFile()){
source.get().setDexDirectory(this);
}
}
public ZipEntryMap getZipEntryMap() {
return getDexSourceSet().getZipEntryMap();
}
public void setZipEntryMap(ZipEntryMap zipEntryMap) {
getDexSourceSet().setZipEntryMap(zipEntryMap);
}
public DexFile createDefault(){
DexFileSourceSet sourceSet = getDexSourceSet();
if(size() == 0 && sourceSet.getZipEntryMap() == null) {
sourceSet.setZipEntryMap(new ZipEntryMap());
}
DexSource source = sourceSet.createNext();
DexFile dexFile = DexFile.createDefault();
source.set(dexFile);
dexFile.setDexDirectory(this);
dexFile.setSimpleName(source.toString());
int version = getVersion();
if(version != 0){
dexFile.setVersion(version);
}
return dexFile;
}
public DexFileSourceSet getDexSourceSet() {
return dexSourceSet;
}
public int rename(TypeKey search, TypeKey replace){
if(containsClass(replace)){
throw new RuntimeException("Duplicate: " + search + " --> " + replace);
}
int count = 0;
Iterator> iterator = renameTypes(search, replace, true, true);
while (iterator.hasNext()){
iterator.next();
count++;
}
return count;
}
public Iterator renameTypes(TypeKey search, TypeKey replace){
return renameTypes(search, replace, true, true);
}
public Iterator renameTypes(TypeKey search, TypeKey replace, boolean renameInner, boolean renameJava){
return renameTypes(new KeyPair<>(search, replace), renameInner, renameJava);
}
public Iterator renameTypes(KeyPair pair, boolean renameInner, boolean renameJava){
return FilterIterator.of(getClonedItems(SectionType.STRING_ID),
stringId -> renameTypes(stringId, pair, renameInner, renameJava));
}
public Iterator renameTypes(Iterable> iterable, boolean renameInner, boolean renameJava){
return FilterIterator.of(getClonedItems(SectionType.STRING_ID),
stringId -> renameTypes(stringId, iterable, renameInner, renameJava));
}
boolean renameTypes(StringId stringId, Iterable> iterable, boolean renameInner, boolean renameJava){
for(KeyPair pair : iterable){
boolean renamed = renameTypes(stringId, pair, renameInner, renameJava);
if(renamed){
return true;
}
}
return false;
}
boolean renameTypes(StringId stringId, KeyPair pair, boolean renameInner, boolean renameJava){
boolean renamed = renameTypeString(stringId, pair, renameInner, renameJava);
if(renamed){
DexClass dexClass = getDexClass(stringId.getString());
if(dexClass != null){
dexClass.fixDalvikInnerClassName();
}
}
return renamed;
}
private boolean renameTypeString(StringId stringId, KeyPair pair, boolean renameInner, boolean renameJava){
String text = stringId.getString();
TypeKey search = pair.getFirst();
TypeKey replace = pair.getSecond();
String type = search.getTypeName();
String type2 = replace.getTypeName();
if(type.equals(text)){
stringId.setString(type2);
return true;
}
if(renameInner){
type = type.replace(';', '$');
if(text.startsWith(type)){
type2 = replace.getTypeName();
type2 = type2.replace(';', '$');
text = text.substring(type.length());
stringId.setString(type2 + text);
return true;
}
}
type = search.getSignatureTypeName();
if(type.equals(text)){
type2 = replace.getSignatureTypeName();
stringId.setString(type2);
return true;
}
type = search.getArrayType(1);
if(type.equals(text)){
type2 = replace.getArrayType(1);
stringId.setString(type2);
return true;
}
if(renameInner){
type = type.replace(';', '$');
if(text.startsWith(type)){
type2 = replace.getArrayType(1);
type2 = type2.replace(';', '$');
text = text.substring(type.length());
stringId.setString(type2 + text);
return true;
}
}
type = search.getArrayType(2);
if(type.equals(text)){
type2 = replace.getArrayType(2);
stringId.setString(type2);
return true;
}
if(renameInner){
type = type.replace(';', '$');
if(text.startsWith(type)){
type2 = replace.getArrayType(2);
type2 = type2.replace(';', '$');
text = text.substring(type.length());
stringId.setString(type2 + text);
return true;
}
}
type = search.getArrayType(3);
if(type.equals(text)){
type2 = replace.getArrayType(3);
stringId.setString(type2);
return true;
}
if(renameInner){
type = type.replace(';', '$');
if(text.startsWith(type)){
type2 = replace.getArrayType(3);
type2 = type2.replace(';', '$');
text = text.substring(type.length());
stringId.setString(type2 + text);
return true;
}
}
if(renameJava){
type = search.getSourceName();
if(type.equals(text)){
type2 = replace.getSourceName();
stringId.setString(type2);
return true;
}
if(renameInner){
type = type + "$";
if(text.startsWith(type)){
type2 = replace.getSourceName();
type2 = type2 + "$";
text = text.substring(type.length());
stringId.setString(type2 + text);
return true;
}
type = type + ".";
if(text.startsWith(type)){
type2 = replace.getSourceName();
type2 = type2 + ".";
text = text.substring(type.length());
stringId.setString(type2 + text);
return true;
}
}
}
return false;
}
public List replace(MethodKey methodKey, String name){
List results = rename(methodKey, name);
if(!results.isEmpty()){
return results;
}
List methodIdList = CollectionUtil.toList(getItems(SectionType.METHOD_ID, methodKey));
int size = methodIdList.size();
if(size == 0){
return EmptyList.of();
}
results = new ArrayCollection<>(size);
for(MethodId methodId : methodIdList){
methodId.setName(name);
results.add(methodId.getKey());
}
return results;
}
public List rename(MethodKey methodKey, String name){
if(containsDeepSearch(methodKey.changeName(name))){
return EmptyList.of();
}
ArrayCollection methodIdList = new ArrayCollection<>();
methodIdList.addAll(getMethodIds(methodKey));
if(methodIdList.size() == 0){
return EmptyList.of();
}
MethodKey renamed = methodKey.changeName(name);
for(MethodId methodId : methodIdList){
if(renamed.equals(methodId.getKey())){
throw new IllegalArgumentException("Duplicate: " + renamed);
}
}
List results = new ArrayCollection<>(methodIdList.size());
for(MethodId methodId : methodIdList){
methodId.setName(name);
results.add(methodId.getKey());
}
return results;
}
public List replace(FieldKey fieldKey, String name){
List results = rename(fieldKey, name);
if(!results.isEmpty()){
return results;
}
List fieldIdList = CollectionUtil.toList(getItems(SectionType.FIELD_ID, fieldKey));
int size = fieldIdList.size();
if(size == 0){
return EmptyList.of();
}
results = new ArrayCollection<>(size);
for(FieldId fieldId : fieldIdList){
fieldId.setName(name);
results.add(fieldId.getKey());
}
return results;
}
public List rename(FieldKey fieldKey, String name){
ArrayCollection existingFields = ArrayCollection.of(findEquivalentFields(fieldKey.changeName(name)));
ArrayCollection fieldIdList = ArrayCollection.of(getItems(SectionType.FIELD_ID, fieldKey));
if(fieldIdList.isEmpty()){
return EmptyList.of();
}
if(!existingFields.isEmpty()){
throw new IllegalArgumentException("Conflicting fields: " + existingFields.getFirst());
}
FieldKey renamed = fieldKey.changeName(name);
for(FieldId fieldId : fieldIdList){
if(renamed.equals(fieldId.getKey())){
throw new IllegalArgumentException("Duplicate: " + renamed);
}
}
List results = new ArrayCollection<>(fieldIdList.size());
for(FieldId fieldId : fieldIdList){
fieldId.setName(name);
results.add(fieldId.getKey());
}
return results;
}
public boolean containsDeepSearch(MethodKey methodKey){
DexClass startClass = getDexClass(methodKey.getDeclaring());
if(startClass == null){
return false;
}
if(startClass.containsDeclaredMethod(methodKey)){
return true;
}
Iterator iterator = startClass.getOverridingAndSuperTypes();
while (iterator.hasNext()){
DexClass dexClass = iterator.next();
if(dexClass.containsDeclaredMethod(methodKey)){
return true;
}
}
return false;
}
public boolean containsDeepSearch(FieldKey fieldKey){
DexClass startClass = getDexClass(fieldKey.getDeclaring());
if(startClass == null){
return false;
}
Iterator iterator = startClass.getOverridingAndSuperTypes();
while (iterator.hasNext()){
DexClass dexClass = iterator.next();
FieldKey key = fieldKey.changeDeclaring(dexClass.getKey());
if(fieldKey.equals(key)){
return true;
}
}
return false;
}
@Override
public Iterator searchExtending(TypeKey typeKey){
UniqueIterator iterator = new UniqueIterator<>(
new IterableIterator(iterator()) {
@Override
public Iterator iterator(DexFile element) {
return element.getExtendingClasses(typeKey);
}
});
iterator.exclude(getDexClass(typeKey));
return iterator;
}
@Override
public Iterator searchImplementations(TypeKey typeKey){
UniqueIterator iterator = new UniqueIterator<>(
new IterableIterator(iterator()) {
@Override
public Iterator iterator(DexFile element) {
return element.getImplementClasses(typeKey);
}
});
iterator.exclude(getDexClass(typeKey));
return iterator;
}
public int distributeClasses(int maxClassesPerDex) {
if(maxClassesPerDex <= 0){
throw new IllegalArgumentException(
"Classes per dex must be greater than zero: " + maxClassesPerDex);
}
int size = this.size();
if(size == 0){
return 0;
}
int count = this.getDexClassesCount();
int classesPerDex = count / size;
while (classesPerDex > maxClassesPerDex){
this.createDefault();
int check = this.size();
if(check <= size){
throw new IllegalArgumentException("Failed to create next dex");
}
size = check;
classesPerDex = count / size;
}
int result = 0;
for(int i = 0; i < size; i++){
result += distributeClasses(this.get(i), classesPerDex);
}
return result;
}
private int distributeClasses(DexFile source, int classesPerDex){
int result = 0;
DexDirectory directory = source.getDexDirectory();
for(int i = 0; i < directory.size(); i++){
result += distributeClasses(source, directory.get(i), classesPerDex);
}
return result;
}
private int distributeClasses(DexFile source, DexFile destination, int classesPerDex){
int result = 0;
if(source.getDexLayout() == destination.getDexLayout()){
return result;
}
Section classSection = source.getSection(SectionType.CLASS_ID);
ClassId previous = null;
SectionArray array = classSection.getItemArray();
while (source.getDexClassesCount() > classesPerDex && destination.getDexClassesCount() < classesPerDex){
ClassId classId = array.getLast();
if(classId == previous){
break;
}
destination.merge(classId);
previous = classId;
result ++;
}
return result;
}
public DexFile get(int i){
return dexSourceSet.getDexFile(i);
}
public int indexOf(DexFile dexFile){
int size = size();
for(int i = 0; i < size; i++){
if(dexFile == get(i)){
return i;
}
}
return -1;
}
public int size() {
return dexSourceSet.size();
}
public DexFile getFirst(){
DexSource source = dexSourceSet.getFirst();
if(source != null){
return source.get();
}
return null;
}
public DexFile getLast(){
DexSource source = dexSourceSet.getLast();
if(source != null){
return source.get();
}
return null;
}
@Override
public List getExternalTypeKeyReferenceList() {
return externalTypeKeyReferenceList;
}
public void addExternalTypeKeyReference(TypeKeyReference reference) {
if(reference != null && !externalTypeKeyReferenceList.contains(reference)) {
externalTypeKeyReferenceList.add(reference);
}
}
public void clearExternalTypeKeyReferences() {
externalTypeKeyReferenceList.clear();
}
@Override
public void close() throws IOException {
this.dexSourceSet.close();
this.clearExternalTypeKeyReferences();
}
public void writeSmali(SmaliWriter writer, File root) throws IOException {
for(DexFile dexFile : this){
dexFile.writeSmali(writer, root);
}
}
@Override
public String toString() {
return "DexFiles = " + size();
}
public static DexDirectory fromZip(ZipEntryMap zipEntryMap) throws IOException {
DexDirectory dexDirectory = new DexDirectory();
dexDirectory.getDexSourceSet().addAll(zipEntryMap);
dexDirectory.updateDexFileList();
return dexDirectory;
}
public static DexDirectory fromZip(ZipEntryMap zipEntryMap, String directoryPath) throws IOException {
DexDirectory dexDirectory = new DexDirectory();
dexDirectory.getDexSourceSet().addAll(zipEntryMap, directoryPath);
dexDirectory.updateDexFileList();
return dexDirectory;
}
public static DexDirectory fromDexFilesDirectory(File dir) throws IOException {
DexDirectory dexDirectory = new DexDirectory();
dexDirectory.getDexSourceSet().addAll(dir);
dexDirectory.updateDexFileList();
return dexDirectory;
}
public static DexDirectory readStrings(ZipEntryMap zipEntryMap) throws IOException {
return readStrings(zipEntryMap, null);
}
public static DexDirectory readStrings(ZipEntryMap zipEntryMap, String directoryPath) throws IOException {
DexDirectory dexDirectory = new DexDirectory();
DexFileSourceSet sourceSet = dexDirectory.getDexSourceSet();
sourceSet.setReadStringsMode(true);
sourceSet.addAll(zipEntryMap, directoryPath);
dexDirectory.updateDexFileList();
return dexDirectory;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy