apparat.abc.Abc.scala Maven / Gradle / Ivy
/*
* This file is part of Apparat.
*
* Apparat 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 of the License, or
* (at your option) any later version.
*
* Apparat 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 Apparat. If not, see .
*
* Copyright (C) 2009 Joa Ebert
* http://www.joa-ebert.com/
*
*/
package apparat.abc
import scala.annotation.tailrec
import apparat.bytecode._
import apparat.utils.IO
import apparat.utils.IO._
import apparat.utils.Dumpable
import apparat.utils.IndentingPrintWriter
import apparat.swf.{DoABC, Swf, SwfTag, SwfTags}
import apparat.abc.analysis._
import scala.collection.immutable._
import apparat.actors.Futures._
import java.io.{
BufferedInputStream => JBufferedInputStream,
ByteArrayInputStream => JByteArrayInputStream,
ByteArrayOutputStream => JByteArrayOutputStream,
InputStream => JInputStream,
File => JFile,
FileInputStream => JFileInputStream,
FileOutputStream => JFileOutputStream,
OutputStream => JOutputStream
}
import scala.math.max
object Abc {
val MINOR = 16
//val MINOR = 17//with decimals!
val MAJOR = 46
def fromDoABC(doABC: DoABC) = fromByteArray(doABC.abcData)
def fromByteArray(byteArray: Array[Byte]) = {
val abc = new Abc
abc read byteArray
abc
}
def fromTag(tag: SwfTag) = tag match {
case doABC : DoABC => Some(fromDoABC(doABC))
case _ => None
}
def fromSwf(swf: Swf) = swf.tags find (_.kind == SwfTags.DoABC) match {
case Some(doABC) => fromTag(doABC)
case None => None
}
def fromFile(file: JFile): Abc = {
val abc = new Abc()
abc read file
abc
}
def fromInputStream(inputStream: JInputStream): Abc = {
val abc = new Abc()
abc read inputStream
abc
}
def fromFile(pathname: String): Abc = fromFile(new JFile(pathname))
def using[T](doABC: DoABC, supportBytecode: Boolean = true)(f: Abc => T) = {
val abc = Abc fromDoABC doABC
val result = if(supportBytecode) {
abc.loadBytecode()
val temp = f(abc)
abc.saveBytecode()
temp
} else {
f(abc)
}
abc write doABC
result
}
}
class Abc extends Dumpable {
var cpool = new AbcConstantPool(new Array[Int](0),
new Array[Long](0), new Array[Double](0), new Array[Symbol](0),
new Array[AbcNamespace](0), new Array[AbcNSSet](0), new Array[AbcName](0))
var methods = new Array[AbcMethod](0)
var metadata = new Array[AbcMetadata](0)
var types = new Array[AbcNominalType](0)
var scripts = new Array[AbcScript](0)
var bytecodeAvailable = false
def +(that: Abc) = {
val result = new Abc()
result.cpool = this.cpool + that.cpool
result.methods = this.methods ++ that.methods
result.metadata = this.metadata ++ that.metadata
result.types = this.types ++ that.types
result.scripts = this.scripts ++ that.scripts
result
}
def accept(visitor: AbcVisitor) = {
visitor visit this
cpool accept visitor
methods foreach { _ accept visitor }
metadata foreach { _ accept visitor }
types foreach { _ accept visitor }
scripts foreach { _ accept visitor }
}
def rebuildPool() = cpool = AbcConstantPoolBuilder using this
def loadBytecode() = if(!bytecodeAvailable) {
implicit val abc = this
methods filter (_.body.isDefined) map {
method => future {
val body = method.body.get
body.bytecode = Some(Bytecode fromBody body)
}
} map { _() }
/*methods foreach {
_.body match {
case Some(body) => body.bytecode = Some(Bytecode fromBody body)
case None => {}
}
}*/
bytecodeAvailable = true
}
def saveBytecode() = if(bytecodeAvailable) {
implicit val abc = this
val tasks = for(method <- methods if method.body.isDefined) yield future {
val body = method.body.get
body.bytecode match {
case Some(bytecode) => bytecode storeIn body
case None =>
}
}
tasks foreach { _() }
/*methods foreach {
_.body match {
case Some(body) => body.bytecode match {
case Some(bytecode) => bytecode storeIn body
case None => {}
}
case None => {}
}
}*/
}
def read(file: JFile): Unit = using(new JBufferedInputStream(new JFileInputStream(file), 0x1000))(read _)
def read(pathname: String): Unit = read(new JFile(pathname))
def read(input: JInputStream): Unit = using(new AbcInputStream(input))(read _)
def read(data: Array[Byte]): Unit = using(new JByteArrayInputStream(data))(read _)
def read(doABC: DoABC): Unit = read(doABC.abcData)
def read(input: AbcInputStream): Unit = {
if (input.readU16() != Abc.MINOR) error("Minor version not supported.")
if (input.readU16() != Abc.MAJOR) error("Major version not supported.")
cpool = readPool(input)
methods = readMethods(input)
metadata = readMetadata(input)
types = readTypes(input)
scripts = readScripts(input)
readBodies(input)
}
def write(file: JFile): Unit = using(new JFileOutputStream(file))(write _)
def write(pathname: String): Unit = write(new JFile(pathname))
def write(output: JOutputStream): Unit = using(new AbcOutputStream(output))(write _)
def write(doABC: DoABC): Unit = doABC.abcData = toByteArray
def write(output: AbcOutputStream): Unit = {
output writeU16 Abc.MINOR
output writeU16 Abc.MAJOR
writePool(output)
writeMethods(output)
writeMetadata(output)
writeTypes(output)
writeScripts(output)
writeBodies(output)
}
def toByteArray = {
val byteArrayOutputStream = new JByteArrayOutputStream()
using(byteArrayOutputStream)(write _)
byteArrayOutputStream.toByteArray
}
private def readPool(implicit input: AbcInputStream) = {
def readTable[T](t: Array[T], empty: T)(reader: => T) = {
t(0) = empty
for (i <- 1 until t.length) t(i) = reader
t
}
val ints = readTable(new Array[Int](max(1, input.readU30())), 0) { input.readS32() }
val uints = readTable(new Array[Long](max(1, input.readU30())), 0L) { input.readU32() }
val doubles = readTable(new Array[Double](max(1, input.readU30())), Double.NaN) { input.readD64() }
//val decimals = ...
val strings = readTable(new Array[Symbol](max(1, input.readU30())), AbcConstantPool.EMPTY_STRING) { Symbol(input.readString()) }
val namespaces = readTable(new Array[AbcNamespace](max(1, input.readU30())), AbcConstantPool.EMPTY_NAMESPACE) { AbcNamespace(input.readU08(), strings(input.readU30())) }
val nssets = readTable(new Array[AbcNSSet](max(1, input.readU30())), AbcConstantPool.EMPTY_NSSET) { AbcNSSet(Array.fill(input.readU08()) { namespaces(input.readU30()) }) }
val tmp = new Array[AbcName](max(1, input.readU30()))
val names = readTable(tmp, AbcConstantPool.EMPTY_NAME) {
input.readU08() match {
case AbcNameKind.QName => {
val namespace = input.readU30()
val name = input.readU30()
AbcQName(strings(name), namespaces(namespace))
}
case AbcNameKind.QNameA => {
val namespace = input.readU30()
val name = input.readU30()
AbcQNameA(strings(name), namespaces(namespace))
}
case AbcNameKind.RTQName => AbcRTQName(strings(input.readU30()))
case AbcNameKind.RTQNameA => AbcRTQNameA(strings(input.readU30()))
case AbcNameKind.RTQNameL => AbcRTQNameL
case AbcNameKind.RTQNameLA => AbcRTQNameLA
case AbcNameKind.Multiname => AbcMultiname(strings(input.readU30()), nssets(input.readU30()))
case AbcNameKind.MultinameA => AbcMultinameA(strings(input.readU30()), nssets(input.readU30()))
case AbcNameKind.MultinameL => AbcMultinameL(nssets(input.readU30()))
case AbcNameKind.MultinameLA => AbcMultinameLA(nssets(input.readU30()))
case AbcNameKind.Typename => {
AbcTypename((tmp(input.readU30())).asInstanceOf[AbcQName], Array.fill(input.readU30()) { tmp(input.readU30()) })
}
case _ => error("Unknown multiname kind.")
}
}
new AbcConstantPool(ints, uints, doubles, strings, namespaces, nssets, names)
}
private def writePool(implicit output: AbcOutputStream) = {
def writeTable[T](t: Array[T])(writer: T => Unit) = {
t.length match {
case 1 => output writeU30 0
case n => {
output writeU30 n
for(i <- 1 until n)
writer(t(i))
}
}
}
writeTable(cpool.ints)(output writeS32 _)
writeTable(cpool.uints)(output writeU32 _)
writeTable(cpool.doubles)(output writeD64 _)
writeTable(cpool.strings)(output writeString _.name)
writeTable(cpool.namespaces) {
namespace => {
output writeU08 namespace.kind
writePooledString(namespace.name)
}
}
writeTable(cpool.nssets) {
nsset => {
output writeU08 nsset.set.length
nsset.set foreach writePooledNamespace
}
}
writeTable(cpool.names) {
name => {
output writeU08 name.kind
name match {
case AbcQName(name, namespace) => {
writePooledNamespace(namespace)
writePooledString(name)
}
case AbcQNameA(name, namespace) => {
writePooledNamespace(namespace)
writePooledString(name)
}
case AbcRTQName(name) => writePooledString(name)
case AbcRTQNameA(name) => writePooledString(name)
case AbcRTQNameL | AbcRTQNameLA => {}
case AbcMultiname(name, nsset) => {
writePooledString(name)
writePooledNSSet(nsset)
}
case AbcMultinameA(name, nsset) => {
writePooledString(name)
writePooledNSSet(nsset)
}
case AbcMultinameL(nsset) => writePooledNSSet(nsset)
case AbcMultinameLA(nsset) => writePooledNSSet(nsset)
case AbcTypename(name, parameters) => {
writePooledName(name)
output writeU30 parameters.length
parameters foreach writePooledName
}
}
}
}
}
private def readMethods(implicit input: AbcInputStream) = Array.fill(input.readU30()) {
val numParameters = input.readU30()
val returnType = readPooledName()
val parameters = Array.fill(numParameters) { new AbcMethodParameter(readPooledName()) }
val name = readPooledString()
val flags = input.readU08()
if (0 != (flags & 0x08)) {
val numOptional = input.readU30()
assert(numOptional <= numParameters)
parameters takeRight numOptional foreach {
parameter => {
val index = input.readU30()
parameter.optional = true;
parameter.optionalType = Some(input.readU08())
parameter.optionalVal = Some(cpool.constant(parameter.optionalType.get, index))
}
}
}
if (0 != (flags & 0x80))
parameters foreach (_.name = Some(readPooledString()))
new AbcMethod(parameters, returnType, name,
0 != (flags & 0x01), 0 != (flags & 0x02), 0 != (flags & 0x04),
0 != (flags & 0x08), 0 != (flags & 0x10), 0 != (flags & 0x20),
0 != (flags & 0x40), 0 != (flags & 0x80))
}
private def writeMethods(implicit output: AbcOutputStream) = writeAll(methods) {
method => {
output writeU30 method.parameters.length
writePooledName(method.returnType)
method.parameters foreach (((_: AbcMethodParameter).typeName) andThen writePooledName)
writePooledString(method.name)
output writeU08 (
(if(method.needsArguments) 0x01 else 0x00)
| (if(method.needsActivation) 0x02 else 0x00)
| (if(method.needsRest) 0x04 else 0x00)
| (if(method.hasOptionalParameters) 0x08 else 0x00)
| (if(method.ignoreRest) 0x10 else 0x00)
| (if(method.isNative) 0x20 else 0x00)
| (if(method.setsDXNS) 0x40 else 0x00)
| (if(method.hasParameterNames) 0x80 else 0x00))
if(method.hasOptionalParameters) {
output writeU30 (method.parameters count (_.optional == true))
for(parameter <- method.parameters if parameter.optional) {
output writeU30 (cpool indexOf (parameter.optionalType.get, parameter.optionalVal.get))
output writeU08 parameter.optionalType.get
}
}
if(method.hasParameterNames)
method.parameters foreach (((_: AbcMethodParameter).name.get) andThen writePooledString)
}
}
private def readMetadata(implicit input: AbcInputStream) = Array.fill(input.readU30()) {
val name = readPooledString()
val n = input.readU30()
val keys = Array.fill(n)(readPooledString)
@tailrec def traverse(index: Int, map: Map[Symbol, Symbol]): Map[Symbol, Symbol] = index match {
case x if x == n => map
case y => traverse(y + 1, map + (keys(y) -> readPooledString()))
}
new AbcMetadata(name, traverse(0, new HashMap[Symbol, Symbol]))
}
private def writeMetadata(implicit output: AbcOutputStream) = writeAll(metadata) {
meta => {
output writeU30 (cpool indexOf meta.name)
output writeU30 meta.attributes.size
meta.attributes.keysIterator foreach writePooledString
meta.attributes.valuesIterator foreach writePooledString
}
}
private def readTypes(implicit input: AbcInputStream) = {
val result = Array.fill(input.readU30()) {
val name = readPooledNonZeroName().asInstanceOf[AbcQName]
val base = input.readU30() match {
case 0 => None
case x => Some(cpool.names(x))
}
val flags = input.readU08()
val protectedNs = if (0 != (flags & 0x08)) Some(readPooledNamespace()) else None
val interfaces = Array.fill(input.readU30())(readPooledName)
new AbcNominalType(new AbcInstance(name, base, 0 != (flags & 0x01),
0 != (flags & 0x02), 0 != (flags & 0x04), 0 != (flags & 0x10), protectedNs,
interfaces, methods(input.readU30()), readTraits()))
}
result foreach (_.klass = new AbcClass(methods(input.readU30()), readTraits()))
result
}
private def writeTypes(implicit output: AbcOutputStream) = {
writeAll(types) {
nominalType => {
val inst = nominalType.inst
writePooledName(inst.name)
output writeU30 (inst.base match {
case Some(x) => cpool indexOf x
case None => 0
})
output writeU08 (
(if(inst.isSealed) 0x01 else 0x00)
| (if(inst.isFinal) 0x02 else 0x00)
| (if(inst.isInterface) 0x04 else 0x00)
| (if(inst.protectedNs.isDefined) 0x08 else 0x00)
| (if(inst.nonNullable) 0x10 else 0x00))
inst.protectedNs match {
case Some(x) => writePooledNamespace(x)
case None => {}
}
output writeU30 inst.interfaces.length
inst.interfaces foreach writePooledName
output writeU30 (methods indexOf inst.init)
writeTraits(inst.traits)
}
}
for(nominalType <- types) {
val klass = nominalType.klass
output writeU30 (methods indexOf klass.init)
writeTraits(klass.traits)
}
}
private def readScripts(implicit input: AbcInputStream) = Array.fill(input.readU30()) { new AbcScript(methods(input.readU30()), readTraits()) }
private def writeScripts(implicit output: AbcOutputStream) = writeAll(scripts) {
script => {
output writeU30 (methods indexOf script.init)
writeTraits(script.traits)
}
}
private def readTraits()(implicit input: AbcInputStream): Array[AbcTrait] = Array.fill(input.readU30()) { //why is the type annotation needed?
val name = readPooledNonZeroName().asInstanceOf[AbcQName]
val pack = input.readU08()
val kind = pack & 0x0f
val attr = (pack & 0xf0) >> 4
def meta(a: Int) = if (0 != (a & 0x04)) {
Some(Array.fill(input.readU30()) { metadata(input.readU30()) })
} else {
None
}
kind match {
case AbcTraitKind.Slot => {
val index = input.readU30()
val typeName = readPooledName()
val value = input.readU30()
if (0 != value) {
val kind = input.readU08()
new AbcTraitSlot(name, index, typeName, Some(kind), Some(cpool.constant(kind, value)), meta(attr))
} else { new AbcTraitSlot(name, index, typeName, None, None, meta(attr)) }
}
case AbcTraitKind.Const => {
val index = input.readU30()
val typeName = readPooledName()
val value = input.readU30()
if (0 != value) {
val kind = input.readU08()
new AbcTraitConst(name, index, typeName, Some(kind), Some(cpool.constant(kind, value)), meta(attr))
} else { new AbcTraitConst(name, index, typeName, None, None, meta(attr)) }
}
case AbcTraitKind.Method => new AbcTraitMethod(name, input.readU30(), methods(input.readU30()), 0 != (attr & 0x01), 0 != (attr & 0x02), meta(attr))
case AbcTraitKind.Getter => new AbcTraitGetter(name, input.readU30(), methods(input.readU30()), 0 != (attr & 0x01), 0 != (attr & 0x02), meta(attr))
case AbcTraitKind.Setter => new AbcTraitSetter(name, input.readU30(), methods(input.readU30()), 0 != (attr & 0x01), 0 != (attr & 0x02), meta(attr))
case AbcTraitKind.Class => new AbcTraitClass(name, input.readU30(), types(input.readU30()), meta(attr))
case AbcTraitKind.Function => new AbcTraitFunction(name, input.readU30(), methods(input.readU30()), meta(attr))
}
}
private def writeTraits(traits: Array[AbcTrait])(implicit output: AbcOutputStream) = writeAll(traits) {
t => {
writePooledName(t.name)
output writeU08 (t.kind | (((t match {
case x: AbcTraitAnyMethod => ((if(x.isFinal) 0x01 else 0x00) | (if(x.isOverride) 0x02 else 0x00))
case _ => 0x00
}) | (t.metadata match {
case Some(x) => 0x04
case None => 0x00
})) << 0x04))
t match {
case AbcTraitSlot(name, index, typeName, valueType, value, metadata) => {
output writeU30 index
writePooledName(typeName)
value match {
case Some(x) => {
output writeU30 (cpool indexOf (valueType.get, value.get))
output writeU08 valueType.get
}
case None => output writeU30 0
}
}
case AbcTraitConst(name, index, typeName, valueType, value, metadata) => {
output writeU30 index
writePooledName(typeName)
value match {
case Some(x) => {
output writeU30 (cpool indexOf (valueType.get, value.get))
output writeU08 valueType.get
}
case None => output writeU30 0
}
}
case AbcTraitMethod(name, dispId, method, isFinal, isOverride, metadata) => {
output writeU30 dispId
output writeU30 (methods indexOf method)
}
case AbcTraitGetter(name, dispId, method, isFinal, isOverride, metadata) => {
output writeU30 dispId
output writeU30 (methods indexOf method)
}
case AbcTraitSetter(name, dispId, method, isFinal, isOverride, metadata) => {
output writeU30 dispId
output writeU30 (methods indexOf method)
}
case AbcTraitClass(name, index, nominalType, metadata) => {
output writeU30 index
output writeU30 (types indexOf nominalType)
}
case AbcTraitFunction(name, index, function, metadata) => {
output writeU30 index
output writeU30 (methods indexOf function)
}
}
t.metadata match {
case Some(meta) => {
output writeU30 meta.length
meta foreach (x => output writeU30 (metadata indexOf x))
}
case None => {}
}
}
}
private def readBodies(implicit input: AbcInputStream) = for(i <- 0 until input.readU30()) {
val methodId = input.readU30()
val maxStack = input.readU30()
val localCount = input.readU30()
val initScopeDepth = input.readU30()
val maxScopeDepth = input.readU30()
val code = IO read input.readU30()
methods(methodId).body = Some(new AbcMethodBody(maxStack, localCount,
initScopeDepth, maxScopeDepth, code, readExceptions(),
readTraits()))
}
private def writeBodies(implicit output: AbcOutputStream) = {
output writeU30 (methods count (method => method.body.isDefined))
for(i <- 0 until methods.length) {
val method = methods(i)
method.body match {
case Some(body) => {
output writeU30 i
output writeU30 body.maxStack
output writeU30 body.localCount
output writeU30 body.initScopeDepth
output writeU30 body.maxScopeDepth
output writeU30 body.code.length
output write body.code
writeExceptions(body.exceptions)
writeTraits(body.traits)
}
case None => {}
}
}
}
private def readExceptions()(implicit input: AbcInputStream) = Array.fill(input.readU30()) { new AbcExceptionHandler(input.readU30(), input.readU30(), input.readU30(), readPooledName(), readPooledName()) }
private def writeExceptions(exceptions: Array[AbcExceptionHandler])(implicit output: AbcOutputStream) = writeAll(exceptions) {
handler => {
output writeU30 handler.from
output writeU30 handler.to
output writeU30 handler.target
writePooledName(handler.typeName)
writePooledName(handler.varName)
}
}
private def readPooledString()(implicit input: AbcInputStream): Symbol = cpool.strings(input.readU30())
private def writePooledString(value: Symbol)(implicit output: AbcOutputStream) = output writeU30 (cpool indexOf value)
private def readPooledNamespace()(implicit input: AbcInputStream) = cpool.namespaces(input.readU30())
private def writePooledNamespace(value: AbcNamespace)(implicit output: AbcOutputStream) = output writeU30 (cpool indexOf value)
private def readPooledNSSet()(implicit input: AbcInputStream) = cpool.nssets(input.readU30())
private def writePooledNSSet(value: AbcNSSet)(implicit output: AbcOutputStream) = output writeU30 (cpool indexOf value)
private def readPooledName()(implicit input: AbcInputStream) = cpool.names(input.readU30())
private def writePooledName(value: AbcName)(implicit output: AbcOutputStream) = output writeU30 (cpool indexOf value)
private def readPooledNonZeroName()(implicit input: AbcInputStream) = {
val index = input.readU30()
if (0 == index) error("Constant pool index may not be zero.")
cpool.names(index)
}
private def writePooledNonZeroName(value: AbcName)(implicit output: AbcOutputStream) = {
val index = cpool indexOf value
if(0 == index) error("Constant pool index may not be zero.")
output writeU30 index
}
private def writeAll[T](array: Array[T])(write: T => Unit)(implicit output: AbcOutputStream) = {
output writeU30 array.length
array foreach write
}
override def dump(writer: IndentingPrintWriter) = {
writer <= "Abc:"
writer withIndent {
writer <= methods.length+" method(s), "+metadata.length+
" metadata, "+types.length+" type(s), "+scripts.length+" script(s)"
cpool dump writer
writer <= "Functions:"
writer withIndent {
methods filter (_.anonymous) foreach (_ dump writer)
}
writer <= "Metadata:"
writer <<< metadata
writer <= "Scripts:"
writer withIndent {
scripts foreach (_ dump writer)
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy