blob: 566577d4e58b534712a55f8f1b3405cde396c2b6 [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.Label;
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.LineRecord;
import flash.swf.debug.RegisterRecord;
import flash.swf.types.ActionList;
import flash.swf.types.ClipActionRecord;
import flash.swf.types.ClipActions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
/**
* AS2 encoder.
*/
public class ActionEncoder extends ActionHandler
{
private SwfEncoder writer;
private DebugEncoder debug;
// map label -> position
private HashMap<Label, LabelEntry> labels;
private ArrayList<UpdateEntry> updates;
private int actionCount;
public ActionEncoder(SwfEncoder writer, DebugEncoder debug)
{
this.writer = writer;
this.debug = debug;
labels = new HashMap<Label, LabelEntry>();
updates = new ArrayList<UpdateEntry>();
}
private static class UpdateEntry
{
int anchor; // location to subtract to compute delta
int updatePos; // byte offset to update with delta value
Action source;
public UpdateEntry(int anchor, int updatePos, Action source)
{
this.anchor = anchor;
this.updatePos = updatePos;
this.source = source;
}
}
private static class LabelEntry
{
int offset; // byte offset in swf file
int count; // action ordinal number (Nth action in current block)
public LabelEntry(int offset, int count)
{
this.count = count;
this.offset = offset;
}
}
private int getLabelOffset(Label label)
{
assert (labels.containsKey(label)) : ("missing label");
return labels.get(label).offset;
}
private int getLabelCount(Label label)
{
assert (labels.containsKey(label)) : ("missing label");
return labels.get(label).count;
}
public void encode(ActionList actionList)
{
// write the actions
for (int i=0; i < actionList.size(); i++)
{
Action a = actionList.getAction(i);
switch (a.code)
{
// don't update actionCount for synthetic opcodes
case ActionList.sactionLabel:
a.visit(this);
break;
case ActionList.sactionLineRecord:
if (debug != null)
debug.offset(writer.getPos(), (LineRecord)a);
break;
case ActionList.sactionRegisterRecord:
if (debug != null)
debug.registers(writer.getPos(), (RegisterRecord)a);
break;
// the remaining types need counting
case ActionConstants.sactionPush:
i = encodePush((Push)a, i, actionList);
actionCount++;
break;
default:
if (a.code < 0x80)
writer.writeUI8(a.code);
else
a.visit(this);
actionCount++;
break;
}
}
patchForwardBranches();
}
private void patchForwardBranches()
{
// now patch forward branches
for (Iterator<UpdateEntry> i = updates.iterator(); i.hasNext();)
{
UpdateEntry entry = i.next();
switch (entry.source.code)
{
case ActionConstants.sactionIf:
case ActionConstants.sactionJump:
int target = getLabelOffset(((Branch)entry.source).target);
writer.writeSI16at(entry.updatePos, target - entry.anchor);
break;
case ActionConstants.sactionWith:
int endWith = getLabelOffset(((With)entry.source).endWith);
writer.writeUI16at(entry.updatePos, endWith - entry.anchor);
break;
case ActionConstants.sactionWaitForFrame:
case ActionConstants.sactionWaitForFrame2:
int skipTarget = getLabelCount(((WaitForFrame)entry.source).skipTarget);
writer.writeUI8at(entry.updatePos, skipTarget - entry.anchor);
break;
case ActionConstants.sactionTry:
Try t = (Try) entry.source;
int endTry = getLabelOffset(t.endTry);
writer.writeUI16at(entry.updatePos, endTry - entry.anchor);
entry.anchor = endTry;
if (t.hasCatch())
{
int endCatch = getLabelOffset(t.endCatch);
writer.writeUI16at(entry.updatePos+2, endCatch - entry.anchor);
entry.anchor = endCatch;
}
if (t.hasFinally())
{
int endFinally = getLabelOffset(t.endFinally);
writer.writeUI16at(entry.updatePos+4, endFinally - entry.anchor);
}
break;
default:
assert false : ("invalid action in UpdateEntry");
}
}
}
// public void inlineBinaryOp(InlineBinaryOp op)
// {
// writer.writeU8(op.code);
// writer.writeU8(op.dst);
// encodeOperand(op.lhs, op.rhs);
// }
// private void encodeOperand(Object lhs, Object rhs)
// {
// int lhsType = operandType(lhs);
// int rhsType = operandType(rhs);
// writer.writeU8(lhsType >> 4 | rhsType);
// encodeOperand(lhsType, lhs);
// encodeOperand(rhsType, rhs);
// }
//
// private void encodeOperand(Object operand)
// {
// int type = operandType(operand);
// writer.writeU8(type);
// encodeOperand(type, operand);
// }
//
// public void inlineBranchWhenFalse(InlineBranchWhenFalse op)
// {
// writer.writeU8(op.code);
// int pos = writer.getPos();
// writer.writeU8(op.cond);
// encodeOperand(op.lhs, op.rhs);
// Integer offset = (Integer) labels.get(op.target);
// if (offset != null)
// {
// // label came earlier
// writer.writeU16(offset.intValue() - pos - 2);
// }
// else
// {
// // label comes later. don't know the offset yet.
// updates.add(new UpdateEntry(pos+2, pos, op));
// writer.writeU16(0);
// }
// }
// public void inlineGetMember(InlineGetMember op)
// {
// writer.writeU8(op.code);
// writer.writeU8(op.dst);
// writer.writeU8(op.src);
// encodeOperand(op.member);
// }
//
// public void inlineSetMember(InlineSetMember op)
// {
// writer.writeU8(op.code);
// writer.writeU8(op.dst);
// encodeOperand(op.member, op.src);
// }
// public void inlineSetRegister(InlineSetRegister op)
// {
// writer.writeU8(op.code);
// writer.writeU8(op.dst);
// encodeOperand(op.value);
// }
//
// public void inlineUnaryRegOp(InlineUnaryRegOp op)
// {
// writer.writeU8(op.code);
// writer.writeU8(op.register);
// }
// private void encodeOperand(int opType, Object operand)
// {
// switch (opType)
// {
// case ActionConstants.kInlineTrue:
// case ActionConstants.kInlineFalse:
// case ActionConstants.kInlineNull:
// case ActionConstants.kInlineUndefined:
// // do nothing, type implies value
// break;
// case ActionConstants.kInlineConstantByte:
// case ActionConstants.kInlineChar:
// case ActionConstants.kInlineRegister:
// int i = ((Number)operand).intValue();
// writer.writeU8(i);
// break;
// case ActionConstants.kInlineConstantWord:
// case ActionConstants.kInlineShort:
// i = ((Number)operand).intValue();
// writer.writeU16(i);
// break;
// case ActionConstants.kInlineLong:
// i = ((Number)operand).intValue();
// writer.write32(i);
// break;
// case ActionConstants.kInlineDouble:
// long num = Double.doubleToLongBits(((Number)operand).doubleValue());
// writer.write32((int)(num>>32));
// writer.write32((int)num);
// break;
// }
// }
// private int operandType(Object operand)
// {
// if (operand == Boolean.TRUE)
// return ActionConstants.kInlineTrue;
// else if (operand == Boolean.FALSE)
// return ActionConstants.kInlineFalse;
// else if (operand == ActionFactory.UNDEFINED)
// return ActionConstants.kInlineUndefined;
// else if (operand == ActionFactory.STACKTOP)
// return ActionConstants.kInlineStack;
// else if (operand == null)
// return ActionConstants.kInlineNull;
// else if (operand instanceof Short)
// return ((Short)operand).intValue() < 256 ? ActionConstants.kInlineConstantByte : ActionConstants.kInlineConstantWord;
// else if (operand instanceof Double)
// return ActionConstants.kInlineDouble;
// else if (operand instanceof Integer)
// return (((Integer)operand).intValue() & ~0xFF) == 0 ? ActionConstants.kInlineChar :
// (((Integer)operand).intValue() & ~0xFFFF) == 0 ? ActionConstants.kInlineShort :
// ActionConstants.kInlineLong;
// else if (operand instanceof Byte)
// return ActionConstants.kInlineRegister;
// else
// throw new IllegalArgumentException("unknown operand type " + operand.getClass().getName());
// }
public void call(Action action)
{
writer.writeUI8(action.code);
// this action's opcode 0x9E has hi bit set, but it never has data. considered a permanent minor bug.
writer.writeUI16(0);
}
public void constantPool(ConstantPool action)
{
int updatePos = encodeActionHeader(action);
writer.writeUI16(action.pool.length);
for (int i = 0; i < action.pool.length; i++)
{
writer.writeString(action.pool[i]);
}
updateActionHeader(updatePos);
}
private void updateActionHeader(int updatePos)
{
int length = (writer.getPos()-updatePos)-2;
if (length >= 0x10000)
{
assert false : ("action length ("+length+") exceeds 64K");
}
writer.writeUI16at(updatePos, length);
}
private int encodeActionHeader(Action action)
{
writer.writeUI8(action.code);
int updatePos = writer.getPos();
writer.writeUI16(0);
return updatePos;
}
public void defineFunction(DefineFunction action)
{
int updatePos = encodeActionHeader(action);
writer.writeString(action.name);
writer.writeUI16(action.params.length);
for (int i = 0; i < action.params.length; i++)
{
writer.writeString(action.params[i]);
}
int pos = writer.getPos();
writer.writeUI16(0); // codesize placeholder
updateActionHeader(updatePos);
new ActionEncoder(writer, debug).encode(action.actionList);
writer.writeUI16at(pos, (writer.getPos()-pos)-2);
}
public void defineFunction2(DefineFunction action)
{
int updatePos = encodeActionHeader(action);
writer.writeString(action.name);
writer.writeUI16(action.params.length);
writer.writeUI8(action.regCount);
writer.writeUI16(action.flags);
for (int i = 0; i < action.params.length; i++)
{
writer.writeUI8(action.paramReg[i]);
writer.writeString(action.params[i]);
}
int pos = writer.getPos();
writer.writeUI16(0); // placeholder
updateActionHeader(updatePos);
new ActionEncoder(writer, debug).encode(action.actionList);
writer.writeUI16at(pos, (writer.getPos()-pos)-2);
}
public void getURL(GetURL action)
{
int updatePos = encodeActionHeader(action);
writer.writeString(action.url);
writer.writeString(action.target);
updateActionHeader(updatePos);
}
public void getURL2(GetURL2 action)
{
int updatePos = encodeActionHeader(action);
writer.writeUI8(action.method);
updateActionHeader(updatePos);
}
public void gotoFrame(GotoFrame action)
{
int updatePos = encodeActionHeader(action);
writer.writeUI16(action.frame);
updateActionHeader(updatePos);
}
public void gotoFrame2(GotoFrame2 action)
{
int updatePos = encodeActionHeader(action);
writer.writeUI8(action.playFlag);
updateActionHeader(updatePos);
}
public void gotoLabel(GotoLabel action)
{
int updatePos = encodeActionHeader(action);
writer.writeString(action.label);
updateActionHeader(updatePos);
}
public void ifAction(Branch action)
{
encodeBranch(action);
}
public void jump(Branch action)
{
encodeBranch(action);
}
private void encodeBranch(Branch branch)
{
writer.writeUI8(branch.code);
writer.writeUI16(2);
int pos = writer.getPos();
if (labels.containsKey(branch.target))
{
// label came earlier
writer.writeSI16(getLabelOffset(branch.target) - pos - 2);
}
else
{
// label comes later. don't know the offset yet.
updates.add(new UpdateEntry(pos+2, pos, branch));
writer.writeSI16(0);
}
}
public void with(With action)
{
writer.writeUI8(action.code);
writer.writeUI16(2);
// label comes later, don't know offset yet
int pos = writer.getPos();
updates.add(new UpdateEntry(pos+2, pos, action));
writer.writeUI16(0);
}
public void waitForFrame(WaitForFrame action)
{
writer.writeUI8(action.code);
writer.writeUI16(3);
writer.writeUI16(action.frame);
int pos = writer.getPos();
updates.add(new UpdateEntry(actionCount+1, pos, action));
writer.writeUI8(0);
}
public void waitForFrame2(WaitForFrame action)
{
writer.writeUI8(action.code);
writer.writeUI16(1);
int pos = writer.getPos();
updates.add(new UpdateEntry(actionCount+1, pos, action));
writer.writeUI8(0);
}
public void label(Label label)
{
assert (!labels.containsKey(label)) : ("found duplicate label");
int labelPos = writer.getPos();
labels.put(label, new LabelEntry(labelPos, actionCount));
}
/**
* encode a run of push actions into one action record. The player
* supports this compact encoding since push is such a common
* opcode. the format is:
*
* sactionPush type1 value1 type2 value2 ...
*
* @param push
* @param j the index of the starting push action
* @param actions
* @return the index of the last push action encoded. the next action will
* not be a push action.
*/
public int encodePush(Push push, int j, ActionList actions)
{
int updatePos = encodeActionHeader(push);
do
{
Object value = push.value;
int type = Push.getTypeCode(value);
writer.writeUI8(type);
switch (type)
{
case 0: // string
writer.writeString(value.toString());
break;
case 1: // float
int bits = Float.floatToIntBits(((Float) value).floatValue());
writer.write32(bits);
break;
case 2: // null
break;
case 3: // undefined
break;
case 4: // register
writer.writeUI8(((Byte) value).intValue() & 0xFF);
break;
case 5: // boolean
writer.writeUI8(((Boolean) value).booleanValue() ? 1 : 0);
break;
case 6: // double
double d = ((Double) value).doubleValue();
long num = Double.doubleToLongBits(d);
writer.write32((int)(num>>32));
writer.write32((int)num);
break;
case 7: // integer
writer.write32(((Integer) value).intValue());
break;
case 8: // const8
writer.writeUI8(((Short) value).intValue());
break;
case 9: // const16
writer.writeUI16(((Short) value).intValue() & 0xFFFF);
break;
}
if (debug == null)
{
// ignore line records if we aren't debugging
while (j+1 < actions.size() && actions.getAction(j+1).code == ActionList.sactionLineRecord)
j++;
}
Action a;
if (++j < actions.size()
&& (a=actions.getAction(j)).code == ActionConstants.sactionPush)
{
push = (Push) a;
}
else
{
push = null;
}
}
while (push != null);
updateActionHeader(updatePos);
return j-1;
}
public void setTarget(SetTarget action)
{
int updatePos = encodeActionHeader(action);
writer.writeString(action.targetName);
updateActionHeader(updatePos);
}
public void storeRegister(StoreRegister action)
{
int updatePos = encodeActionHeader(action);
writer.writeUI8(action.register);
updateActionHeader(updatePos);
}
public void strictMode(StrictMode action)
{
int updatePos = encodeActionHeader(action);
writer.writeUI8(action.mode ? 1 : 0);
updateActionHeader(updatePos);
}
public void tryAction(Try a)
{
int updatePos = encodeActionHeader(a);
writer.writeUI8(a.flags);
int trySizePos = writer.getPos();
writer.writeUI16(0); // try size
writer.writeUI16(0); // catch size
writer.writeUI16(0); // finally size
if (a.hasRegister())
writer.writeUI8(a.catchReg);
else
writer.writeString(a.catchName);
// we have emitted the try action, what follows is label mgmt
updateActionHeader(updatePos);
int tryStart = writer.getPos();
updates.add(new UpdateEntry(tryStart, trySizePos, a));
}
public void unknown(Unknown action)
{
int updatePos = encodeActionHeader(action);
writer.write(action.data);
updateActionHeader(updatePos);
}
public void encodeClipActions(ClipActions clipActions)
{
writer.writeUI16(0);
encodeClipEventFlags(clipActions.allEventFlags, writer);
Iterator it = clipActions.clipActionRecords.iterator();
while (it.hasNext())
{
ClipActionRecord r = (ClipActionRecord) it.next();
encodeClipActionRecord(r);
}
if (writer.swfVersion >= 6)
writer.write32(0);
else
writer.writeUI16(0);
}
private void encodeClipActionRecord(ClipActionRecord r)
{
encodeClipEventFlags(r.eventFlags, writer);
int pos = writer.getPos();
writer.write32(0); // offset placeholder
if ((r.eventFlags & ClipActionRecord.keyPress) != 0)
{
writer.writeUI8(r.keyCode);
}
encode(r.actionList);
writer.write32at(pos, (writer.getPos()-pos)-4);
}
private void encodeClipEventFlags(int flags, SwfEncoder w)
{
if (w.swfVersion >= 6)
w.write32(flags);
else
w.writeUI16(flags);
}
}