This commit is contained in:
REAndroid 2022-06-27 14:27:50 -04:00
parent 1763dfbc3c
commit 15e031fdeb
116 changed files with 2300 additions and 502 deletions

0
LICENSE Normal file → Executable file
View File

30
README.md Normal file → Executable file
View File

@ -7,22 +7,38 @@
public static void example() throws IOException {
File inFile=new File("resources.arsc");
BlockReader blockReader=new BlockReader(inFile);
TableBlock tableBlock=new TableBlock();
tableBlock.readBytes(blockReader);
tableBlock.readBytes(inFile);
// edit tableBlock as desired
//edit tableBlock as desired, for example to change the package:
PackageBlock packageBlock=tableBlock.getPackageArray().get(0);
packageBlock.setPackageName("com.new.package.name");
//refresh to recalculate offsets
tableBlock.refresh();
//save the edited table
File outFile=new File("resources_out.arsc");
OutputStream outputStream=new FileOutputStream(outFile, false);
tableBlock.writeBytes(outFile);
}
tableBlock.writeBytes(outputStream);
public static void exampleManifest() throws IOException {
File inFile=new File("AndroidManifest.xml");
outputStream.flush();
outputStream.close();
AndroidManifestBlock manifestBlock=new AndroidManifestBlock();
manifestBlock.readBytes(file);
//edit AndroidManifest as desired, for example to change the package:
manifestBlock.setPackageName("com.new.package.name");
//refresh to recalculate offsets
manifestBlock.refresh();
//save the edited table
File outFile=new File("AndroidManifest_out.xml");
manifestBlock.writeBytes(outFile);
}
```

12
build.gradle Normal file → Executable file
View File

@ -2,7 +2,7 @@
apply plugin: 'java-library'
group 'com.reandroid.lib.arsc'
version '1.0.0'
version '1.0.1'
java {
sourceCompatibility JavaVersion.VERSION_1_8
@ -18,12 +18,12 @@ if (JavaVersion.current().isJava8Compatible()) {
}
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
processResources {
filesMatching('lib.properties') {
expand('version': version)
}
}
processResources.inputs.property('version', version)
processResources.expand('version': version)

0
gradle/wrapper/gradle-wrapper.jar vendored Normal file → Executable file
View File

0
gradle/wrapper/gradle-wrapper.properties vendored Normal file → Executable file
View File

0
gradlew.bat vendored Normal file → Executable file
View File

0
settings.gradle Normal file → Executable file
View File

0
src/main/java/com/reandroid/lib/arsc/BuildInfo.java Normal file → Executable file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
src/main/java/com/reandroid/lib/arsc/base/Block.java Normal file → Executable file
View File

View File

View File

View File

@ -9,6 +9,18 @@ public abstract class BlockContainer<T extends Block> extends Block{
public BlockContainer(){
super();
}
protected final BlockContainer getTopContainer(){
Block parent=this;
BlockContainer result=this;
while (parent!=null){
if(parent instanceof BlockContainer){
result=(BlockContainer)parent;
}
parent=parent.getParent();
}
return result;
}
protected abstract void onRefreshed();
public final void refresh(){
if(isNull()){

View File

View File

View File

View File

View File

View File

View File

@ -4,7 +4,6 @@ import com.reandroid.lib.arsc.array.SpecTypePairArray;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.container.PackageLastBlocks;
import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.arsc.decoder.ResourceNameProvider;
import com.reandroid.lib.arsc.group.EntryGroup;
import com.reandroid.lib.arsc.item.IntegerItem;
import com.reandroid.lib.arsc.item.PackageName;
@ -12,15 +11,13 @@ import com.reandroid.lib.arsc.item.ReferenceItem;
import com.reandroid.lib.arsc.pool.SpecStringPool;
import com.reandroid.lib.arsc.pool.TableStringPool;
import com.reandroid.lib.arsc.pool.TypeStringPool;
import com.reandroid.lib.arsc.value.BaseResValue;
import com.reandroid.lib.arsc.value.EntryBlock;
import com.reandroid.lib.arsc.value.LibraryInfo;
import com.reandroid.lib.arsc.value.ResValueBag;
import java.util.*;
public class PackageBlock extends BaseChunk implements ResourceNameProvider {
public class PackageBlock extends BaseChunk {
private final IntegerItem mPackageId;
private final PackageName mPackageName;
@ -270,67 +267,6 @@ public class PackageBlock extends BaseChunk implements ResourceNameProvider {
refreshKeyStrings();
}
@Override
public String getResourceFullName(int resId, boolean includePackageName) {
EntryGroup entryGroup=getEntryGroup(resId);
if(entryGroup==null){
return null;
}
String type=entryGroup.getTypeName();
if(type==null){
return null;
}
String spec=entryGroup.getSpecName();
if(spec==null){
return null;
}
StringBuilder builder=new StringBuilder();
builder.append('@');
if(includePackageName){
builder.append(getPackageName());
builder.append(':');
}
builder.append(type);
builder.append('/');
builder.append(spec);
return builder.toString();
}
@Override
public String getResourceName(int resId, boolean includePackageName) {
EntryGroup entryGroup=getEntryGroup(resId);
if(entryGroup==null){
return null;
}
String spec=entryGroup.getSpecName();
if(spec==null){
return null;
}
StringBuilder builder=new StringBuilder();
if(includePackageName){
builder.append(getPackageName());
builder.append(':');
}
builder.append(spec);
return builder.toString();
}
@Override
public ResValueBag getAttributeBag(int resId){
EntryGroup entryGroup=getEntryGroup(resId);
if(entryGroup==null){
return null;
}
EntryBlock entryBlock=entryGroup.pickOne();
if(entryBlock==null){
return null;
}
BaseResValue resValue=entryBlock.getResValue();
if(resValue instanceof ResValueBag){
return (ResValueBag)resValue;
}
return null;
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();

View File

View File

@ -1,14 +1,16 @@
package com.reandroid.lib.arsc.chunk;
import com.reandroid.lib.arsc.array.PackageArray;
import com.reandroid.lib.arsc.decoder.ResourceNameProvider;
import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock;
import com.reandroid.lib.arsc.header.HeaderBlock;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.item.IntegerItem;
import com.reandroid.lib.arsc.pool.TableStringPool;
import com.reandroid.lib.arsc.value.ResValueBag;
import java.io.*;
import java.util.Collection;
public class TableBlock extends BaseChunk implements ResourceNameProvider {
public class TableBlock extends BaseChunk {
private final IntegerItem mPackageCount;
private final TableStringPool mTableStringPool;
private final PackageArray mPackageArray;
@ -42,43 +44,34 @@ public class TableBlock extends BaseChunk implements ResourceNameProvider {
protected void onChunkRefreshed() {
refreshPackageCount();
}
@Override
public String getResourceFullName(int resId, boolean includePackageName) {
byte pkgId= (byte) ((resId>>24)&0xFF);
if(pkgId==0){
return null;
}
PackageBlock packageBlock=getPackageBlockById(pkgId);
if(packageBlock!=null){
return packageBlock.getResourceFullName(resId, includePackageName);
}
return null;
}
@Override
public String getResourceName(int resId, boolean includePackageName) {
byte pkgId= (byte) ((resId>>24)&0xFF);
if(pkgId==0){
return null;
public int onWriteBytes(OutputStream stream) throws IOException{
int result=super.onWriteBytes(stream);
stream.flush();
stream.close();
return result;
}
PackageBlock packageBlock=getPackageBlockById(pkgId);
if(packageBlock!=null){
return packageBlock.getResourceName(resId, includePackageName);
public void readBytes(File file) throws IOException{
BlockReader reader=new BlockReader(file);
super.readBytes(reader);
}
return null;
public void readBytes(InputStream inputStream) throws IOException{
BlockReader reader=new BlockReader(inputStream);
super.readBytes(reader);
}
@Override
public ResValueBag getAttributeBag(int resId){
byte pkgId= (byte) ((resId>>24)&0xFF);
if(pkgId==0){
return null;
public final int writeBytes(File file) throws IOException{
if(isNull()){
throw new IOException("Can NOT save null block");
}
PackageBlock packageBlock=getPackageBlockById(pkgId);
if(packageBlock!=null){
return packageBlock.getAttributeBag(resId);
File dir=file.getParentFile();
if(dir!=null && !dir.exists()){
dir.mkdirs();
}
return null;
OutputStream outputStream=new FileOutputStream(file);
return super.writeBytes(outputStream);
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();
@ -89,4 +82,43 @@ public class TableBlock extends BaseChunk implements ResourceNameProvider {
return builder.toString();
}
public static boolean isResTableBlock(File file){
if(file==null){
return false;
}
try {
InputStream inputStream=new FileInputStream(file);
return isResTableBlock(inputStream);
} catch (FileNotFoundException ignored) {
return false;
}
}
public static boolean isResTableBlock(InputStream inputStream){
try {
HeaderBlock headerBlock= BlockReader.readHeaderBlock(inputStream);
return isResTableBlock(headerBlock);
} catch (IOException ignored) {
return false;
}
}
public static boolean isResTableBlock(BlockReader blockReader){
if(blockReader==null){
return false;
}
try {
HeaderBlock headerBlock = blockReader.readHeaderBlock();
return isResTableBlock(headerBlock);
} catch (IOException ignored) {
return false;
}
}
public static boolean isResTableBlock(HeaderBlock headerBlock){
if(headerBlock==null){
return false;
}
ChunkType chunkType=headerBlock.getChunkType();
return chunkType==ChunkType.TABLE;
}
}

View File

View File

@ -0,0 +1,166 @@
package com.reandroid.lib.arsc.chunk.xml;
import com.reandroid.lib.arsc.item.ResXmlString;
import com.reandroid.lib.arsc.pool.ResXmlStringPool;
import com.reandroid.lib.arsc.value.ValueType;
import java.util.ArrayList;
import java.util.List;
public class AndroidManifestBlock extends ResXmlBlock{
public AndroidManifestBlock(){
super();
}
public List<String> getPermissions(){
List<String> results=new ArrayList<>();
ResXmlElement manifestElement=getManifestElement();
if(manifestElement==null){
return results;
}
List<ResXmlElement> permissionList = manifestElement.searchElementsByTagName(TAG_uses_permission);
for(ResXmlElement permission:permissionList){
ResXmlAttribute nameAttr = permission.searchAttributeByName(ATTR_android_name);
if(nameAttr==null){
continue;
}
String val=nameAttr.getValueString();
if(val!=null){
results.add(val);
}
}
return results;
}
public String getPackageName(){
return getManifestAttributeString(ATTR_PACKAGE);
}
public boolean setPackageName(String packageName){
return setManifestAttributeString(ATTR_PACKAGE, packageName);
}
public Integer getCompileSdkVersion(){
return getManifestAttributeInt(ATTR_compileSdkVersion);
}
public boolean setCompileSdkVersion(int val){
return setManifestAttributeInt(ATTR_compileSdkVersion, val);
}
public String getCompileSdkVersionCodename(){
return getManifestAttributeString(ATTR_compileSdkVersionCodename);
}
public boolean setCompileSdkVersionCodename(String val){
return setManifestAttributeString(ATTR_compileSdkVersionCodename, val);
}
public Integer getVersionCode(){
return getManifestAttributeInt(ATTR_versionCode);
}
public boolean setVersionCode(int val){
return setManifestAttributeInt(ATTR_versionCode, val);
}
public String getVersionName(){
return getManifestAttributeString(ATTR_versionName);
}
public boolean setVersionName(String packageName){
return setManifestAttributeString(ATTR_versionName, packageName);
}
private String getManifestAttributeString(String name){
ResXmlElement manifestElement=getManifestElement();
if(manifestElement==null){
return null;
}
ResXmlAttribute attribute= manifestElement.searchAttributeByName(name);
if(attribute==null){
return null;
}
int raw=attribute.getRawValue();
ResXmlStringPool pool = getStringPool();
ResXmlString resXmlString = pool.get(raw);
if(resXmlString==null){
return null;
}
return resXmlString.getHtml();
}
private boolean setManifestAttributeString(String name, String value){
ResXmlElement manifestElement=getManifestElement();
if(manifestElement==null){
return false;
}
ResXmlAttribute attribute= manifestElement.searchAttributeByName(name);
if(attribute==null){
return false;
}
attribute.setValueType(ValueType.STRING);
ResXmlString resXmlString=attribute.setValueString(value);
return resXmlString!=null;
}
private boolean setManifestAttributeInt(String name, int value){
ResXmlElement manifestElement=getManifestElement();
if(manifestElement==null){
return false;
}
ResXmlAttribute attribute= manifestElement.searchAttributeByName(name);
if(attribute==null){
return false;
}
attribute.setValueType(ValueType.INT_DEC);
attribute.setValueString(String.valueOf(value));
attribute.setRawValue(value);
return true;
}
private Integer getManifestAttributeInt(String name){
ResXmlElement manifestElement=getManifestElement();
if(manifestElement==null){
return null;
}
ResXmlAttribute attribute= manifestElement.searchAttributeByName(name);
if(attribute==null){
return null;
}
return attribute.getRawValue();
}
private ResXmlElement getManifestElement(){
ResXmlElement manifestElement=getResXmlElement();
if(manifestElement==null){
return null;
}
if(!TAG_MANIFEST.equals(manifestElement.getTag())){
return null;
}
return manifestElement;
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();
builder.append(getClass().getSimpleName());
builder.append("{");
builder.append(ATTR_PACKAGE).append("=").append(getPackageName());
builder.append(", ").append(ATTR_versionCode).append("=").append(getVersionCode());
builder.append(", ").append(ATTR_versionName).append("=").append(getVersionName());
builder.append(", ").append(ATTR_compileSdkVersion).append("=").append(getCompileSdkVersion());
builder.append(", ").append(ATTR_compileSdkVersionCodename).append("=").append(getCompileSdkVersionCodename());
List<String> allPermissions=getPermissions();
builder.append(", PERMISSIONS[");
boolean appendOnce=false;
for(String permissions:allPermissions){
if(appendOnce){
builder.append(", ");
}
builder.append(permissions);
appendOnce=true;
}
builder.append("]");
builder.append("}");
return builder.toString();
}
private static final String TAG_MANIFEST="manifest";
private static final String TAG_uses_permission="uses-permission";
private static final String ATTR_compileSdkVersion="compileSdkVersion";
private static final String ATTR_compileSdkVersionCodename="compileSdkVersionCodename";
private static final String ATTR_installLocation="installLocation";
private static final String ATTR_PACKAGE="package";
private static final String ATTR_platformBuildVersionCode="platformBuildVersionCode";
private static final String ATTR_platformBuildVersionName="platformBuildVersionName";
private static final String ATTR_versionCode="versionCode";
private static final String ATTR_versionName="versionName";
private static final String ATTR_android_name="name";
}

View File

@ -57,6 +57,15 @@ public class BaseXmlChunk extends BaseChunk {
public int getStringReference(){
return mStringReference.get();
}
public ResXmlString setString(String str){
ResXmlStringPool pool = getStringPool();
if(pool==null){
return null;
}
ResXmlString xmlString = pool.getOrCreate(str);
setStringReference(xmlString.getIndex());
return xmlString;
}
public ResXmlStringPool getStringPool(){
@ -82,6 +91,13 @@ public class BaseXmlChunk extends BaseChunk {
}
return null;
}
ResXmlString getOrCreateResXmlString(String str){
ResXmlStringPool stringPool=getStringPool();
if(stringPool!=null){
return stringPool.getOrCreate(str);
}
return null;
}
String getString(int ref){
ResXmlString xmlString=getResXmlString(ref);
if(xmlString!=null){
@ -96,6 +112,7 @@ public class BaseXmlChunk extends BaseChunk {
public String getUri(){
return getString(getNamespaceReference());
}
public ResXmlElement getParentResXmlElement(){
Block parent=getParent();
while (parent!=null){

View File

@ -17,10 +17,10 @@ public class ResXmlAttribute extends FixedBlockContainer {
private final IntegerItem mRawValue;
public ResXmlAttribute() {
super(7);
mNamespaceReference =new IntegerItem();
mNamespaceReference =new IntegerItem(-1);
mNameReference =new IntegerItem();
mValueStringReference =new IntegerItem();
mNameType=new ShortItem();
mNameType=new ShortItem((short) 0x0008);
mReserved =new ByteItem();
mValueTypeByte=new ByteItem();
mRawValue=new IntegerItem();
@ -75,6 +75,11 @@ public class ResXmlAttribute extends FixedBlockContainer {
public ValueType getValueType(){
return ValueType.valueOf(getValueTypeByte());
}
public void setValueType(ValueType valueType){
if(valueType!=null){
setValueTypeByte(valueType.getByte());
}
}
public String getFullName(){
String name=getName();
if(name==null){
@ -100,12 +105,86 @@ public class ResXmlAttribute extends FixedBlockContainer {
}
return startNamespace.getPrefix();
}
public ResXmlStartNamespace getStartNamespace(){
ResXmlElement xmlElement=getParentResXmlElement();
if(xmlElement==null){
return null;
}
return xmlElement.getStartNamespaceByUriRef(getNamespaceReference());
}
public String getValueString(){
return getString(getValueStringReference());
}
ResXmlString setValueString(String str){
ResXmlString resXmlString=getOrCreateResXmlString(str);
if(resXmlString==null){
return null;
}
int ref=resXmlString.getIndex();
setValueStringReference(ref);
if(getValueType()==ValueType.STRING){
setRawValue(ref);
}
return resXmlString;
}
public int getNameResourceID(){
return getResourceId(getNameReference());
}
public boolean setName(String name, int resourceId){
ResXmlStringPool stringPool=getStringPool();
if(stringPool==null){
return false;
}
String old=getName();
if(resourceId==0){
if(name.equals(old)){
return false;
}
ResXmlString resXmlString=stringPool.getOrCreate(name);
setNameReference(resXmlString.getIndex());
return true;
}
ResXmlIDMap xmlIDMap=getResXmlIDMap();
if(xmlIDMap==null){
return false;
}
int oldId=getNameResourceID();
if(oldId==resourceId){
if(name.equals(old)){
return false;
}
}
ResXmlID resXmlID=xmlIDMap.getByResId(resourceId);
if(resXmlID!=null){
int ref=resXmlID.getIndex();
ResXmlString idName=stringPool.get(ref);
if(idName != null){
if(name.equals(idName.getHtml())){
setNameReference(ref);
}else {
idName.set(name);
}
return true;
}
}
int stringsCount=stringPool.countStrings();
int idCount=xmlIDMap.getResXmlIDArray().childesCount();
if(idCount>stringsCount){
xmlIDMap.addResourceId(idCount, resourceId);;
stringPool.getStringsArray().ensureSize(idCount+1);
ResXmlString resXmlString=stringPool.get(idCount);
resXmlString.set(name);
setNameReference(idCount);
return true;
}
xmlIDMap.addResourceId(stringsCount, resourceId);
stringPool.getStringsArray().ensureSize(stringsCount+1);
ResXmlString resXmlString=stringPool.get(stringsCount);
resXmlString.set(name);
setNameReference(stringsCount);
return true;
}
private int getResourceId(int ref){
if(ref<0){
return 0;
@ -139,6 +218,13 @@ public class ResXmlAttribute extends FixedBlockContainer {
}
return null;
}
private ResXmlString getOrCreateResXmlString(String str){
ResXmlStringPool stringPool=getStringPool();
if(stringPool!=null){
return stringPool.getOrCreate(str);
}
return null;
}
private ResXmlStringPool getStringPool(){
ResXmlElement xmlElement=getParentResXmlElement();
if(xmlElement!=null){
@ -153,7 +239,7 @@ public class ResXmlAttribute extends FixedBlockContainer {
}
return null;
}
private ResXmlElement getParentResXmlElement(){
public ResXmlElement getParentResXmlElement(){
Block parent=getParent();
while (parent!=null){
if(parent instanceof ResXmlElement){

View File

@ -2,20 +2,94 @@ package com.reandroid.lib.arsc.chunk.xml;
import com.reandroid.lib.arsc.chunk.ChunkType;
import com.reandroid.lib.arsc.chunk.BaseChunk;
import com.reandroid.lib.arsc.container.SingleBlockContainer;
import com.reandroid.lib.arsc.header.HeaderBlock;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.pool.ResXmlStringPool;
import java.io.*;
public class ResXmlBlock extends BaseChunk {
private final ResXmlStringPool mResXmlStringPool;
private final ResXmlIDMap mResXmlIDMap;
private final ResXmlElement mResXmlElement;
private ResXmlElement mResXmlElement;
private final SingleBlockContainer<ResXmlElement> mResXmlElementContainer;
public ResXmlBlock() {
super(ChunkType.XML,3);
this.mResXmlStringPool=new ResXmlStringPool(true);
this.mResXmlIDMap=new ResXmlIDMap();
this.mResXmlElement=new ResXmlElement();
this.mResXmlElementContainer=new SingleBlockContainer<>();
this.mResXmlElementContainer.setItem(mResXmlElement);
addChild(mResXmlStringPool);
addChild(mResXmlIDMap);
addChild(mResXmlElement);
addChild(mResXmlElementContainer);
}
@Override
public void onReadBytes(BlockReader reader) throws IOException {
HeaderBlock headerBlock=reader.readHeaderBlock();
if(headerBlock==null){
return;
}
BlockReader chunkReader=reader.create(reader.getPosition(), headerBlock.getChunkSize());
ChunkType chunkType=headerBlock.getChunkType();
if(chunkType==ChunkType.XML){
getHeaderBlock().readBytes(chunkReader);
}else {
throw new IOException("Not ResXmlBlock: "+reader+", Header="+headerBlock);
}
while (chunkReader.isAvailable()){
boolean readOk=readNext(chunkReader);
if(!readOk){
break;
}
}
reader.offset(headerBlock.getChunkSize());
chunkReader.close();
onChunkLoaded();
}
private boolean readNext(BlockReader reader) throws IOException {
if(!reader.isAvailable()){
return false;
}
int position=reader.getPosition();
HeaderBlock headerBlock=reader.readHeaderBlock();
if(headerBlock==null){
return false;
}
ChunkType chunkType=headerBlock.getChunkType();
if(chunkType==ChunkType.STRING){
mResXmlStringPool.readBytes(reader);
}else if(chunkType==ChunkType.XML_RESOURCE_MAP){
mResXmlIDMap.readBytes(reader);
}else if(isElementChunk(chunkType)){
mResXmlElementContainer.readBytes(reader);
return reader.isAvailable();
}else {
throw new IOException("Unexpected chunk "+headerBlock);
}
return reader.isAvailable() && position!=reader.getPosition();
}
private boolean isElementChunk(ChunkType chunkType){
if(chunkType==ChunkType.XML_START_ELEMENT){
return true;
}
if(chunkType==ChunkType.XML_END_ELEMENT){
return true;
}
if(chunkType==ChunkType.XML_START_NAMESPACE){
return true;
}
if(chunkType==ChunkType.XML_END_NAMESPACE){
return true;
}
if(chunkType==ChunkType.XML_CDATA){
return true;
}
if(chunkType==ChunkType.XML_LAST_CHUNK){
return true;
}
return false;
}
public ResXmlStringPool getStringPool(){
return mResXmlStringPool;
@ -26,8 +100,76 @@ public class ResXmlBlock extends BaseChunk {
public ResXmlElement getResXmlElement(){
return mResXmlElement;
}
public void setResXmlElement(ResXmlElement resXmlElement){
this.mResXmlElement=resXmlElement;
this.mResXmlElementContainer.setItem(resXmlElement);
}
@Override
protected void onChunkRefreshed() {
}
@Override
public int onWriteBytes(OutputStream stream) throws IOException{
int result=super.onWriteBytes(stream);
stream.flush();
stream.close();
return result;
}
public void readBytes(File file) throws IOException{
BlockReader reader=new BlockReader(file);
super.readBytes(reader);
}
public void readBytes(InputStream inputStream) throws IOException{
BlockReader reader=new BlockReader(inputStream);
super.readBytes(reader);
}
public final int writeBytes(File file) throws IOException{
if(isNull()){
throw new IOException("Can NOT save null block");
}
File dir=file.getParentFile();
if(dir!=null && !dir.exists()){
dir.mkdirs();
}
OutputStream outputStream=new FileOutputStream(file);
return super.writeBytes(outputStream);
}
public static boolean isResXmlBlock(File file){
if(file==null){
return false;
}
try {
InputStream inputStream=new FileInputStream(file);
return isResXmlBlock(inputStream);
} catch (FileNotFoundException ignored) {
return false;
}
}
public static boolean isResXmlBlock(InputStream inputStream){
try {
HeaderBlock headerBlock=BlockReader.readHeaderBlock(inputStream);
return isResXmlBlock(headerBlock);
} catch (IOException ignored) {
return false;
}
}
public static boolean isResXmlBlock(BlockReader blockReader){
if(blockReader==null){
return false;
}
try {
HeaderBlock headerBlock = blockReader.readHeaderBlock();
return isResXmlBlock(headerBlock);
} catch (IOException ignored) {
return false;
}
}
public static boolean isResXmlBlock(HeaderBlock headerBlock){
if(headerBlock==null){
return false;
}
ChunkType chunkType=headerBlock.getChunkType();
return chunkType==ChunkType.XML;
}
}

View File

@ -7,6 +7,7 @@ import com.reandroid.lib.arsc.container.FixedBlockContainer;
import com.reandroid.lib.arsc.container.SingleBlockContainer;
import com.reandroid.lib.arsc.header.HeaderBlock;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.item.ResXmlString;
import com.reandroid.lib.arsc.pool.ResXmlStringPool;
@ -38,6 +39,42 @@ public class ResXmlElement extends FixedBlockContainer {
addChild(4, mEndElementContainer);
addChild(5, mEndNamespaceList);
}
public List<ResXmlElement> searchElementsByTagName(String name){
List<ResXmlElement> results=new ArrayList<>();
if(name==null){
return results;
}
for(ResXmlElement child:listElements()){
if(name.equals(child.getTag())||name.equals(child.getTagName())){
results.add(child);
}
}
return results;
}
public ResXmlAttribute searchAttributeByName(String name){
ResXmlStartElement startElement=getStartElement();
if(startElement!=null){
return startElement.searchAttributeByName(name);
}
return null;
}
public void setTag(String tag){
ResXmlStringPool pool = getStringPool();
if(pool==null){
return;
}
ensureStartEndElement();
ResXmlStartElement start=getStartElement();
String prefix=null;
String name=tag;
int i=tag.lastIndexOf(':');
if(i>=0){
prefix=tag.substring(0,i);
i++;
name=tag.substring(i);
}
start.setName(name);
}
public String getTagName(){
ResXmlStartElement startElement=getStartElement();
if(startElement!=null){
@ -45,6 +82,27 @@ public class ResXmlElement extends FixedBlockContainer {
}
return null;
}
public String getTag(){
ResXmlStartElement startElement=getStartElement();
if(startElement!=null){
return startElement.getName();
}
return null;
}
public String getTagUri(){
ResXmlStartElement startElement=getStartElement();
if(startElement!=null){
return startElement.getUri();
}
return null;
}
public String getTagPrefix(){
ResXmlStartElement startElement=getStartElement();
if(startElement!=null){
return startElement.getPrefix();
}
return null;
}
public Collection<ResXmlAttribute> listResXmlAttributes(){
ResXmlStartElement startElement=getStartElement();
if(startElement!=null){
@ -182,6 +240,22 @@ public class ResXmlElement extends FixedBlockContainer {
start.setResXmlEndElement(end);
end.setResXmlStartElement(start);
}
private void ensureStartEndElement(){
ResXmlStartElement start=getStartElement();
ResXmlEndElement end=getEndElement();
if(start!=null && end!=null){
return;
}
if(start==null){
start=new ResXmlStartElement();
setStartElement(start);
}
if(end==null){
end=new ResXmlEndElement();
setEndElement(end);
}
linkStartEndElement();
}
private void linkStartEndNameSpaces(){
if(!isNamespaceBalanced()){
return;
@ -227,9 +301,9 @@ public class ResXmlElement extends FixedBlockContainer {
unBalancedFinish(reader);
}else {
readBytes(reader);
}
return;
}
}
linkStartEnd();
onFinishedRead(reader, headerBlock);
}
@ -245,7 +319,14 @@ public class ResXmlElement extends FixedBlockContainer {
}
private void onFinishedUnexpected(BlockReader reader) throws IOException{
throw new IOException("Unexpected finish reading: "+reader.toString());
StringBuilder builder=new StringBuilder();
builder.append("Unexpected finish reading: reader=").append(reader.toString());
HeaderBlock header = reader.readHeaderBlock();
if(header!=null){
builder.append(", next header=");
builder.append(header.toString());
}
throw new IOException(builder.toString());
}
private void onStartElement(BlockReader reader) throws IOException{
if(hasStartElement()){
@ -298,9 +379,25 @@ public class ResXmlElement extends FixedBlockContainer {
throw new IOException("Unbalanced namespace: start="
+mStartNamespaceList.size()+", end="+mEndNamespaceList.size());
}
if(!isElementBalanced()){
throw new IOException("Unbalanced element: hasStart="
+hasStartElement()+", hasEnd="+hasEndElement());
// Should not happen unless corrupted file, auto corrected above
StringBuilder builder=new StringBuilder();
builder.append("Unbalanced element: start=");
ResXmlStartElement startElement=getStartElement();
if(startElement!=null){
builder.append(startElement);
}else {
builder.append("null");
}
builder.append(", end=");
ResXmlEndElement endElement=getEndElement();
if(endElement!=null){
builder.append(endElement);
}else {
builder.append("null");
}
throw new IOException(builder.toString());
}
}
@Override
@ -324,4 +421,13 @@ public class ResXmlElement extends FixedBlockContainer {
}
return "NULL";
}
static ResXmlElement newResXmlElement(String tag){
ResXmlElement resXmlElement=new ResXmlElement();
ResXmlStartElement startElement=new ResXmlStartElement();
resXmlElement.setStartElement(startElement);
ResXmlEndElement endElement=new ResXmlEndElement();
resXmlElement.setEndElement(endElement);
resXmlElement.setTag(tag);
return resXmlElement;
}
}

View File

View File

View File

View File

@ -3,6 +3,7 @@ package com.reandroid.lib.arsc.chunk.xml;
import com.reandroid.lib.arsc.chunk.ChunkType;
import com.reandroid.lib.arsc.array.ResXmlAttributeArray;
import com.reandroid.lib.arsc.item.IntegerItem;
import com.reandroid.lib.arsc.item.ResXmlString;
import com.reandroid.lib.arsc.item.ShortItem;
import java.util.Collection;
@ -31,6 +32,17 @@ public class ResXmlStartElement extends BaseXmlChunk {
addChild(mClassAttribute);
addChild(mAttributeArray);
}
public ResXmlAttribute searchAttributeByName(String name){
if(name==null){
return null;
}
for(ResXmlAttribute attribute:listResXmlAttributes()){
if(name.equals(attribute.getFullName()) || name.equals(attribute.getName())){
return attribute;
}
}
return null;
}
public String getTagName(){
String prefix=getPrefix();
String name=getName();
@ -39,6 +51,9 @@ public class ResXmlStartElement extends BaseXmlChunk {
}
return prefix+":"+name;
}
public void setName(String name){
setString(name);
}
public Collection<ResXmlAttribute> listResXmlAttributes(){
return getResXmlAttributeArray().listItems();
}

View File

@ -3,6 +3,7 @@ package com.reandroid.lib.arsc.chunk.xml;
import com.reandroid.lib.arsc.chunk.ChunkType;
import com.reandroid.lib.arsc.item.IntegerItem;
import com.reandroid.lib.arsc.item.ResXmlString;
import com.reandroid.lib.arsc.pool.ResXmlStringPool;
public class ResXmlText extends BaseXmlChunk {
private final IntegerItem mReserved;
@ -24,6 +25,15 @@ public class ResXmlText extends BaseXmlChunk {
public void setTextReference(int ref){
setNamespaceReference(ref);
}
public void setText(String text){
ResXmlStringPool stringPool=getStringPool();
if(stringPool==null){
return;
}
ResXmlString resXmlString = stringPool.getOrCreate(text);
int ref=resXmlString.getIndex();
setTextReference(ref);
}
@Override
public String toString(){
String txt=getText();

View File

View File

View File

View File

View File

@ -1,85 +0,0 @@
package com.reandroid.lib.arsc.decoder;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.value.ResValueBag;
import com.reandroid.lib.arsc.value.ResValueBagItem;
import com.reandroid.lib.arsc.value.ValueType;
public abstract class ResDecoder<InputBlock extends Block, Output> implements ResourceNameProvider{
private final ResourceNameStore resourceNameStore;
public ResDecoder(ResourceNameStore store){
this.resourceNameStore=store;
}
public String decodeAttribute(int attrResId, int rawValue){
ResValueBag valueBag=getAttributeBag(attrResId);
if(valueBag==null){
return null;
}
ResValueBagItem[] bagItems=valueBag.getBagItems();
if(bagItems==null||bagItems.length==0){
return null;
}
int len=bagItems.length;
ResValueBagItem firstAttrChild=bagItems[0];
if(len==1){
return null;
}
if(isFlagAttr(firstAttrChild)){
for(int i=1;i<len;i++){
ResValueBagItem bagItem=bagItems[i];
int data=bagItem.getData();
if(data!=rawValue){
continue;
}
int id=bagItem.getId();
return getResourceName(id, false);
}
return null;
}
StringBuilder builder=new StringBuilder();
boolean appendOnce=false;
for(int i=1;i<len;i++){
ResValueBagItem bagItem=bagItems[i];
int data=bagItem.getData();
if((data&rawValue)==0){
continue;
}
int id=bagItem.getId();
String name=getResourceName(id, false);
if(appendOnce){
builder.append("|");
}
builder.append(name);
builder.append(data);
appendOnce=true;
}
if(!appendOnce){
return null;
}
builder.append(":");
builder.append(rawValue);
builder.append(firstAttrChild.getValueType());
return builder.toString();
}
private boolean isFlagAttr(ResValueBagItem firstAttrChild){
ValueType valueType=firstAttrChild.getValueType();
return valueType==ValueType.FIRST_INT;
}
public ResourceNameStore getResourceNameStore() {
return resourceNameStore;
}
@Override
public String getResourceFullName(int resId, boolean includePackageName) {
return getResourceNameStore().getResourceFullName(resId, includePackageName);
}
@Override
public String getResourceName(int resId, boolean includePackageName) {
return getResourceNameStore().getResourceName(resId, includePackageName);
}
@Override
public ResValueBag getAttributeBag(int resId) {
return getResourceNameStore().getAttributeBag(resId);
}
public abstract Output decode(byte currentPackageId, InputBlock inputBlock);
}

View File

@ -1,10 +0,0 @@
package com.reandroid.lib.arsc.decoder;
import com.reandroid.lib.arsc.value.ResValueBag;
public interface ResourceNameProvider {
String getResourceFullName(int resId, boolean includePackageName);
String getResourceName(int resId, boolean includePackageName);
ResValueBag getAttributeBag(int resId);
}

View File

@ -1,52 +0,0 @@
package com.reandroid.lib.arsc.decoder;
import com.reandroid.lib.arsc.value.ResValueBag;
import java.util.HashSet;
import java.util.Set;
public class ResourceNameStore implements ResourceNameProvider {
private final Set<ResourceNameProvider> resourceNameProviders;
public ResourceNameStore(){
this.resourceNameProviders=new HashSet<>();
}
public void addResourceNameProvider(ResourceNameProvider provider){
if(provider!=null){
resourceNameProviders.add(provider);
}
}
public Set<ResourceNameProvider> getResourceNameProviders(){
return resourceNameProviders;
}
@Override
public String getResourceFullName(int resId, boolean includePackageName) {
for(ResourceNameProvider provider:this.resourceNameProviders){
String name=provider.getResourceFullName(resId, includePackageName);
if(name!=null){
return name;
}
}
return null;
}
@Override
public String getResourceName(int resId, boolean includePackageName) {
for(ResourceNameProvider provider:this.resourceNameProviders){
String name=provider.getResourceName(resId, includePackageName);
if(name!=null){
return name;
}
}
return null;
}
@Override
public ResValueBag getAttributeBag(int resId) {
for(ResourceNameProvider provider:this.resourceNameProviders){
ResValueBag resValueBag=provider.getAttributeBag(resId);
if(resValueBag!=null){
return resValueBag;
}
}
return null;
}
}

View File

@ -1,8 +1,181 @@
package com.reandroid.lib.arsc.decoder;
import com.reandroid.lib.arsc.value.ValueType;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.group.EntryGroup;
import com.reandroid.lib.arsc.item.TableString;
import com.reandroid.lib.arsc.pool.TableStringPool;
import com.reandroid.lib.arsc.value.*;
import com.reandroid.lib.arsc.value.attribute.AttributeBag;
import com.reandroid.lib.common.EntryStore;
import java.util.Collection;
import java.util.Iterator;
public class ValueDecoder {
public static String decodeAttributeName(EntryStore store, PackageBlock currentPackage, int resourceId){
EntryGroup entryGroup=searchEntryGroup(store, currentPackage, resourceId);
if(entryGroup==null){
return String.format("@0x%08x", resourceId);
}
EntryBlock entryBlock=entryGroup.pickOne();
if(entryBlock==null){
return String.format("@0x%08x", resourceId);
}
String prefix=null;
if(currentPackage!=null){
String name=currentPackage.getPackageName();
String other=entryBlock.getPackageBlock().getPackageName();
if(!name.equals(other)){
prefix=other+":";
}
}
String name=entryGroup.getSpecName();
if(prefix!=null){
name=prefix+name;
}
return name;
}
public static String decodeAttribute(EntryStore store, int attrResId, int rawValue){
AttributeBag attributeBag = getAttributeBag(store, attrResId);
if(attributeBag==null){
return null;
}
return attributeBag.decodeAttributeValue(store, rawValue);
}
public static String decodeEntryValue(EntryStore store, PackageBlock currentPackage, ValueType valueType, int data){
if(store==null || currentPackage==null){
return null;
}
if(valueType==ValueType.STRING){
return decodeIntEntryString(currentPackage, data);
}
boolean is_reference=false;
boolean is_attribute=false;
if(valueType==ValueType.REFERENCE){
if(data==0){
return "@null";
}
is_reference=true;
}
if(valueType==ValueType.ATTRIBUTE){
if(data==0){
return "?null";
}
is_attribute=true;
}
if(is_reference || is_attribute){
String ref=buildReferenceValue(store, valueType, currentPackage.getPackageName(), data);
if(ref!=null){
return ref;
}
char atOrQues=is_reference?'@':'?';
ref=atOrQues+toHexResourceId(data);
return ref;
}
return decode(valueType, data);
}
public static String decodeIntEntry(EntryStore store, EntryBlock entryBlock){
if(entryBlock==null){
return null;
}
BaseResValue baseResValue = entryBlock.getResValue();
if(!(baseResValue instanceof ResValueInt)){
return null;
}
ResValueInt resValueInt=(ResValueInt)baseResValue;
return decodeIntEntry(store, resValueInt);
}
public static String decodeIntEntry(EntryStore store, ResValueInt resValueInt){
if(resValueInt==null){
return null;
}
EntryBlock parentEntry=resValueInt.getEntryBlock();
if(parentEntry==null){
return null;
}
ValueType valueType=resValueInt.getValueType();
int data=resValueInt.getData();
return decodeIntEntry(store, parentEntry, valueType, data);
}
public static String decodeIntEntry(EntryStore store, ResValueBagItem bagItem){
if(bagItem==null){
return null;
}
EntryBlock parentEntry=bagItem.getEntryBlock();
if(parentEntry==null){
return null;
}
ValueType valueType=bagItem.getValueType();
int data=bagItem.getData();
return decodeIntEntry(store, parentEntry, valueType, data);
}
public static String decodeIntEntry(EntryStore store, EntryBlock parentEntry, ValueType valueType, int data){
if(valueType==ValueType.NULL){
return "@empty";
}
if(valueType==ValueType.STRING){
return decodeIntEntryString(parentEntry, data);
}
boolean is_reference=false;
boolean is_attribute=false;
if(valueType==ValueType.REFERENCE){
if(data==0){
return "@null";
}
is_reference=true;
}
if(valueType==ValueType.ATTRIBUTE){
if(data==0){
return "?null";
}
is_attribute=true;
}
if(is_reference || is_attribute){
String ref=buildReferenceValue(store, parentEntry, valueType, data);
if(ref!=null){
return ref;
}
char atOrQues=is_reference?'@':'?';
ref=atOrQues+toHexResourceId(data);
return ref;
}
return decode(valueType, data);
}
public static String buildReferenceValue(EntryStore store, EntryBlock entryBlock){
if(entryBlock==null){
return null;
}
BaseResValue baseResValue = entryBlock.getResValue();
if(!(baseResValue instanceof ResValueInt)){
return null;
}
ResValueInt resValueInt=(ResValueInt)baseResValue;
int resourceId=resValueInt.getData();
ValueType valueType=resValueInt.getValueType();
return buildReferenceValue(store, entryBlock, valueType, resourceId);
}
public static String decode(EntryStore entryStore, int currentPackageId, int nameResourceId, ValueType valueType, int rawVal){
String currPackageName=getPackageName(entryStore, currentPackageId);
String result=buildReferenceValue(entryStore, valueType, currPackageName, rawVal);
if(result!=null){
return result;
}
if(valueType==ValueType.STRING){
// Should not happen the string could be in ResXmlBlock, but if you are lazy here it goes
return decodeString(entryStore, currentPackageId, rawVal);
}
if(valueType==ValueType.FIRST_INT||valueType==ValueType.INT_HEX){
result=decodeAttribute(entryStore, nameResourceId, rawVal);
if(result!=null){
return result;
}
}
return decode(valueType, rawVal);
}
public static String decode(ValueType valueType, int data){
if(valueType==null){
return null;
@ -30,6 +203,229 @@ public class ValueDecoder {
}
return null;
}
public static String buildReference(String currentPackageName,
String referredPackageName,
char atOrQues,
String typeName,
String resourceName){
StringBuilder builder=new StringBuilder();
if(atOrQues!=0){
builder.append(atOrQues);
}
if(!isEqualString(currentPackageName, referredPackageName)){
if(!isEmpty(currentPackageName) && !isEmpty(referredPackageName)){
builder.append(referredPackageName);
if(!referredPackageName.endsWith(":")){
builder.append(':');
}
}
}
if(!isEmpty(typeName)){
builder.append(typeName);
builder.append('/');
}
builder.append(resourceName);
return builder.toString();
}
private static String buildReferenceValue(EntryStore store, EntryBlock entryBlock, ValueType valueType, int resourceId){
if(entryBlock==null){
return null;
}
EntryGroup value=searchEntryGroup(store, entryBlock, resourceId);
if(value==null){
return null;
}
return buildReferenceValue(valueType, entryBlock, value);
}
private static String buildReferenceValue(ValueType valueType, EntryBlock entryBlock, EntryGroup value){
char atOrQues;
if(valueType==ValueType.REFERENCE){
atOrQues='@';
}else if(valueType==ValueType.ATTRIBUTE){
atOrQues='?';
}else {
atOrQues=0;
}
String currentPackageName=getPackageName(entryBlock);
String referredPackageName=getPackageName(value);
String typeName=value.getTypeName();
String name=value.getSpecName();
return buildReference(currentPackageName, referredPackageName, atOrQues, typeName, name);
}
private static String buildReferenceValue(EntryStore entryStore, ValueType valueType, String currentPackageName, int resourceId){
char atOrQues;
if(valueType==ValueType.REFERENCE){
atOrQues='@';
}else if(valueType==ValueType.ATTRIBUTE){
atOrQues='?';
}else {
return null;
}
EntryGroup value=null;
if(entryStore!=null){
value=entryStore.getEntryGroup(resourceId);
}
if(value==null){
return atOrQues+toHexResourceId(resourceId);
}
String referredPackageName=getPackageName(value);
String typeName=value.getTypeName();
String name=value.getSpecName();
return buildReference(currentPackageName, referredPackageName, atOrQues, typeName, name);
}
private static String getPackageName(EntryStore entryStore, int packageOrResourceId){
if(entryStore==null || packageOrResourceId==0){
return null;
}
int pkgId=(packageOrResourceId>>24)&0xFF;
if(pkgId==0){
pkgId=packageOrResourceId;
}
Collection<PackageBlock> allPkg = entryStore.getPackageBlocks((byte) pkgId);
if(allPkg==null){
return null;
}
for(PackageBlock packageBlock:allPkg){
String name=packageBlock.getPackageName();
if(name!=null){
return name;
}
}
return null;
}
private static String getPackageName(EntryGroup entryGroup){
if(entryGroup==null){
return null;
}
return getPackageName(entryGroup.pickOne());
}
private static String getPackageName(EntryBlock entryBlock){
if(entryBlock==null){
return null;
}
PackageBlock packageBlock=entryBlock.getPackageBlock();
if(packageBlock==null){
return null;
}
return packageBlock.getPackageName();
}
private static EntryGroup searchEntryGroup(EntryStore store, EntryBlock entryBlock, int resourceId){
EntryGroup entryGroup=searchEntryGroup(entryBlock, resourceId);
if(entryGroup!=null){
return entryGroup;
}
if(store==null){
return null;
}
return store.getEntryGroup(resourceId);
}
private static EntryGroup searchEntryGroup(EntryBlock entryBlock, int resourceId){
if(entryBlock==null){
return null;
}
PackageBlock packageBlock=entryBlock.getPackageBlock();
if(packageBlock==null){
return null;
}
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock==null){
return null;
}
for(PackageBlock pkg:tableBlock.listPackages()){
EntryGroup entryGroup=pkg.getEntryGroup(resourceId);
if(entryGroup!=null){
return entryGroup;
}
}
return null;
}
private static EntryGroup searchEntryGroup(EntryStore store, PackageBlock packageBlock, int resourceId){
if(packageBlock!=null){
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock!=null){
for(PackageBlock pkg:tableBlock.listPackages()){
EntryGroup entryGroup=pkg.getEntryGroup(resourceId);
if(entryGroup!=null){
return entryGroup;
}
}
}
}
if(store!=null){
return store.getEntryGroup(resourceId);
}
return null;
}
private static String decodeIntEntryString(EntryBlock entryBlock, int data){
if(entryBlock==null){
return null;
}
PackageBlock packageBlock=entryBlock.getPackageBlock();
if(packageBlock==null){
return null;
}
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock==null){
return null;
}
TableStringPool pool = tableBlock.getTableStringPool();
TableString tableString=pool.get(data);
if(tableString==null){
return null;
}
return tableString.getHtml();
}
private static String decodeString(EntryStore entryStore, int packageOrResourceId, int stringRef){
if(entryStore==null||packageOrResourceId==0){
return null;
}
int pkgId=(packageOrResourceId>>24)&0xFF;
if(pkgId==0){
pkgId=packageOrResourceId;
}
Collection<PackageBlock> allPkg = entryStore.getPackageBlocks((byte) pkgId);
if(allPkg==null){
return null;
}
TableString tableString=null;
for(PackageBlock packageBlock:allPkg){
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock==null){
continue;
}
TableString ts=tableBlock.getTableStringPool().get(stringRef);
if(ts==null){
continue;
}
if(tableString==null){
tableString=ts;
}else {
// Duplicate result, could be from split apks
return null;
}
}
if(tableString!=null){
return tableString.getHtml();
}
return null;
}
private static String decodeIntEntryString(PackageBlock packageBlock, int data){
if(packageBlock==null){
return null;
}
TableBlock tableBlock=packageBlock.getTableBlock();
if(tableBlock==null){
return null;
}
TableStringPool pool = tableBlock.getTableStringPool();
TableString tableString=pool.get(data);
if(tableString==null){
return null;
}
return tableString.getHtml();
}
private static String decodeHex(int rawVal){
return String.format("0x%x", rawVal);
}
@ -99,6 +495,104 @@ public class ValueDecoder {
}
return 0;
}
private static String getResourceName(EntryStore store, EntryBlock entryBlock, int resourceId){
if(entryBlock!=null){
EntryGroup group=searchEntryGroup(entryBlock, resourceId);
if(group!=null){
String name=group.getSpecName();
if(name!=null){
return name;
}
}
}
if(store==null){
return null;
}
Collection<EntryGroup> foundGroups = store.getEntryGroups(resourceId);
return pickResourceName(foundGroups);
}
private static String pickResourceName(Collection<EntryGroup> groups){
if(groups==null){
return null;
}
for(EntryGroup entryGroup:groups){
String name=entryGroup.getSpecName();
if(name!=null){
return name;
}
}
return null;
}
private static AttributeBag getAttributeBag(EntryStore store, int resourceId){
ResValueBag resValueBag=getAttributeValueBag(store, resourceId);
if(resValueBag==null){
return null;
}
return AttributeBag.create(resValueBag);
}
private static ResValueBag getAttributeValueBag(EntryStore store, int resourceId){
if(store==null){
return null;
}
Collection<EntryGroup> foundGroups = store.getEntryGroups(resourceId);
ResValueBag best=null;
for(EntryGroup group:foundGroups){
ResValueBag valueBag= getAttributeValueBag(group);
best=chooseBest(best, valueBag);
}
return best;
}
private static ResValueBag getAttributeValueBag(EntryGroup entryGroup){
if(entryGroup==null){
return null;
}
ResValueBag best=null;
Iterator<EntryBlock> iterator=entryGroup.iterator(true);
while (iterator.hasNext()){
EntryBlock entryBlock=iterator.next();
ResValueBag valueBag= getAttributeValueBag(entryBlock);
best=chooseBest(best, valueBag);
}
return best;
}
private static ResValueBag getAttributeValueBag(EntryBlock entryBlock){
if(entryBlock==null){
return null;
}
BaseResValue baseResValue = entryBlock.getResValue();
if(baseResValue instanceof ResValueBag){
return (ResValueBag) baseResValue;
}
return null;
}
private static ResValueBag chooseBest(ResValueBag valueBag1, ResValueBag valueBag2){
if(valueBag1==null){
return valueBag2;
}
if(valueBag2==null){
return valueBag1;
}
if(valueBag2.getCount()>valueBag1.getCount()){
return valueBag2;
}
return valueBag1;
}
private static String toHexResourceId(int resourceId){
return String.format("0x%08x", resourceId);
}
private static boolean isEqualString(String str1, String str2){
if(isEmpty(str1)){
return isEmpty(str2);
}
return str1.equals(str2);
}
private static boolean isEmpty(String str){
if(str==null){
return true;
}
str=str.trim();
return str.length()==0;
}
private static final String[] DIMENSION_UNIT_STRS = new String[] { "px", "dip", "sp", "pt", "in", "mm" };
private static final float MANTISSA_MULT = 1.0f / (1 << 8);

View File

@ -1,139 +0,0 @@
package com.reandroid.lib.arsc.decoder.xml;
import com.reandroid.lib.arsc.chunk.xml.ResXmlAttribute;
import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock;
import com.reandroid.lib.arsc.chunk.xml.ResXmlElement;
import com.reandroid.lib.arsc.chunk.xml.ResXmlText;
import com.reandroid.lib.arsc.decoder.ResDecoder;
import com.reandroid.lib.arsc.decoder.ResourceNameStore;
import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.value.ValueType;
import java.util.Collection;
import java.util.List;
public class ResXmlDecoder extends ResDecoder<ResXmlBlock, String> {
private byte mCurrentPackageId;
public ResXmlDecoder(ResourceNameStore store){
super(store);
}
private String decodeToString(ResXmlBlock resXmlBlock){
StringBuilder builder=new StringBuilder();
builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
builder.append("\n");
String body=decodeToString(resXmlBlock.getResXmlElement());
builder.append(body);
return builder.toString();
}
private String decodeToString(ResXmlElement resXmlElement){
if(resXmlElement==null){
return null;
}
StringBuilder builder=new StringBuilder();
for(int i=0;i<resXmlElement.getDepth();i++){
builder.append(' ');
}
builder.append('<');
String tagName=resXmlElement.getTagName();
builder.append(tagName);
for(ResXmlAttribute attribute:resXmlElement.listResXmlAttributes()){
builder.append(' ');
String name=decodeAttributeFullName(attribute);
builder.append(name);
builder.append("=\"");
String value=decodeAttributeValue(attribute);
builder.append(value);
builder.append("\"");
}
boolean useEndTag=false;
ResXmlText resXmlText=resXmlElement.getResXmlText();
String text=null;
if(resXmlText!=null){
useEndTag=true;
text=resXmlText.getText();
}
List<ResXmlElement> childElements=resXmlElement.listElements();
if(!useEndTag){
useEndTag=childElements.size()>0;
}
if(!useEndTag){
builder.append("/>");
return builder.toString();
}
builder.append(">");
if(text!=null){
builder.append(text);
}
for(ResXmlElement child:childElements){
builder.append("\n");
String txtChild=decodeToString(child);
builder.append(txtChild);
}
if(childElements.size()>0){
builder.append("\n");
for(int i=0;i<resXmlElement.getDepth();i++){
builder.append(' ');
}
}
builder.append("</");
builder.append(tagName);
builder.append(">");
return builder.toString();
}
private String decodeAttributeValue(ResXmlAttribute attribute){
ValueType valueType=attribute.getValueType();
if(valueType==ValueType.FIRST_INT || valueType==ValueType.INT_HEX){
int nameId=attribute.getNameResourceID();
String val=decodeAttribute(nameId, attribute.getRawValue());
if(val!=null){
return val;
}
}
if(valueType==ValueType.REFERENCE){
int id=attribute.getRawValue();
byte pkgId= (byte) ((id>>24)&0xFF);
return getResourceFullName(id, pkgId!=getCurrentPackageId());
}
if(valueType==ValueType.STRING){
return attribute.getValueString();
}
String val= ValueDecoder.decode(valueType, attribute.getRawValue());
if(val!=null){
return val;
}
return attribute.getRawValue()+"["+valueType+"]";
}
private String decodeAttributeFullName(ResXmlAttribute attribute){
StringBuilder builder=new StringBuilder();
String prefix=getRealAttributeNamePrefix(attribute);
String name=getRealAttributeName(attribute);
if(prefix!=null&&!name.contains(":")){
builder.append(prefix);
builder.append(':');
}
builder.append(name);
return builder.toString();
}
private String getRealAttributeNamePrefix(ResXmlAttribute attribute){
/* TODO readjust wrong/stripped attribute name prefix prefix; */
return attribute.getNamePrefix();
}
private String getRealAttributeName(ResXmlAttribute attribute){
int nameId=attribute.getNameResourceID();
ResourceNameStore store=getResourceNameStore();
byte pkgId= (byte) ((nameId>>24)&0xFF);
String name=store.getResourceName(nameId, getCurrentPackageId()!=pkgId);
if(name!=null){
return name;
}
return attribute.getName();
}
private byte getCurrentPackageId(){
return mCurrentPackageId;
}
@Override
public String decode(byte currentPackageId, ResXmlBlock resXmlBlock) {
this.mCurrentPackageId=currentPackageId;
return decodeToString(resXmlBlock);
}
}

View File

View File

View File

View File

0
src/main/java/com/reandroid/lib/arsc/io/BlockLoad.java Normal file → Executable file
View File

View File

@ -324,4 +324,15 @@ public class BlockReader extends InputStream {
System.arraycopy(arr2, 0, result, arr1.length, len);
return result;
}
public static HeaderBlock readHeaderBlock(File file) throws IOException{
InputStream inputStream=new FileInputStream(file);
return readHeaderBlock(inputStream);
}
public static HeaderBlock readHeaderBlock(InputStream inputStream) throws IOException{
byte[] buffer=new byte[8];
inputStream.read(buffer);
inputStream.close();
BlockReader reader=new BlockReader(buffer);
return reader.readHeaderBlock();
}
}

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -9,6 +9,7 @@ import com.reandroid.lib.arsc.group.StringGroup;
import com.reandroid.lib.arsc.io.BlockLoad;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.item.*;
import com.reandroid.lib.arsc.pool.builder.StyleBuilder;
import java.io.IOException;
import java.util.*;
@ -63,6 +64,16 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
mUniqueMap=new HashMap<>();
}
public void recreateStyles(){
StyleArray styleArray = getStyleArray();
//styleArray.clearChildes();
StringArray<T> stringArray=getStringsArray();
for(T stringItem:stringArray.listItems()){
if(StyleBuilder.hasStyle(stringItem)){
StyleBuilder.buildStyle(stringItem);
}
}
}
public List<T> removeUnusedStrings(){
return getStringsArray().removeUnusedStrings();
}

View File

View File

View File

@ -15,4 +15,7 @@ public class SpecStringPool extends BaseStringPool<SpecString> {
StringArray<SpecString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
return new SpecStringArray(offsets, itemCount, itemStart, is_utf8);
}
@Override
public void recreateStyles(){
}
}

View File

View File

@ -17,4 +17,7 @@ public class TypeStringPool extends BaseStringPool<TypeString> {
StringArray<TypeString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
return new TypeStringArray(offsets, itemCount, itemStart, is_utf8);
}
@Override
public void recreateStyles(){
}
}

View File

@ -0,0 +1,16 @@
package com.reandroid.lib.arsc.pool.builder;
import java.util.List;
public class SpannedText {
private String mLeftText;
private String mTag;
private String mText;
private String mRightText;
private List<SpannedText> mChildes;
public SpannedText(){
}
public void parse(int start, String text){
int i=text.indexOf('<', start);
}
}

View File

@ -0,0 +1,29 @@
package com.reandroid.lib.arsc.pool.builder;
import com.reandroid.lib.arsc.item.StringItem;
import java.util.regex.Pattern;
public class StyleBuilder {
public static void buildStyle(StringItem stringItem){
System.out.println(stringItem.toString());
}
public static boolean hasStyle(StringItem stringItem){
if(stringItem==null){
return false;
}
return hasStyle(stringItem.getHtml());
}
public static boolean hasStyle(String text){
if(text==null){
return false;
}
int i=text.indexOf('<');
if(i<0){
return false;
}
i=text.indexOf('>');
return i>1;
}
private static final Pattern PATTERN_STYLE=Pattern.compile("");
}

View File

@ -1,9 +1,11 @@
package com.reandroid.lib.arsc.util;
import com.reandroid.lib.arsc.chunk.ChunkType;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.group.EntryGroup;
import com.reandroid.lib.arsc.header.HeaderBlock;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.item.ReferenceItem;
import com.reandroid.lib.arsc.item.TableString;
@ -15,9 +17,42 @@ import java.util.*;
public class FrameworkTable extends TableBlock {
private String mFrameworkTitle;
private String mFrameworkName;
private String mFrameworkVersion;
public FrameworkTable(){
super();
}
public String getFrameworkTitle(){
if(mFrameworkTitle==null){
mFrameworkTitle=loadProperty(PROP_TITLE);
}
return mFrameworkTitle;
}
public String getFrameworkName(){
if(mFrameworkName==null){
mFrameworkName=loadProperty(PROP_NAME);
}
return mFrameworkName;
}
public String getFrameworkVersion(){
if(mFrameworkVersion==null){
mFrameworkVersion=loadProperty(PROP_VERSION);
}
return mFrameworkVersion;
}
private void setFrameworkTitle(String value){
mFrameworkTitle=null;
writeProperty(PROP_TITLE, value);
}
public void setFrameworkName(String value){
mFrameworkName=null;
writeProperty(PROP_NAME, value);
}
public void setFrameworkVersion(String value){
mFrameworkVersion=null;
writeProperty(PROP_VERSION, value);
}
public int writeTable(File resourcesArscFile) throws IOException{
File dir=resourcesArscFile.getParentFile();
if(dir!=null && !dir.exists()){
@ -49,7 +84,7 @@ public class FrameworkTable extends TableBlock {
super.onReadBytes(reader);
reader.close();
}
public void optimize(){
public void optimize(String frameworkName, String frameworkVersion){
Map<Integer, EntryGroup> groupMap=scanAllEntryGroups();
for(EntryGroup group:groupMap.values()){
List<EntryBlock> entryBlockList=getEntriesToRemove(group);
@ -60,6 +95,9 @@ public class FrameworkTable extends TableBlock {
pkg.refresh();
}
optimizeTableString();
setFrameworkTitle(TITLE_STRING);
setFrameworkName(frameworkName);
setFrameworkVersion(frameworkVersion);
refresh();
}
private void optimizeTableString(){
@ -75,13 +113,14 @@ public class FrameworkTable extends TableBlock {
}
private void shrinkTableString(){
TableStringPool tableStringPool=getTableStringPool();
TableString zero=tableStringPool.get(0);
zero.set("Framework string table");
tableStringPool.getStringsArray().ensureSize(1);
TableString title=tableStringPool.get(0);
title.set(PROP_TITLE+":"+TITLE_STRING);
for(TableString tableString:tableStringPool.getStringsArray().listItems()){
if(tableString==zero){
if(tableString==title){
continue;
}
shrinkTableString(zero, tableString);
shrinkTableString(title, tableString);
}
tableStringPool.refresh();
}
@ -141,4 +180,106 @@ public class FrameworkTable extends TableBlock {
}
return results;
}
private TableString writeProperty(String name, String value){
if(!name.endsWith(":")){
name=name+":";
}
if(value==null){
value="";
}
if(!value.startsWith(name)){
value=name+value;
}
TableString tableString=loadPropertyString(name);
if(tableString!=null){
tableString.set(value);
}else {
TableStringPool tableStringPool=getTableStringPool();
tableString=tableStringPool.getOrCreate(value);
}
return tableString;
}
private String loadProperty(String name){
if(name==null){
return null;
}
if(!name.endsWith(":")){
name=name+":";
}
TableString tableString=loadPropertyString(name);
if(tableString==null){
return null;
}
String str=tableString.get().trim();
return str.substring(name.length());
}
private TableString loadPropertyString(String name){
if(name==null){
return null;
}
if(!name.endsWith(":")){
name=name+":";
}
TableStringPool tableStringPool=getTableStringPool();
int max=PROP_COUNT;
for(int i=0;i<max;i++){
TableString tableString=tableStringPool.get(i);
if(tableString==null){
break;
}
String str=tableString.get();
if(str==null){
continue;
}
str=str.trim();
if(str.startsWith(name)){
return tableString;
}
}
return null;
}
@Override
public String toString(){
HeaderBlock headerBlock=getHeaderBlock();
if(headerBlock.getChunkType()!= ChunkType.TABLE){
return super.toString();
}
StringBuilder builder=new StringBuilder();
builder.append(getClass().getSimpleName());
builder.append(": SIZE=").append(headerBlock.getChunkSize());
String str=getFrameworkTitle();
builder.append("\n");
if(str==null){
builder.append(PROP_TITLE).append(":null");
}else {
builder.append(str);
}
str=getFrameworkName();
builder.append("\n ").append(PROP_NAME).append(":");
if(str==null){
builder.append("null");
}else {
builder.append(str);
}
str=getFrameworkVersion();
builder.append("\n ").append(PROP_VERSION).append(":");
if(str==null){
builder.append("null");
}else {
builder.append(str);
}
Collection<PackageBlock> allPkg = listPackages();
builder.append("\n PACKAGES=").append(allPkg.size());
for(PackageBlock packageBlock:allPkg){
builder.append("\n ");
builder.append(String.format("0x%02x", packageBlock.getPackageId()));
builder.append(":").append(packageBlock.getPackageName());
}
return builder.toString();
}
private static final String TITLE_STRING="Framework table";
private static final String PROP_TITLE="TITLE";
private static final String PROP_NAME="NAME";
private static final String PROP_VERSION="VERSION";
private static final int PROP_COUNT=10;
}

View File

View File

View File

View File

View File

View File

View File

Some files were not shown because too many files have changed in this diff Show More