blob: 1e2ba2a5b366efd71d35f08c617c9acff2561a05 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 flash.swf;
import flash.swf.actions.Branch;
import flash.swf.actions.ConstantPool;
import flash.swf.actions.DefineFunction;
import flash.swf.actions.GetURL;
import flash.swf.actions.GetURL2;
import flash.swf.actions.GotoFrame;
import flash.swf.actions.GotoFrame2;
import flash.swf.actions.GotoLabel;
import flash.swf.actions.Push;
import flash.swf.actions.SetTarget;
import flash.swf.actions.StoreRegister;
import flash.swf.actions.StrictMode;
import flash.swf.actions.Try;
import flash.swf.actions.Unknown;
import flash.swf.actions.WaitForFrame;
import flash.swf.actions.With;
import flash.swf.debug.DebugTable;
import flash.swf.debug.LineRecord;
import flash.swf.debug.RegisterRecord;
import flash.swf.types.ActionList;
import flash.swf.types.ClipActionRecord;
import flash.swf.types.ClipActions;
import java.io.IOException;
import java.util.ArrayList;
/**
* AS2 decoder.
*/
public class ActionDecoder
implements ActionConstants
{
private SwfDecoder reader;
private DebugTable debug;
private boolean keepOffsets;
private int actionCount;
public ActionDecoder(SwfDecoder reader)
{
this(reader, null);
}
public ActionDecoder(SwfDecoder reader, DebugTable debug)
{
this.reader = reader;
this.debug = debug;
}
public void setKeepOffsets(boolean b)
{
keepOffsets = b;
}
/**
* consume actions until length bytes are used up.
* @param length
* @throws IOException
*/
public ActionList decode(int length) throws IOException { return decode(length, true); }
/**
* consume actions until length bytes are used up
* @param length
* @param throwExceptions - if false exceptions will NOT be thrown. This is
* used for decoding a series of opcodes which may not be complete on their own.
* @throws IOException
*/
public ActionList decode(int length, boolean throwExceptions) throws IOException
{
int startOffset = reader.getOffset();
int end = startOffset+length;
boolean ending = false;
ActionFactory factory = new ActionFactory(length, startOffset, actionCount);
try
{
for (int offset = startOffset; offset < end; offset = reader.getOffset())
{
int opcode = reader.readUI8();
if (opcode > 0)
{
if (ending)
throw new SwfFormatException("unexpected bytes after sactionEnd: " + opcode);
factory.setActionOffset(actionCount, offset);
decodeAction(opcode, offset, factory);
actionCount++;
}
else if (opcode == 0)
{
ending = true;
}
else
{
break;
}
}
// keep track of the end too
factory.setActionOffset(actionCount, reader.getOffset());
}
catch(ArrayIndexOutOfBoundsException aio)
{
if (throwExceptions)
throw aio;
}
catch(SwfFormatException swf)
{
if (throwExceptions)
throw swf;
}
return factory.createActionList(keepOffsets);
}
public ClipActions decodeClipActions(int length) throws IOException
{
ClipActions a = new ClipActions();
reader.readUI16(); // must be 0
a.allEventFlags = decodeClipEventFlags(reader);
ArrayList<ClipActionRecord> list = new ArrayList<ClipActionRecord>();
ClipActionRecord record = decodeClipActionRecord();
while (record != null)
{
list.add(record);
record = decodeClipActionRecord();
}
a.clipActionRecords = list;
return a;
}
private ClipActionRecord decodeClipActionRecord() throws IOException
{
int flags = decodeClipEventFlags(reader);
if (flags != 0)
{
ClipActionRecord c = new ClipActionRecord();
c.eventFlags = flags;
// this tells us how big the action block is
int size = (int)reader.readUI32();
if ((flags & ClipActionRecord.keyPress) != 0)
{
size--;
c.keyCode = reader.readUI8();
}
c.actionList = decode(size);
return c;
}
else
{
return null;
}
}
private int decodeClipEventFlags(SwfDecoder r) throws IOException
{
int flags;
if (r.swfVersion >= 6)
flags = (int)r.readUI32();
else
flags = r.readUI16();
return flags;
}
private void decodeAction(int opcode, int offset, ActionFactory factory) throws IOException
{
LineRecord line = debug != null ? debug.getLine(offset) : null;
if (line != null)
{
factory.setLine(offset, line);
}
// interleave register records in the action list
RegisterRecord record = (debug != null) ? debug.getRegisters(offset) : null;
if (record != null)
{
factory.setRegister(offset, record);
}
Action a;
if (opcode < 0x80)
{
a = ActionFactory.createAction(opcode);
factory.setAction(offset, a);
return;
}
int len = reader.readUI16();
int pos = offset+3;
switch (opcode)
{
case sactionDefineFunction:
a = decodeDefineFunction(pos, len);
factory.setAction(offset, a);
return;
case sactionDefineFunction2:
a = decodeDefineFunction2(pos, len);
factory.setAction(offset, a);
return;
case sactionWith:
a = decodeWith(factory);
break;
case sactionTry:
a = decodeTry(factory);
break;
case sactionPush:
Push p = decodePush(offset, pos+len, factory);
checkConsumed(pos, len, p);
return;
case sactionStrictMode:
a = decodeStrictMode();
break;
case sactionCall:
// this actions opcode has the high bit set, but there is no length. considered a permanent bug.
a = ActionFactory.createCall();
break;
case sactionGotoFrame:
a = decodeGotoFrame();
break;
case sactionGetURL:
a = decodeGetURL();
break;
case sactionStoreRegister:
a = decodeStoreRegister();
break;
case sactionConstantPool:
a = decodeConstantPool();
break;
case sactionWaitForFrame:
a = decodeWaitForFrame(opcode, factory);
break;
case sactionSetTarget:
a = decodeSetTarget();
break;
case sactionGotoLabel:
a = decodeGotoLabel();
break;
case sactionWaitForFrame2:
a = decodeWaitForFrame(opcode, factory);
break;
case sactionGetURL2:
a = decodeGetURL2();
break;
case sactionJump:
case sactionIf:
a = decodeBranch(opcode, factory);
break;
case sactionGotoFrame2:
a = decodeGotoFrame2();
break;
default:
a = decodeUnknown(opcode, len);
break;
}
checkConsumed(pos, len, a);
factory.setAction(offset, a);
}
private Try decodeTry(ActionFactory factory) throws IOException
{
Try a = new Try();
a.flags = reader.readUI8();
int trySize = reader.readUI16();
int catchSize = reader.readUI16();
int finallySize = reader.readUI16();
if (a.hasRegister())
a.catchReg = reader.readUI8();
else
a.catchName = reader.readString();
// we have now consumed the try action. what follows is label mgmt
int tryEnd = reader.getOffset() + trySize;
a.endTry = factory.getLabel(tryEnd);
// place the catchLabel to mark the end point of the catch handler
if (a.hasCatch())
a.endCatch = factory.getLabel(tryEnd + catchSize);
// place the finallyLabel to mark the end point of the finally handler
if (a.hasFinally())
a.endFinally = factory.getLabel(tryEnd + finallySize + (a.hasCatch() ? catchSize : 0));
return a;
}
private GotoFrame2 decodeGotoFrame2() throws IOException
{
GotoFrame2 a = new GotoFrame2();
a.playFlag = reader.readUI8();
return a;
}
private Branch decodeBranch(int code, ActionFactory factory) throws IOException
{
Branch a = new Branch(code);
int offset = reader.readSI16();
int target = offset + reader.getOffset();
a.target = factory.getLabel(target);
return a;
}
private WaitForFrame decodeWaitForFrame(int opcode, ActionFactory factory) throws IOException
{
WaitForFrame a = new WaitForFrame(opcode);
if (opcode == sactionWaitForFrame)
a.frame = reader.readUI16();
int skipCount = reader.readUI8();
int skipTarget = actionCount+1 + skipCount;
factory.addSkipEntry(a, skipTarget);
return a;
}
private GetURL2 decodeGetURL2() throws IOException
{
GetURL2 a = new GetURL2();
a.method = reader.readUI8();
return a;
}
private GotoLabel decodeGotoLabel() throws IOException
{
GotoLabel a = new GotoLabel();
a.label = reader.readString();
return a;
}
private SetTarget decodeSetTarget() throws IOException
{
SetTarget a = new SetTarget();
a.targetName = reader.readString();
return a;
}
private ConstantPool decodeConstantPool() throws IOException
{
ConstantPool cpool = new ConstantPool();
int count = reader.readUI16();
cpool.pool = new String[count];
for (int i = 0; i < count; i++)
{
cpool.pool[i] = reader.readString();
}
return cpool;
}
private StoreRegister decodeStoreRegister() throws IOException
{
int register = reader.readUI8();
return ActionFactory.createStoreRegister(register);
}
private GetURL decodeGetURL() throws IOException
{
GetURL a = new GetURL();
a.url = reader.readString();
a.target = reader.readString();
return a;
}
private GotoFrame decodeGotoFrame() throws IOException
{
GotoFrame a = new GotoFrame();
a.frame = reader.readUI16();
return a;
}
private Unknown decodeUnknown(int opcode, int length) throws IOException
{
Unknown a = new Unknown(opcode);
a.data = new byte[length];
reader.readFully(a.data);
return a;
}
private StrictMode decodeStrictMode() throws IOException
{
boolean mode = reader.readUI8() != 0;
return ActionFactory.createStrictMode(mode);
}
private Push decodePush(int offset, int end, ActionFactory factory) throws IOException
{
Push p;
do
{
int pushType = reader.readUI8();
switch (pushType)
{
case Push.kPushStringType: // string
p = ActionFactory.createPush(reader.readString());
break;
case Push.kPushFloatType: // float
float fvalue = Float.intBitsToFloat((int) reader.readUI32());
p = ActionFactory.createPush(fvalue); // value
break;
case Push.kPushNullType: // null
p = ActionFactory.createPushNull();
break;
case Push.kPushUndefinedType: // undefined
p = ActionFactory.createPushUndefined();
break;
case Push.kPushRegisterType: // register
p = ActionFactory.createPushRegister(reader.readUI8());
break;
case Push.kPushBooleanType: // boolean
p = ActionFactory.createPush(reader.readUI8() != 0);
break;
case Push.kPushDoubleType: // double
// read two 32 bit little-endian values in big-endian order. weird.
long hx = reader.readUI32();
long lx = reader.readUI32();
p = ActionFactory.createPush(Double.longBitsToDouble((hx << 32) | (lx & 0xFFFFFFFFL)));
break;
case Push.kPushIntegerType: // integer
p = ActionFactory.createPush((int)reader.readUI32());
break;
case Push.kPushConstant8Type: // 8-bit cpool reference
p = ActionFactory.createPushCpool(reader.readUI8());
break;
case Push.kPushConstant16Type: // 16-bit cpool reference
p = ActionFactory.createPushCpool(reader.readUI16());
break;
default:
throw new SwfFormatException("Unknown push data type "+pushType);
}
factory.setAction(offset, p);
offset = reader.getOffset();
}
while (offset < end);
return p;
}
private DefineFunction decodeDefineFunction(int pos, int len) throws IOException
{
DefineFunction a = new DefineFunction(ActionConstants.sactionDefineFunction);
a.name = reader.readString();
int number = reader.readUI16();
a.params = new String[number];
for (int i = 0; i < number; i++)
{
a.params[i] = reader.readString();
}
a.codeSize = reader.readUI16();
checkConsumed(pos, len, a);
a.actionList = decode(a.codeSize);
return a;
}
private DefineFunction decodeDefineFunction2(int pos, int len) throws IOException
{
DefineFunction a = new DefineFunction(ActionConstants.sactionDefineFunction2);
a.name = reader.readString();
int number = reader.readUI16();
a.params = new String[number];
a.paramReg = new int[number];
a.regCount = reader.readUI8();
a.flags = reader.readUI16();
for (int i = 0; i < number; i++)
{
a.paramReg[i] = reader.readUI8();
a.params[i] = reader.readString();
}
a.codeSize = reader.readUI16();
checkConsumed(pos, len, a);
a.actionList = decode(a.codeSize);
return a;
}
private void checkConsumed(int pos, int len, Action a) throws IOException
{
int consumed = reader.getOffset() - pos;
if (consumed != len)
{
throw new SwfFormatException(a.getClass().getName() + ": " + consumed + " was read. " + len + " was required.");
}
}
private With decodeWith(ActionFactory factory) throws IOException
{
With a = new With();
int size = reader.readUI16();
int target = size + reader.getOffset();
a.endWith = factory.getLabel(target);
return a;
}
}