diff --git a/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeException.java b/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeException.java new file mode 100644 index 0000000..64074ec --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeException.java @@ -0,0 +1,25 @@ + /* + * 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.lib.apk.xmlencoder; + +public class EncodeException extends IllegalArgumentException{ + public EncodeException(String message){ + super(message); + } + public EncodeException(String message, Throwable cause){ + super(message, cause); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeMaterials.java b/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeMaterials.java new file mode 100644 index 0000000..57b4c9c --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeMaterials.java @@ -0,0 +1,317 @@ + /* + * 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.lib.apk.xmlencoder; + + import com.reandroid.lib.apk.APKLogger; + import com.reandroid.lib.apk.ResourceIds; + import com.reandroid.lib.arsc.chunk.PackageBlock; + import com.reandroid.lib.arsc.chunk.TypeBlock; + import com.reandroid.lib.arsc.container.SpecTypePair; + import com.reandroid.lib.arsc.decoder.ValueDecoder; + import com.reandroid.lib.arsc.group.EntryGroup; + import com.reandroid.lib.arsc.item.SpecString; + import com.reandroid.lib.arsc.util.FrameworkTable; + import com.reandroid.lib.arsc.value.EntryBlock; + import com.reandroid.lib.common.ResourceResolver; + + import java.util.Collection; + import java.util.HashSet; + import java.util.Set; + import java.util.regex.Matcher; + + public class EncodeMaterials implements ResourceResolver { + private ResourceIds.Table.Package packageIds; + private PackageBlock currentPackage; + private final Set frameworkTables = new HashSet<>(); + private APKLogger apkLogger; + public EncodeMaterials(){ + } + public SpecString getSpecString(String name){ + return currentPackage.getSpecStringPool() + .get(name) + .get(0); + } + public void addTableStringPool(Collection stringList){ + getCurrentPackage() + .getTableBlock() + .getTableStringPool() + .addStrings(stringList); + } + public int resolveAttributeNameReference(String refString){ + String packageName = null; + String type = "attr"; + String name = refString; + int i=refString.lastIndexOf(':'); + if(i>=0){ + packageName=refString.substring(0, i); + name=refString.substring(i+1); + } + if(EncodeUtil.isEmpty(packageName) + || packageName.equals(getCurrentPackageName()) + || !isFrameworkPackageName(packageName)){ + + return resolveLocalResourceId(type, name); + } + return resolveFrameworkResourceId(packageName, type, name); + } + public EntryBlock getAttributeBlock(String refString){ + String packageName = null; + String type = "attr"; + String name = refString; + int i=refString.lastIndexOf(':'); + if(i>=0){ + packageName=refString.substring(0, i); + name=refString.substring(i+1); + } + if(EncodeUtil.isEmpty(packageName) + || packageName.equals(getCurrentPackageName()) + || !isFrameworkPackageName(packageName)){ + + return getLocalEntryBlock(type, name); + } + return getFrameworkEntry(type, name); + } + public int resolveReference(String refString){ + if("@null".equals(refString)){ + return 0; + } + Matcher matcher = ValueDecoder.PATTERN_REFERENCE.matcher(refString); + if(!matcher.find()){ + throw new EncodeException( + "Not proper reference string: '"+refString+"'"); + } + String prefix=matcher.group(1); + String packageName = matcher.group(2); + if(packageName!=null && packageName.endsWith(":")){ + packageName=packageName.substring(0, packageName.length()-1); + } + String type = matcher.group(4); + String name = matcher.group(5); + if(EncodeUtil.isEmpty(packageName) + || packageName.equals(getCurrentPackageName()) + || !isFrameworkPackageName(packageName)){ + return resolveLocalResourceId(type, name); + } + return resolveFrameworkResourceId(packageName, type, name); + } + public int resolveLocalResourceId(String type, String name){ + ResourceIds.Table.Package.Type.Entry entry = + this.packageIds.getEntry(type, name); + if(entry!=null){ + return entry.getResourceId(); + } + EntryGroup entryGroup=getLocalEntryGroup(type, name); + if(entryGroup!=null){ + return entryGroup.getResourceId(); + } + throw new EncodeException("Local entry not found: " + + "type="+type+ + ", name="+name); + } + public int resolveFrameworkResourceId(String type, String name){ + EntryBlock entryBlock = getFrameworkEntry(type, name); + if(entryBlock!=null){ + return entryBlock.getResourceId(); + } + throw new EncodeException("Framework entry not found: " + + "type="+type+ + ", name="+name); + } + public int resolveFrameworkResourceId(String packageName, String type, String name){ + EntryBlock entryBlock = getFrameworkEntry(packageName, type, name); + if(entryBlock!=null){ + return entryBlock.getResourceId(); + } + throw new EncodeException("Framework entry not found: " + + "package="+packageName+ + ", type="+type+ + ", name="+name); + } + public int resolveFrameworkResourceId(int packageId, String type, String name){ + EntryBlock entryBlock = getFrameworkEntry(packageId, type, name); + if(entryBlock!=null){ + return entryBlock.getResourceId(); + } + throw new EncodeException("Framework entry not found: " + + "packageId="+String.format("0x%02x", packageId)+ + ", type="+type+ + ", name="+name); + } + public EntryGroup getLocalEntryGroup(String type, String name){ + for(EntryGroup entryGroup : currentPackage.listEntryGroup()){ + if(type.equals(entryGroup.getTypeName()) && + name.equals(entryGroup.getSpecName())){ + return entryGroup; + } + } + for(PackageBlock packageBlock:currentPackage.getTableBlock().listPackages()){ + if(packageBlock==currentPackage || + packageBlock.getId()!=currentPackage.getId()){ + continue; + } + for(EntryGroup entryGroup : currentPackage.listEntryGroup()){ + if(type.equals(entryGroup.getTypeName()) && + name.equals(entryGroup.getSpecName())){ + return entryGroup; + } + } + } + return null; + } + public EntryBlock getLocalEntryBlock(String type, String name){ + for(EntryGroup entryGroup : currentPackage.listEntryGroup()){ + if(type.equals(entryGroup.getTypeName()) && + name.equals(entryGroup.getSpecName())){ + return entryGroup.pickOne(); + } + } + SpecTypePair specTypePair=currentPackage.searchByTypeName(type); + if(specTypePair!=null){ + for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){ + for(EntryBlock entryBlock:typeBlock.listEntries()){ + if(name.equals(entryBlock.getName())){ + return entryBlock; + } + } + break; + } + } + for(PackageBlock packageBlock:currentPackage.getTableBlock().listPackages()){ + if(packageBlock==currentPackage || + packageBlock.getId()!=currentPackage.getId()){ + continue; + } + specTypePair=packageBlock.searchByTypeName(type); + if(specTypePair!=null){ + for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){ + for(EntryBlock entryBlock:typeBlock.listEntries()){ + if(name.equals(entryBlock.getName())){ + return entryBlock; + } + } + break; + } + } + } + return null; + } + public EntryBlock getFrameworkEntry(String type, String name){ + for(FrameworkTable table:frameworkTables){ + EntryBlock entryBlock = table.searchEntryBlock(type, name); + if(entryBlock!=null){ + return entryBlock; + } + } + return null; + } + private boolean isFrameworkPackageName(String packageName){ + for(FrameworkTable table:frameworkTables){ + for(PackageBlock packageBlock:table.listPackages()){ + if(packageName.equals(packageBlock.getName())){ + return true; + } + } + } + return false; + } + public EntryBlock getFrameworkEntry(String packageName, String type, String name){ + for(FrameworkTable table:frameworkTables){ + for(PackageBlock packageBlock:table.listPackages()){ + if(packageName.equals(packageBlock.getName())){ + EntryBlock entryBlock = table.searchEntryBlock(type, name); + if(entryBlock!=null){ + return entryBlock; + } + } + } + } + return null; + } + public EntryBlock getFrameworkEntry(int packageId, String type, String name){ + for(FrameworkTable table:frameworkTables){ + for(PackageBlock packageBlock:table.listPackages()){ + if(packageId==packageBlock.getId()){ + EntryBlock entryBlock = table.searchEntryBlock(type, name); + if(entryBlock!=null){ + return entryBlock; + } + } + } + } + return null; + } + public EncodeMaterials setPackageIds(ResourceIds.Table.Package packageIds) { + this.packageIds = packageIds; + return this; + } + public EncodeMaterials setCurrentPackage(PackageBlock currentPackage) { + this.currentPackage = currentPackage; + return this; + } + public EncodeMaterials addFramework(FrameworkTable frameworkTable) { + frameworkTable.loadResourceNameMap(); + this.frameworkTables.add(frameworkTable); + return this; + } + public EncodeMaterials setAPKLogger(APKLogger logger) { + this.apkLogger = logger; + return this; + } + + public ResourceIds.Table.Package getPackageIds() { + return packageIds; + } + public PackageBlock getCurrentPackage() { + return currentPackage; + } + public String getCurrentPackageName(){ + return currentPackage.getName(); + } + public int getCurrentPackageId(){ + return currentPackage.getId(); + } + + @Override + public int resolveResourceId(String packageName, String type, String name) { + if(packageName==null || packageName.equals(getCurrentPackageName())){ + return resolveLocalResourceId(type, name); + } + return resolveFrameworkResourceId(packageName, type, name); + } + @Override + public int resolveResourceId(int packageId, String type, String name) { + if(packageId==getCurrentPackageId()){ + return resolveLocalResourceId(type, name); + } + return resolveFrameworkResourceId(packageId, type, name); + } + public void logMessage(String msg) { + if(apkLogger!=null){ + apkLogger.logMessage(msg); + } + } + public void logError(String msg, Throwable tr) { + if(apkLogger!=null){ + apkLogger.logError(msg, tr); + } + } + public void logVerbose(String msg) { + if(apkLogger!=null){ + apkLogger.logVerbose(msg); + } + } + + } diff --git a/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeUtil.java b/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeUtil.java new file mode 100644 index 0000000..5e44853 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeUtil.java @@ -0,0 +1,55 @@ + /* + * 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.lib.apk.xmlencoder; + + import java.io.File; + import java.util.regex.Matcher; + import java.util.regex.Pattern; + + public class EncodeUtil { + public static boolean isEmpty(String text){ + if(text==null){ + return true; + } + text=text.trim(); + return text.length()==0; + } + public static String getQualifiersFromValuesXml(File valuesXml){ + String dirName=valuesXml.getParentFile().getName(); + int i=dirName.indexOf('-'); + if(i>0){ + return dirName.substring(i); + } + return ""; + } + public static String getTypeNameFromValuesXml(File valuesXml){ + String name=valuesXml.getName(); + name=name.substring(0, name.length()-4); + if(!name.equals("plurals") && name.endsWith("s")){ + name=name.substring(0, name.length()-1); + } + return name; + } + public static String sanitizeType(String type){ + Matcher matcher=PATTERN_TYPE.matcher(type); + if(!matcher.find()){ + return ""; + } + return matcher.group(1); + } + public static final String NULL_PACKAGE_NAME = "NULL_PACKAGE_NAME"; + private static final Pattern PATTERN_TYPE=Pattern.compile("^([a-z]+)[^a-z]*.*$"); +} diff --git a/src/main/java/com/reandroid/lib/apk/xmlencoder/XMLValuesEncoder.java b/src/main/java/com/reandroid/lib/apk/xmlencoder/XMLValuesEncoder.java new file mode 100644 index 0000000..86b50c3 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/xmlencoder/XMLValuesEncoder.java @@ -0,0 +1,126 @@ + /* + * 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.lib.apk.xmlencoder; + +import com.reandroid.lib.arsc.chunk.PackageBlock; +import com.reandroid.lib.arsc.chunk.TypeBlock; +import com.reandroid.lib.arsc.decoder.ValueDecoder; +import com.reandroid.lib.arsc.item.SpecString; +import com.reandroid.lib.arsc.pool.TypeStringPool; +import com.reandroid.lib.arsc.value.EntryBlock; +import com.reandroid.lib.arsc.value.ValueType; +import com.reandroid.xml.XMLDocument; +import com.reandroid.xml.XMLElement; + +public class XMLValuesEncoder { + private final EncodeMaterials materials; + XMLValuesEncoder(EncodeMaterials materials){ + this.materials=materials; + } + public void encode(String type, String qualifiers, XMLDocument xmlDocument){ + XMLElement documentElement = xmlDocument.getDocumentElement(); + TypeBlock typeBlock = getTypeBlock(type, qualifiers); + + int count = documentElement.getChildesCount(); + + typeBlock.getEntryBlockArray().ensureSize(count); + + for(int i=0;i: '"+value+"'"); + } + } +} diff --git a/src/main/java/com/reandroid/lib/apk/xmlencoder/XMLValuesEncoderPlurals.java b/src/main/java/com/reandroid/lib/apk/xmlencoder/XMLValuesEncoderPlurals.java new file mode 100644 index 0000000..0349483 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/xmlencoder/XMLValuesEncoderPlurals.java @@ -0,0 +1,59 @@ + /* + * 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.lib.apk.xmlencoder; + +import com.reandroid.lib.arsc.array.ResValueBagItemArray; +import com.reandroid.lib.arsc.decoder.ValueDecoder; +import com.reandroid.lib.arsc.value.ResValueBag; +import com.reandroid.lib.arsc.value.ResValueBagItem; +import com.reandroid.lib.arsc.value.ValueType; +import com.reandroid.lib.arsc.value.plurals.PluralsQuantity; +import com.reandroid.xml.XMLElement; + +public class XMLValuesEncoderPlurals extends XMLValuesEncoderBag{ + XMLValuesEncoderPlurals(EncodeMaterials materials) { + super(materials); + } + @Override + void encodeChildes(XMLElement parentElement, ResValueBag resValueBag){ + int count = parentElement.getChildesCount(); + ResValueBagItemArray itemArray = resValueBag.getResValueBagItemArray(); + for(int i=0;i stringList = new ArrayList<>(); + int count = documentElement.getChildesCount(); + for(int i=0;i