format.abc: ActionScript Bytecode

format.abc is the successor to hxAsm and is included in the File Formats library. This library can dynamically compile Flash9 assembly code into ABC (ActionScript Bytecode) which can be executed by the AVM2 (Flash 9+).

Classes

  • Reader.hx and Writer.hx are used to deserialize/serialize ABC class/method/field information
    • Raw binary found in the DoABC / TActionScript3 tag of an SWF
  • OpReader.hx and OpWriter.hx are used to deserialize/serialize ABC opcodes for method bodies
    • Raw binary found in abcData.functions[i].code
  • Data.hx contains the classes and enums
  • Context.hx is only used for code generation, and does not contain features for reading existing SWF files

By Usage

Code Modification

Code Modification example - modify an existing SWF file

Once you parse an SWF using swf.Reader.read(), you'll want to search for the DoABC tag. This contains the raw ABC data. You'll feed this in to abc.Reader.read, which will read all of the ABC metadata, but leave the actual opcodes unparsed. So finally, if you want to get the actual code out of a function, you'll want to use abc.OpReader.decode() to decode a function in ABCData,functions that abc.read() returns.

Code Generation

Code Generation example - generate a new ABC code block

If you need to write out AS3 bytecode, format has some slightly better utils for generating ABC -- check out Context.hx. For example, it's not too hard to generate an empty stub class extending Sprite and link it to a graphic using TSymbolClass.

ABC Opcodes

Reader and Writer don't actually use OpWriter and OpReader to read and write the bytecodes, but instead leave the actual code as binary blobs. This is just like how SWF reads the TActionScript3 tag as Bytes, and you have to parse it with OpReader if you are interested.

The bytecode is stored in abcData.functions[i].code, where each function represents a method or other executable unit of AS3 (remember that AS3 allows weird stuff like code at class level, outside of a method) When you read in the ABC, you'll also want to read the ops you are interested in by using OpReader.decode on abcData.functions.

For writing opcodes, you can either use Context (which uses OpWriter internally) or use manually OpWriter manually to write the ops back to Bytes in abcData.functions. Context creates a whole new ABCData block, so it's mainly good for creating new ABC from scratch. If you are modifying existing ABC, you'll want to do the latter.

The ABC decoding/encoding in format isn't super robust. For example, the opcodes you read in OpReader index into the various arrays in an ABCData, so even just printing things out for debugging is not an easy task (you have to dereference all these indexes). If you need to do some heavy parsing and generation of ABC, you'll probably have to write your own utility classes to make things easier.

Assets

If you just need to know which assets map to which AS3 classes, you can look for the TSymbolClass tag in the SWF.

Performing jumps

It's not always easy to count bytes when writing a OJump opcode. There is an API to make it more easy to works with jumps. Here's the modified fib version that uses this API :

    var m = ctx.beginMethod("fib",[tint],tint);
    m.maxStack = 3;
    ctx.ops([
        OReg(1),
        OSmallInt(1),
    ]);
    var j = ctx.jump(JGt); // prepare a jump
    ctx.ops([
        OInt(1),
        ORet,
    ]);
    j(); // patch the jump with current position
    ctx.ops([
        ODecrIReg(1),
        OThis,
        OReg(1),
        OCallProperty(ctx.property("fib"),1),
        ODecrIReg(1),
        OThis,
        OReg(1),
        OCallProperty(ctx.property("fib"),1),
        OOp(OpIAdd),
        ORet,
    ]);

The ctx.jump method writes a OJump opcode, then returns a function. This function can be called when you reach the place of the jump target.

There's also a ctx.backwardJump that works the following :

    var j = ctx.backwardJump();
    // ....
    j(JAlways); // jump to saved position

FAQ

  • In order to read an array, first push on the stack the array and the index, then use OGetProp(ctx.arrayProp)
  • In order to write into an array, first push on the stack the array, the index and the value, then use OSetProp(ctx.arrayProp)
  • I'm getting a "VerifyError":
    • #1017 : This is a scope overflow error. Try increasing your "maxScope" count.
    • #1018 : This is a scope underflow error. It means that you are using an operation (such as OPopScope) while there are not enough scopes in the chain.
    • #1021 : This is an invalid jump address error. This shouldn't occur if you use the Jump API that is presented before.
    • #1023 : This is a stack overflow error. Try increasing the maxStack property of your method.
    • #1024 : This is a stack underflow error. It means that you are using an operation (such as ORet) while there are not enough values on the stack.
    • #1025 : This is an invalid register error. Try increasing the nRegs property of your method.
    • #1030 : This is a stack unbalanced error. It means that two branches of a Jump result in different stack sizes when they join back. All jumps or code leading to a given position should result in the same stack size.

ASM Reference

A good reference of the Flash9 AVM2 instructions can be found here. The names in this reference are not always the same as in format.abc, but they should be similar. If you edit format/abc/OpWriter.hx you'll see for each opcode the hex code AVM2 is using.

A list of available opcodes, jump styles, and operators can be found in format/abc/Data.hx.

Don't hesitate to ask on the Haxe mailing list if you have any question about format.abc usage.

version #19969, modified 2014-03-01 06:00:49 by hgupta