/*
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.abc;
import com.jpexs.decompiler.flash.EndOfStreamException;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.abc.types.Decimal;
import com.jpexs.decompiler.flash.abc.types.Float4;
import com.jpexs.decompiler.flash.abc.types.InstanceInfo;
import com.jpexs.decompiler.flash.abc.types.MethodInfo;
import com.jpexs.decompiler.flash.abc.types.Multiname;
import com.jpexs.decompiler.flash.abc.types.Namespace;
import com.jpexs.decompiler.flash.abc.types.ValueKind;
import com.jpexs.decompiler.flash.abc.types.traits.Trait;
import com.jpexs.decompiler.flash.abc.types.traits.TraitClass;
import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction;
import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter;
import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst;
import com.jpexs.decompiler.flash.abc.types.traits.Traits;
import com.jpexs.decompiler.flash.dumpview.DumpInfo;
import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecial;
import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType;
import com.jpexs.helpers.MemoryInputStream;
import com.jpexs.helpers.utf8.Utf8Helper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
*
* @author JPEXS
*/
public class ABCInputStream implements AutoCloseable {
private static final int CLASS_PROTECTED_NS = 8;
private static final int ATTR_METADATA = 4;
private final MemoryInputStream is;
private ByteArrayOutputStream bufferOs = null;
public static final boolean DEBUG_READ = false;
public DumpInfo dumpInfo;
private byte[] stringDataBuffer = new byte[256];
public void startBuffer() {
if (bufferOs == null) {
bufferOs = new ByteArrayOutputStream();
} else {
bufferOs.reset();
}
}
public byte[] stopBuffer() {
if (bufferOs == null) {
return SWFInputStream.BYTE_ARRAY_EMPTY;
}
byte[] ret = bufferOs.toByteArray();
bufferOs.reset();
return ret;
}
public ABCInputStream(MemoryInputStream is) {
this.is = is;
}
/**
* Sets position in bytes in the stream
*
* @param pos Number of bytes
* @throws java.io.IOException
*/
public void seek(long pos) throws IOException {
is.seek(pos);
}
public DumpInfo newDumpLevel(String name, String type) {
return newDumpLevel(name, type, DumpInfoSpecialType.NONE);
}
public DumpInfo newDumpLevel(String name, String type, DumpInfoSpecialType specialType) {
if (dumpInfo != null) {
long startByte = is.getPos();
DumpInfo di = specialType == DumpInfoSpecialType.NONE
? new DumpInfo(name, type, null, startByte, 0, 0, 0)
: new DumpInfoSpecial(name, type, null, startByte, 0, 0, 0, specialType);
di.parent = dumpInfo;
dumpInfo.getChildInfos().add(di);
dumpInfo = di;
}
return dumpInfo;
}
public void endDumpLevel() {
endDumpLevel(null);
}
public void endDumpLevel(Object value) {
if (dumpInfo != null) {
dumpInfo.lengthBytes = is.getPos() - dumpInfo.startByte;
dumpInfo.previewValue = value;
dumpInfo = dumpInfo.parent;
}
}
public void endDumpLevelUntil(DumpInfo di) {
if (di != null) {
while (dumpInfo != null && dumpInfo != di) {
endDumpLevel();
}
}
}
private int readInternal() throws IOException {
int i = is.read();
if (i == -1) {
throw new EndOfStreamException();
}
if (DEBUG_READ) {
System.out.println("Read:0x" + Integer.toHexString(i));
}
if (bufferOs != null) {
if (i != -1) {
bufferOs.write(i);
}
}
return i;
}
public int read(String name) throws IOException {
newDumpLevel(name, "byte");
int ret = readInternal();
endDumpLevel(ret);
return ret;
}
private int read(byte[] b) throws IOException {
int currBytesRead = is.read(b);
if (DEBUG_READ) {
StringBuilder sb = new StringBuilder("Read[");
sb.append(currBytesRead);
sb.append('/');
sb.append(b.length);
sb.append("]: ");
for (int jj = 0; jj < currBytesRead; jj++) {
sb.append("0x");
sb.append(Integer.toHexString(b[jj]));
sb.append(' ');
}
System.out.println(sb.toString());
}
if (bufferOs != null) {
if (currBytesRead > 0) {
bufferOs.write(b, 0, currBytesRead);
}
}
return currBytesRead;
}
public int readU8(String name) throws IOException {
newDumpLevel(name, "U8");
int ret = readInternal();
endDumpLevel(ret);
return ret;
}
private long readU32Internal() throws IOException {
int i;
long ret = 0;
int bytePos = 0;
int byteCount = 0;
boolean nextByte;
do {
i = readInternal();
nextByte = (i >> 7) == 1;
i &= 0x7f;
ret += (((long) i) << bytePos);
byteCount++;
bytePos += 7;
} while (nextByte && byteCount < 5);
return ret;
}
public long readU32(String name) throws IOException {
newDumpLevel(name, "U32");
long ret = readU32Internal();
endDumpLevel(ret);
return ret;
}
private int readU30Internal() throws IOException {
long u32 = readU32Internal();
//no bits above bit 30
return (int) (u32 & 0x3FFFFFFF);
}
public int readU30(String name) throws IOException {
newDumpLevel(name, "U30");
int ret = readU30Internal();
endDumpLevel(ret);
return ret;
}
public int readS24(String name) throws IOException {
newDumpLevel(name, "S24");
int ret = (readInternal()) + (readInternal() << 8) + (readInternal() << 16);
if ((ret >> 23) == 1) {
ret |= 0xff000000;
}
endDumpLevel(ret);
return ret;
}
public int readU16(String name) throws IOException {
newDumpLevel(name, "U16");
int ret = (readInternal()) + (readInternal() << 8);
endDumpLevel(ret);
return ret;
}
public long readS32(String name) throws IOException {
int i;
long ret = 0;
int bytePos = 0;
int byteCount = 0;
boolean nextByte;
newDumpLevel(name, "S32");
do {
i = readInternal();
nextByte = (i >> 7) == 1;
i &= 0x7f;
ret += (i << bytePos);
byteCount++;
bytePos += 7;
if (bytePos == 35) {
if ((ret >> 31) == 1) {
ret = -(ret & 0x7fffffff);
}
break;
}
} while (nextByte && byteCount < 5);
endDumpLevel(ret);
return ret;
}
public int available() throws IOException {
return is.available();
}
private long readLong() throws IOException {
safeRead(8, stringDataBuffer);
byte[] readBuffer = stringDataBuffer;
return (((long) readBuffer[7] << 56)
+ ((long) (readBuffer[6] & 255) << 48)
+ ((long) (readBuffer[5] & 255) << 40)
+ ((long) (readBuffer[4] & 255) << 32)
+ ((long) (readBuffer[3] & 255) << 24)
+ ((readBuffer[2] & 255) << 16)
+ ((readBuffer[1] & 255) << 8)
+ ((readBuffer[0] & 255)));
}
public double readDouble(String name) throws IOException {
newDumpLevel(name, "Double");
long el = readLong();
double ret = Double.longBitsToDouble(el);
endDumpLevel(ret);
return ret;
}
private void safeRead(int count, byte[] data) throws IOException {
for (int i = 0; i < count; i++) {
data[i] = (byte) readInternal();
}
}
public Namespace readNamespace(String name) throws IOException {
newDumpLevel(name, "Namespace");
int kind = read("kind");
int name_index = 0;
for (int k = 0; k < Namespace.nameSpaceKinds.length; k++) {
if (Namespace.nameSpaceKinds[k] == kind) {
name_index = readU30("name_index");
break;
}
}
endDumpLevel();
return new Namespace(kind, name_index);
}
public Multiname readMultiname(String name) throws IOException {
int kind = readU8("kind");
Multiname result = null;
newDumpLevel(name, "Multiname");
if ((kind == Multiname.QNAME) || (kind == Multiname.QNAMEA)) {
int namespace_index = readU30("namespace_index");
int name_index = readU30("name_index");
result = Multiname.createQName(kind == Multiname.QNAMEA, name_index, namespace_index);
} else if ((kind == Multiname.RTQNAME) || (kind == Multiname.RTQNAMEA)) {
int name_index = readU30("name_index");
result = Multiname.createRTQName(kind == Multiname.RTQNAMEA, name_index);
} else if ((kind == Multiname.RTQNAMEL) || (kind == Multiname.RTQNAMELA)) {
result = Multiname.createRTQNameL(kind == Multiname.RTQNAMELA);
} else if ((kind == Multiname.MULTINAME) || (kind == Multiname.MULTINAMEA)) {
int name_index = readU30("name_index");
int namespace_set_index = readU30("namespace_set_index");
result = Multiname.createMultiname(kind == Multiname.MULTINAMEA, name_index, namespace_set_index);
} else if ((kind == Multiname.MULTINAMEL) || (kind == Multiname.MULTINAMELA)) {
int namespace_set_index = readU30("namespace_set_index");
result = Multiname.createMultinameL(kind == Multiname.MULTINAMELA, namespace_set_index);
} else if (kind == Multiname.TYPENAME) {
int qname_index = readU30("qname_index"); // Multiname index!!!
int paramsLength = readU30("paramsLength");
int[] params = new int[paramsLength];
for (int i = 0; i < paramsLength; i++) {
params[i] = readU30("param"); // multiname indices!
}
result = Multiname.createTypeName(qname_index, params);
} else {
throw new IOException("Unknown kind of Multiname:0x" + Integer.toHexString(kind));
}
endDumpLevel();
return result;
}
public MethodInfo readMethodInfo(String name) throws IOException {
newDumpLevel(name, "method_info");
int param_count = readU30("param_count");
int ret_type = readU30("ret_type");
int[] param_types = new int[param_count];
for (int i = 0; i < param_count; i++) {
param_types[i] = readU30("param_type");
}
int name_index = readU30("name_index");
int flags = read("flags");
// 1=need_arguments, 2=need_activation, 4=need_rest 8=has_optional (16=ignore_rest, 32=explicit,) 64=setsdxns, 128=has_paramnames
ValueKind[] optional = new ValueKind[0];
if ((flags & 8) == 8) { // if has_optional
int optional_count = readU30("optional_count");
optional = new ValueKind[optional_count];
for (int i = 0; i < optional_count; i++) {
optional[i] = new ValueKind(readU30("value_index"), read("value_kind"));
}
}
int[] param_names = new int[param_count];
if ((flags & 128) == 128) { // if has_paramnames
for (int i = 0; i < param_count; i++) {
param_names[i] = readU30("param_name");
}
}
endDumpLevel();
return new MethodInfo(param_types, ret_type, name_index, flags, optional, param_names);
}
public Trait readTrait(String name) throws IOException {
newDumpLevel(name, "Trait");
long pos = getPosition();
startBuffer();
int name_index = readU30("name_index");
int kind = read("kind");
int kindType = 0xf & kind;
int kindFlags = kind >> 4;
Trait trait;
switch (kindType) {
case 0: // slot
case 6: // const
TraitSlotConst t1 = new TraitSlotConst();
t1.slot_id = readU30("slot_id");
t1.type_index = readU30("type_index");
t1.value_index = readU30("value_index");
if (t1.value_index != 0) {
t1.value_kind = read("value_kind");
}
trait = t1;
break;
case 1: // method
case 2: // getter
case 3: // setter
TraitMethodGetterSetter t2 = new TraitMethodGetterSetter();
t2.disp_id = readU30("disp_id");
t2.method_info = readU30("method_info");
trait = t2;
break;
case 4: // class
TraitClass t3 = new TraitClass();
t3.slot_id = readU30("slot_id");
t3.class_info = readU30("class_info");
trait = t3;
break;
case 5: // function
TraitFunction t4 = new TraitFunction();
t4.slot_id = readU30("slot_id");
t4.method_info = readU30("method_info");
trait = t4;
break;
default:
throw new IOException("Unknown trait kind:" + kind);
}
trait.fileOffset = pos;
trait.kindType = kindType;
trait.kindFlags = kindFlags;
trait.name_index = name_index;
if ((kindFlags & ATTR_METADATA) != 0) {
int metadata_count = readU30("metadata_count");
trait.metadata = new int[metadata_count];
for (int i = 0; i < metadata_count; i++) {
trait.metadata[i] = readU30("metadata");
}
}
trait.bytes = stopBuffer();
endDumpLevel();
return trait;
}
public Traits readTraits(String name) throws IOException {
newDumpLevel(name, "Traits");
int count = readU30("count");
Traits traits = new Traits(count);
for (int i = 0; i < count; i++) {
traits.traits.add(readTrait("trait"));
}
endDumpLevel();
return traits;
}
private byte[] readBytesInternal(int count) throws IOException {
byte[] ret = new byte[count];
for (int i = 0; i < count; i++) {
ret[i] = (byte) readInternal();
}
return ret;
}
public byte[] readBytes(int count, String name, DumpInfoSpecialType specialType) throws IOException {
newDumpLevel(name, "Bytes", specialType);
byte[] ret = readBytesInternal(count);
endDumpLevel();
return ret;
}
public Decimal readDecimal(String name) throws IOException {
newDumpLevel(name, "Decimal");
byte[] data = readBytesInternal(16);
endDumpLevel();
return new Decimal(data);
}
public Float readFloat(String name) throws IOException {
newDumpLevel(name, "Float");
int intBits = (readInternal()) + (readInternal() << 8);
float ret = Float.intBitsToFloat(intBits);
endDumpLevel(ret);
return ret;
}
public Float4 readFloat4(String name) throws IOException {
newDumpLevel(name, "Float4");
float f1 = readFloat("value1");
float f2 = readFloat("value2");
float f3 = readFloat("value3");
float f4 = readFloat("value4");
Float4 ret = new Float4(f1, f2, f3, f4);
endDumpLevel(ret);
return ret;
}
public InstanceInfo readInstanceInfo(String name) throws IOException {
newDumpLevel(name, "instance_info");
InstanceInfo ret = new InstanceInfo(null); // do not create Traits in constructor
ret.name_index = readU30("name_index");
ret.super_index = readU30("super_index");
ret.flags = readInternal();
if ((ret.flags & CLASS_PROTECTED_NS) != 0) {
ret.protectedNS = readU30("protectedNS");
}
int interfaces_count = readU30("interfaces_count");
ret.interfaces = new int[interfaces_count];
for (int i = 0; i < interfaces_count; i++) {
ret.interfaces[i] = readU30("interface");
}
ret.iinit_index = readU30("iinit_index");
ret.instance_traits = readTraits("instance_traits");
endDumpLevel();
return ret;
}
public String readString(String name) throws IOException {
newDumpLevel(name, "String");
int length = readU30Internal();
// avoid creating new byte array every time
if (stringDataBuffer.length < length) {
int newLength = stringDataBuffer.length * 2;
while (newLength < length) {
newLength *= 2;
}
stringDataBuffer = new byte[newLength];
}
safeRead(length, stringDataBuffer);
String r = new String(stringDataBuffer, 0, length, Utf8Helper.charset);
endDumpLevel(r);
return r;
}
/*public void markStart(){
bytesRead=0;
}*/
public long getPosition() {
return is.getPos();
}
@Override
public void close() {
}
}