// provide aiw4421.bin on stdin, get unpacked binary config on stdout.
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.crypto.spec.*;
import java.io.*;
import java.util.*;
class XtractCfg {
static class BytePairDecompressionStream extends FilterOutputStream {
boolean escaped = false;
int escapechar;
ByteArrayOutputStream mapstream = new ByteArrayOutputStream();
int mapstreamsize = -2;
java.util.Map<Integer,Short> pairexpansion;
public BytePairDecompressionStream(OutputStream parent) {
super(parent);
}
public void write(int b) throws IOException {
if (pairexpansion != null) {
if (escaped)
{
super.write(b);
escaped = false;
} else if((b & 0xff) == escapechar) {
escaped = true;
} else {
Short expansion = pairexpansion.get(b & 0xFF);
if (expansion == null)
super.write(b);
else {
super.write(expansion.shortValue() >> 8);
super.write(expansion.shortValue() & 0xFF);
}
}
}
else if (mapstreamsize == -2) {
escapechar = b & 0xff;
mapstreamsize = -1;
} else if (mapstreamsize == -1) {
mapstreamsize = 3*b;
} else if (mapstream.size() < mapstreamsize) {
mapstream.write(b);
} else {
if (b != 0x5b)
throw new IOException("Bad magic after map");
pairexpansion = new TreeMap<Integer,Short>();
byte[] mapdata = mapstream.toByteArray();
mapstream = null;
for(int i = 0;i < mapdata.length / 3;i++)
pairexpansion.put(new Integer(mapdata[i*3] & 0xFF),new Short((short)((mapdata[i*3+1] & 0xFF) * 0x100 +
(mapdata[i*3+2] & 0xFF))));
}
}
}
static final byte[] key0 = {(byte)0x1d,(byte)0xe5,(byte)0xfa,(byte)0xb8,(byte)0x81,(byte)0x91,(byte)0x00,(byte)0x84,
(byte)0x11,(byte)0xaf,(byte)0x53,(byte)0xdd,(byte)0xe4,(byte)0x89,(byte)0xea,(byte)0xfd};
static final byte[] key1 = {(byte)0xa9,(byte)0xdc,(byte)0x0e,(byte)0xf0,(byte)0x28,(byte)0x67,(byte)0x91,(byte)0xac,
(byte)0xc9,(byte)0x13,(byte)0x86,(byte)0x5d,(byte)0x2d,(byte)0xf8,(byte)0x20,(byte)0x99};
static final byte[] key2 = {(byte)0x1a,(byte)0xa8,(byte)0x80,(byte)0xb5,(byte)0x44,(byte)0x11,(byte)0xf1,(byte)0x19,
(byte)0x0c,(byte)0xca,(byte)0x3d,(byte)0xe8,(byte)0x77,(byte)0x41,(byte)0x37,(byte)0x89};
static final byte[] key3 = {(byte)0x45,(byte)0x68,(byte)0x70,(byte)0x98,(byte)0x7a,(byte)0xcd,(byte)0xeb,(byte)0xbb,
(byte)0xbd,(byte)0xed,(byte)0xa8,(byte)0x03,(byte)0x98,(byte)0x73,(byte)0x40,(byte)0x24};
static final byte[][] keys = {key0, key1, key2, key3};
static void doit(InputStream source, OutputStream sink) throws Exception {
Cipher c = Cipher.getInstance("AES/CTS/NoPadding");
byte[] iv = new byte[16];
source.read(iv);
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key0, "AES"), new IvParameterSpec(iv));
byte[] header = new byte[0x28];
source.read(header);
DataInput hdrstream = new DataInputStream(new ByteArrayInputStream(c.doFinal(header)));
if(hdrstream.readInt() != 0x59585756)
throw new Exception("Bad magic");
int size = hdrstream.readInt();
int keynum = hdrstream.readByte();
if(keynum > 3 || keynum < 0)
throw new Exception("Bad key number (0-3 expected)");
byte[] expectedmd5 = new byte[16];
hdrstream.readFully(expectedmd5);
byte[] body = new byte[size];
source.read(body);
byte[] actualmd5 = MessageDigest.getInstance("MD5").digest(body);
if(!MessageDigest.isEqual(actualmd5, expectedmd5))
throw new Exception("Body MD5 does not match");
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keys[keynum], "AES"), new IvParameterSpec(body, 0, 16));
byte[] payload = c.doFinal(body,16, size-16);
DataInput bodystream = new DataInputStream(new ByteArrayInputStream(payload));
if(bodystream.readInt() != 0x48464442)
throw new Exception("Bad body magic");
int payloadsize = bodystream.readInt();
bodystream.skipBytes(1); /* key ID - unused as no further crypto layer is present */
bodystream.readFully(expectedmd5);
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(payload, 0x28, payloadsize);
actualmd5 = md5.digest();
if(!MessageDigest.isEqual(actualmd5, expectedmd5))
throw new Exception("Payload MD5 does not match");
DataInputStream payloadhdrstream = new DataInputStream(new ByteArrayInputStream(payload, 0x28, 6));
if(payloadhdrstream.readUnsignedShort() != 0x6562)
throw new Exception("Byte-pair compression magic does not match");
int levels = payloadhdrstream.readUnsignedByte();
int compressedsize = payloadhdrstream.readUnsignedByte() * 0x10000;
compressedsize += payloadhdrstream.readUnsignedShort();
OutputStream str = sink;
for(int i = 0; i < levels; i++)
str = new BytePairDecompressionStream(str);
str.write(payload, 0x28+6, compressedsize);
str.flush();
}
public static void main(String[] args) throws Exception{
doit(System.in, System.out);
}
}