flash.swf.MovieMetaData Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swfutils Show documentation
Show all versions of swfutils Show documentation
The Apache Royale Compiler SWF Utility classes
/*
*
* 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.Dictionary;
import flash.swf.actions.*;
import flash.swf.debug.DebugModule;
import flash.swf.debug.LineRecord;
import flash.swf.tags.*;
import flash.swf.types.*;
import flash.util.IntMap;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* Represents SWF metadata, which should not be confuses with AS3
* metadata.
*/
public final class MovieMetaData extends TagHandler
{
public MovieMetaData(byte[] swf, byte[] swd)
{
this(new ByteArrayInputStream(swf), new ByteArrayInputStream(swd));
}
public MovieMetaData(InputStream swf, InputStream swd)
{
try
{
init();
TagDecoder p = new TagDecoder(swf, swd);
parse(p);
}
catch (IOException ex)
{
}
}
public MovieMetaData(String u)
{
try
{
init();
URL url = new URL(u);
InputStream in = url.openStream();
TagDecoder p = new TagDecoder(in, url);
parse(p);
}
catch (MalformedURLException ex)
{
}
catch (IOException ex)
{
}
}
private void init()
{
actions = new IntMap();
modules = new IntMap();
functionNames = new IntMap();
functionSizes = new IntMap();
functionLines = new IntMap();
preciseLines = new IntMap();
mxml = new HashMap();
pool = null;
skipOffsets = new ArrayList();
}
private void parse(TagDecoder p) throws IOException
{
p.setKeepOffsets(true);
p.parse(this);
Collections.sort(skipOffsets);
className = null;
}
private Dictionary dict;
private Header header;
// given an offset, what's the bytecode?
public IntMap actions;
// given an offset, what debug module it's in?
public IntMap modules;
// given an offset, what function it's in?
public IntMap functionNames;
public IntMap functionSizes;
public IntMap functionLines;
public IntMap preciseLines;
// MXML DebugModule
public Map mxml;
// offsets that we don't want to profile
public List skipOffsets;
// temporarily store AS2 class name...
private String className;
private String[] pool;
public DebugModule getDebugModule(int offset)
{
DebugModule d = (DebugModule) modules.get(offset);
if (d == null)
{
return null;
}
else
{
return d;
}
}
public String getFunctionName(int offset)
{
return (String) functionNames.get(offset);
}
public Iterator getFunctionLines()
{
return preciseLines.iterator();
}
public Integer getOpCode(int offset)
{
return (Integer) actions.get(offset);
}
protected Integer getFunctionLineNumber(int offset)
{
return (Integer) functionLines.get(offset);
}
protected boolean isFunction(int offset)
{
String s = getFunctionName(offset);
return (s != null);
}
public void setDecoderDictionary(Dictionary dict)
{
this.dict = dict;
}
public void header(Header h)
{
header = h;
}
public void defineButton(DefineButton tag)
{
String[] temp = pool;
collectActions(tag.condActions[0].actionList);
pool = temp;
}
public void doAction(DoAction tag)
{
String[] temp = pool;
collectActions(tag.actionList);
pool = temp;
}
public void placeObject2(PlaceObject tag)
{
collectClipActions(tag.clipActions);
}
public void placeObject3(PlaceObject tag)
{
collectClipActions(tag.clipActions);
}
public void defineButton2(DefineButton tag)
{
collectCondActions(tag.condActions);
}
public void defineSprite(DefineSprite tag)
{
collectSpriteActions(tag.tagList);
}
public void doInitAction(DoInitAction tag)
{
if (header.version > 6 && tag.sprite != null)
{
String __Packages = idRef(tag.sprite);
className = (__Packages != null && __Packages.startsWith("__Packages")) ? __Packages.substring(11) : null; // length("__Packages.") = 11
if (isRegisterClass(tag.actionList))
{
DebugModule dm = new DebugModule();
// C: We actually want the class name here, not the linkage ID.
dm.name = "<" + __Packages + ".2>";
// C: We want the class name as the second input argument. Fortunately, we don't
// really do anything with the source, so it's okay.
dm.setText("Object.registerClass(" + __Packages + ", " + __Packages + ");");
dm.bitmap = 1;
LineRecord lr = new LineRecord(1, dm);
int startOffset = tag.actionList.getOffset(0);
dm.addOffset(lr, startOffset);
tag.actionList.insert(startOffset, lr);
modules.put((int) (Math.random() * Integer.MAX_VALUE), dm);
}
}
String[] temp = pool;
collectActions(tag.actionList);
pool = temp;
className = null;
}
private static final int[] regClassCall9 = new int[]
{
ActionConstants.sactionPush, // class name
ActionConstants.sactionGetVariable,
ActionConstants.sactionPush, // linkage id
ActionConstants.sactionPush, // 2
ActionConstants.sactionPush, // Object
ActionConstants.sactionGetVariable,
ActionConstants.sactionPush, // registerClass
ActionConstants.sactionCallMethod,
ActionConstants.sactionPop
};
private static final int[] regClassCall10 = new int[]
{
ActionConstants.sactionConstantPool, // constant pool
ActionConstants.sactionPush, // class name
ActionConstants.sactionGetVariable,
ActionConstants.sactionPush, // linkage id
ActionConstants.sactionPush, // 2
ActionConstants.sactionPush, // Object
ActionConstants.sactionGetVariable,
ActionConstants.sactionPush, // registerClass
ActionConstants.sactionCallMethod,
ActionConstants.sactionPop
};
// TODO: Use an evaluation stack to figure out the Object.registerClass() call.
public static final boolean isRegisterClass(ActionList actionList)
{
if (!hasLineRecord(actionList))
{
int[] opcodes;
if (actionList.size() == 9)
{
opcodes = regClassCall9;
}
else if (actionList.size() == 10)
{
opcodes = regClassCall10;
}
else
{
return false;
}
for (int i = 0;i < opcodes.length;i++)
{
if (actionList.getAction(i).code != opcodes[i])
{
return false;
}
else
{
// TODO: need to check the PUSH values...
}
}
return true;
}
return false;
}
String idRef(DefineTag tag) { return idRef(tag, dict); }
public static String idRef(DefineTag tag, Dictionary d)
{
if (tag == null)
{
// if tag is null then it isn't in the dict -- the SWF is invalid.
// lets be lax and print something; Matador generates invalid SWF sometimes.
return "-1";
}
else if (tag.name == null)
{
// just print the character id since no name was exported
return String.valueOf(d.getId(tag));
}
else
{
return tag.name;
}
}
private static final boolean hasLineRecord(ActionList c)
{
if (c == null || c.size() == 0)
{
return true;
}
boolean result = false;
for (int i=0; i < c.size() && !result; i++)
{
Action action = c.getAction(i);
switch (action.code)
{
case ActionConstants.sactionDefineFunction:
case ActionConstants.sactionDefineFunction2:
result = result || hasLineRecord(((DefineFunction) action).actionList);
break;
case ActionList.sactionLineRecord:
result = true;
break;
}
}
return result;
}
private void collectSpriteActions(TagList s)
{
String[] temp;
int len = s.tags.size();
for (int i = 0; i < len; i++)
{
Tag t = s.tags.get(i);
switch (t.code)
{
case TagValues.stagDoAction:
temp = pool;
collectActions(((DoAction) t).actionList);
pool = temp;
break;
case TagValues.stagDefineButton2:
collectCondActions(((DefineButton) t).condActions);
break;
case TagValues.stagDefineButton:
temp = pool;
collectActions(((DefineButton) t).condActions[0].actionList);
pool = temp;
break;
case TagValues.stagDoInitAction:
temp = pool;
collectActions(((DoInitAction) t).actionList);
pool = temp;
break;
case TagValues.stagDefineSprite:
collectSpriteActions(((DefineSprite) t).tagList);
break;
case TagValues.stagPlaceObject2:
collectClipActions(((PlaceObject) t).clipActions);
break;
}
}
}
private DebugModule findDebugModule(ActionList c)
{
MFUCache modules = new MFUCache();
for (int i=0; i < c.size(); i++)
{
Action a = c.getAction(i);
DebugModule temp = null;
switch (a.code)
{
case ActionConstants.sactionDefineFunction:
case ActionConstants.sactionDefineFunction2:
temp = findDebugModule(((DefineFunction) a).actionList);
break;
case ActionList.sactionLineRecord:
if (((LineRecord)a).module != null)
{
temp = ((LineRecord)a).module;
}
break;
}
if (temp != null)
{
modules.add(temp);
}
}
// ActionList may have actions pointing to more than one debug module because of #include, etc.
// The majority wins.
return modules.topModule;
}
private static Integer[] codes = new Integer[256];
static
{
for (int i=0; i < 256; i++)
{
codes[i] = new Integer(i);
}
}
private void collectActions(ActionList c)
{
// assumption: ActionContext c is always not null! try-catch-finally may be busted.
if (c == null)
{
return;
}
// interprets the actions. try to assign names to anonymous functions...
evalActions(c);
DebugModule d = findDebugModule(c);
String emptyMethodName = null;
// loop again, this time, we register all the actions...
for (int i=0; i < c.size(); i++)
{
int ioffset = c.getOffset(i);
Action a = c.getAction(i);
if (emptyMethodName != null && emptyMethodName.length() != 0)
{
functionNames.put(ioffset, emptyMethodName);
emptyMethodName = null;
}
if (a.code == ActionList.sactionLineRecord)
{
LineRecord line = (LineRecord) a;
if (line.module != null)
{
d = line.module;
if (d.name.endsWith(".mxml"))
{
mxml.put(d.name, d);
}
}
continue;
}
if (a.code >= 256)
{
// something synthetic we don't care about
continue;
}
actions.put(ioffset, codes[a.code]);
modules.put(ioffset, d);
switch (a.code)
{
case ActionConstants.sactionDefineFunction:
case ActionConstants.sactionDefineFunction2:
DefineFunction f = (DefineFunction) a;
Integer size = new Integer(f.codeSize);
if (f.actionList.size() == 0)
{
emptyMethodName = f.name;
}
else
{
Integer lineno = null;
// map all the offsets in this function to the function name
for (int j=0; j < f.actionList.size(); j++)
{
int o = f.actionList.getOffset(j);
Action child = f.actionList.getAction(j);
if (child.code == ActionList.sactionLineRecord)
{
// also find out the first line number of this function
if (lineno == null)
lineno = new Integer(((LineRecord)child).lineno);
preciseLines.put(o, new Integer( ((LineRecord)child).lineno ));
}
functionNames.put(o, f.name);
functionSizes.put(o, size);
}
// map all the offsets in this function to the first line number of this function.
for (int j=0; j < f.actionList.size(); j++)
{
int o = f.actionList.getOffset(j);
functionLines.put(o, lineno);
}
}
collectActions(f.actionList);
break;
}
}
}
private void collectCondActions(ButtonCondAction[] actions)
{
for (int i = 0; i < actions.length; i++)
{
collectActions(actions[i].actionList);
}
}
private void collectClipActions(ClipActions actions)
{
if (actions != null)
{
Iterator it = actions.clipActionRecords.iterator();
while (it.hasNext())
{
ClipActionRecord record = (ClipActionRecord) it.next();
collectActions(record.actionList);
}
}
}
private static Object pop(Stack