/*
* Copyright (c) 2009-2013 Panxiaobo
*
* 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 pxb.android.arsc;
import pxb.android.ResConst;
import pxb.android.StringItem;
import pxb.android.StringItems;
import pxb.android.axml.Util;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
/**
* Write pkgs to an arsc file
*
* @see ArscParser
* @author bob
*
*/
public class ArscWriter implements ResConst {
private static class PkgCtx {
Map<String, StringItem> keyNames = new HashMap<String, StringItem>();
StringItems keyNames0 = new StringItems();
public int keyStringOff;
int offset;
Pkg pkg;
int pkgSize;
List<StringItem> typeNames = new ArrayList<StringItem>();
StringItems typeNames0 = new StringItems();
int typeStringOff;
public void addKeyName(String name) {
if (keyNames.containsKey(name)) {
return;
}
StringItem stringItem = new StringItem(name);
keyNames.put(name, stringItem);
keyNames0.add(stringItem);
}
public void addTypeName(int id, String name) {
while (typeNames.size() <= id) {
typeNames.add(null);
}
StringItem item = typeNames.get(id);
if (item == null) {
typeNames.set(id, new StringItem(name));
} else {
throw new RuntimeException();
}
}
}
private static void D(String fmt, Object... args) {
}
private List<PkgCtx> ctxs = new ArrayList<PkgCtx>(5);
private List<Pkg> pkgs;
private Map<String, StringItem> strTable = new TreeMap<String, StringItem>();
private StringItems strTable0 = new StringItems();
public ArscWriter(List<Pkg> pkgs) {
this.pkgs = pkgs;
}
public static void main(String... args) throws IOException {
if (args.length < 2) {
System.err.println("asrc-write-test in.arsc out.arsc");
return;
}
byte[] data = Util.readFile(new File(args[0]));
List<Pkg> pkgs = new ArscParser(data).parse();
// ArscDumper.dump(pkgs);
byte[] data2 = new ArscWriter(pkgs).toByteArray();
// ArscDumper.dump(new ArscParser(data2).parse());
Util.writeFile(data2, new File(args[1]));
}
private void addString(String str) {
if (strTable.containsKey(str)) {
return;
}
StringItem stringItem = new StringItem(str);
strTable.put(str, stringItem);
strTable0.add(stringItem);
}
private int count() {
int size = 0;
size += 8 + 4;// chunk, pkgcount
{
int stringSize = strTable0.getSize();
if (stringSize % 4 != 0) {
stringSize += 4 - stringSize % 4;
}
size += 8 + stringSize;// global strings
}
for (PkgCtx ctx : ctxs) {
ctx.offset = size;
int pkgSize = 0;
pkgSize += 8 + 4 + 256;// chunk,pid+name
pkgSize += 4 * 4;
ctx.typeStringOff = pkgSize;
{
int stringSize = ctx.typeNames0.getSize();
if (stringSize % 4 != 0) {
stringSize += 4 - stringSize % 4;
}
pkgSize += 8 + stringSize;// type names
}
ctx.keyStringOff = pkgSize;
{
int stringSize = ctx.keyNames0.getSize();
if (stringSize % 4 != 0) {
stringSize += 4 - stringSize % 4;
}
pkgSize += 8 + stringSize;// key names
}
for (Type type : ctx.pkg.types.values()) {
type.wPosition = size + pkgSize;
pkgSize += 8 + 4 + 4 + 4 * type.specs.length; // trunk,id,entryCount,
// configs
for (Config config : type.configs) {
config.wPosition = pkgSize + size;
int configBasePostion = pkgSize;
pkgSize += 8 + 4 + 4 + 4; // trunk,id,entryCount,entriesStart
int size0 = config.id.length;
if (size0 % 4 != 0) {
size0 += 4 - size0 % 4;
}
pkgSize += size0;// config
if (pkgSize - configBasePostion > 0x0038) {
throw new RuntimeException("config id too big");
} else {
pkgSize = configBasePostion + 0x0038;
}
pkgSize += 4 * config.entryCount;// offset
config.wEntryStart = pkgSize - configBasePostion;
int entryBase = pkgSize;
for (ResEntry e : config.resources.values()) {
e.wOffset = pkgSize - entryBase;
pkgSize += 8;// size,flag,keyString
if (e.value instanceof BagValue) {
BagValue big = (BagValue) e.value;
pkgSize += 8 + big.map.size() * 12;
} else {
pkgSize += 8;
}
}
config.wChunkSize = pkgSize - configBasePostion;
}
}
ctx.pkgSize = pkgSize;
size += pkgSize;
}
return size;
}
private List<PkgCtx> prepare() throws IOException {
for (Pkg pkg : pkgs) {
PkgCtx ctx = new PkgCtx();
ctx.pkg = pkg;
ctxs.add(ctx);
for (Type type : pkg.types.values()) {
ctx.addTypeName(type.id - 1, type.name);
for (ResSpec spec : type.specs) {
ctx.addKeyName(spec.name);
}
for (Config config : type.configs) {
for (ResEntry e : config.resources.values()) {
Object object = e.value;
if (object instanceof BagValue) {
travelBagValue((BagValue) object);
} else {
travelValue((Value) object);
}
}
}
}
ctx.keyNames0.prepare();
ctx.typeNames0.addAll(ctx.typeNames);
ctx.typeNames0.prepare();
}
strTable0.prepare();
return ctxs;
}
public byte[] toByteArray() throws IOException {
prepare();
int size = count();
ByteBuffer out = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
write(out, size);
return out.array();
}
private void travelBagValue(BagValue bag) {
for (Map.Entry<Integer, Value> e : bag.map) {
travelValue(e.getValue());
}
}
private void travelValue(Value v) {
if (v.raw != null) {
addString(v.raw);
}
}
private void write(ByteBuffer out, int size) throws IOException {
out.putInt(RES_TABLE_TYPE | (0x000c << 16));
out.putInt(size);
out.putInt(ctxs.size());
{
int stringSize = strTable0.getSize();
int padding = 0;
if (stringSize % 4 != 0) {
padding = 4 - stringSize % 4;
}
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
out.putInt(stringSize + padding + 8);
strTable0.write(out);
out.put(new byte[padding]);
}
for (PkgCtx pctx : ctxs) {
if (out.position() != pctx.offset) {
throw new RuntimeException();
}
final int basePosition = out.position();
out.putInt(RES_TABLE_PACKAGE_TYPE | (0x011c << 16));
out.putInt(pctx.pkgSize);
out.putInt(pctx.pkg.id);
int p = out.position();
out.put(pctx.pkg.name.getBytes("UTF-16LE"));
out.position(p + 256);
out.putInt(pctx.typeStringOff);
out.putInt(pctx.typeNames0.size());
out.putInt(pctx.keyStringOff);
out.putInt(pctx.keyNames0.size());
{
if (out.position() - basePosition != pctx.typeStringOff) {
throw new RuntimeException();
}
int stringSize = pctx.typeNames0.getSize();
int padding = 0;
if (stringSize % 4 != 0) {
padding = 4 - stringSize % 4;
}
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
out.putInt(stringSize + padding + 8);
pctx.typeNames0.write(out);
out.put(new byte[padding]);
}
{
if (out.position() - basePosition != pctx.keyStringOff) {
throw new RuntimeException();
}
int stringSize = pctx.keyNames0.getSize();
int padding = 0;
if (stringSize % 4 != 0) {
padding = 4 - stringSize % 4;
}
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
out.putInt(stringSize + padding + 8);
pctx.keyNames0.write(out);
out.put(new byte[padding]);
}
for (Type t : pctx.pkg.types.values()) {
D("[%08x]write spec", out.position(), t.name);
if (t.wPosition != out.position()) {
throw new RuntimeException();
}
out.putInt(RES_TABLE_TYPE_SPEC_TYPE | (0x0010 << 16));
out.putInt(4 * 4 + 4 * t.specs.length);// size
out.putInt(t.id);
out.putInt(t.specs.length);
for (ResSpec spec : t.specs) {
out.putInt(spec.flags);
}
for (Config config : t.configs) {
D("[%08x]write config", out.position());
int typeConfigPosition = out.position();
if (config.wPosition != typeConfigPosition) {
throw new RuntimeException();
}
out.putInt(RES_TABLE_TYPE_TYPE | (0x0038 << 16));
out.putInt(config.wChunkSize);// size
out.putInt(t.id);
out.putInt(t.specs.length);
out.putInt(config.wEntryStart);
D("[%08x]write config ids", out.position());
out.put(config.id);
int size0 = config.id.length;
int padding = 0;
if (size0 % 4 != 0) {
padding = 4 - size0 % 4;
}
out.put(new byte[padding]);
out.position(typeConfigPosition + 0x0038);
D("[%08x]write config entry offsets", out.position());
for (int i = 0; i < config.entryCount; i++) {
ResEntry entry = config.resources.get(i);
if (entry == null) {
out.putInt(-1);
} else {
out.putInt(entry.wOffset);
}
}
if (out.position() - typeConfigPosition != config.wEntryStart) {
throw new RuntimeException();
}
D("[%08x]write config entrys", out.position());
for (ResEntry e : config.resources.values()) {
D("[%08x]ResTable_entry", out.position());
boolean isBag = e.value instanceof BagValue;
out.putShort((short) (isBag ? 16 : 8));
int flag = e.flag;
if (isBag) { // add complex flag
flag |= ArscParser.ENTRY_FLAG_COMPLEX;
} else { // remove
flag &= ~ArscParser.ENTRY_FLAG_COMPLEX;
}
out.putShort((short) flag);
out.putInt(pctx.keyNames.get(e.spec.name).index);
if (isBag) {
BagValue bag = (BagValue) e.value;
out.putInt(bag.parent);
out.putInt(bag.map.size());
for (Map.Entry<Integer, Value> entry : bag.map) {
out.putInt(entry.getKey());
writeValue(entry.getValue(), out);
}
} else {
writeValue((Value) e.value, out);
}
}
}
}
}
}
private void writeValue(Value value, ByteBuffer out) {
out.putShort((short) 8);
out.put((byte) 0);
out.put((byte) value.type);
if (value.type == ArscParser.TYPE_STRING) {
out.putInt(strTable.get(value.raw).index);
} else {
out.putInt(value.data);
}
}
}