41 Commits
Author SHA1 Message Date
Vladimir Panteleev e49a6b3fec Update Changelog 2011-05-13 03:20:57 +03:00
Vladimir Panteleev bfa02943c6 disassembler: escape % too (the escape character) 2011-05-13 03:08:40 +03:00
Vladimir Panteleev cf8835b87d disassember: Escape space characters in filenames too (Windows forbids leading/trailing spaces) 2011-05-12 13:59:32 +03:00
Vladimir Panteleev 4c411ac1f2 Minor changes 2011-05-12 13:58:56 +03:00
Vladimir Panteleev 76cadf30cf Add swfbinexport and swfbinreplace utilities 2011-05-12 08:33:51 +03:00
Vladimir Panteleev 2498673231 Update copyright notices 2011-05-12 08:07:57 +03:00
Vladimir Panteleev a1b9ea05c0 Add a simple build tool 2011-05-12 07:59:26 +03:00
Vladimir Panteleev a766cf5919 Emit commented-out method body with raw hex bytes 2011-05-11 04:24:43 +03:00
Vladimir Panteleev e6f54db4c1 disassembler: Handle infinities in double shortening 2011-05-11 03:59:40 +03:00
Vladimir Panteleev 51e82bf985 Update Changelog 2011-05-11 03:45:39 +03:00
Vladimir Panteleev 282e8cbfc1 asprogram: Remove enum compiler hack 2011-05-11 01:06:09 +03:00
Vladimir Panteleev 4f53b789ee disassembler: aggressively try to shorten double literals 2011-04-17 22:46:12 +03:00
Vladimir Panteleev 0c5cbde348 disassembler: refuse to overwrite files 2011-04-17 08:29:03 +03:00
Vladimir Panteleev ff799d002f disassembler: place the contents of an entire script in one file 2011-04-17 08:11:35 +03:00
Vladimir Panteleev 1bf7a9a593 disassembler: handle private namespaces only referenced in initalizers 2011-04-16 20:53:50 +03:00
Vladimir Panteleev 1167b14471 disassembler: place script initializers in files near the rest of the script content 2011-04-16 20:09:39 +03:00
Vladimir Panteleev d3e8e47d54 disassembler: better refids and location for orphan classes and methods
This also changes the way classes are written (in a backwards-compatible
way).
2011-04-16 08:32:25 +03:00
Vladimir Panteleev 3f9be2b192 Whitespace 2011-04-16 08:25:39 +03:00
Vladimir Panteleev a690c82b7d disassembler: write exact context of private namespaces as a comment in the .privatens.asasm file 2011-04-16 07:15:49 +03:00
Vladimir Panteleev 2d7886bb0b disassembler: relax rootContext 2011-04-15 18:22:50 +03:00
Vladimir Panteleev 4cb179a1c6 disassembler: allow named private namespaces (ignore the name) 2011-04-15 18:21:45 +03:00
Vladimir Panteleev 0031be1f75 disassembler: improve context abbreviation for some cases 2011-04-15 17:58:06 +03:00
Vladimir Panteleev c7b7a5bce8 disassembler: Sort #privatens directives alphabetically 2011-04-15 17:46:29 +03:00
Vladimir Panteleev ac5d4b5e74 disassembler: rewrite the way refids/private namespace names are generated.
This will also place nested classes correctly in the directory structure.
2011-04-15 17:37:46 +03:00
Vladimir Panteleev eb07838668 Documentation: v1.1 change did not in fact break backwards compatibility 2011-04-15 07:15:45 +03:00
Vladimir Panteleev b80fe48bee Failing to decode a method body is now not a fatal error
Some obfuscators seem to append an orphan method to the end of the file
filled with junk. The disassembly will contain an empty body with a comment
describing the error message.
2011-04-14 13:26:17 +03:00
Vladimir Panteleev a083ed265f disassembler: always disassemble to the same location as the source file
Previously, rabcdasm would always create the disassembly directory in the
current directory.
2011-04-14 12:26:34 +03:00
Vladimir Panteleev 986ea47d03 Use "final switch" for opcode types 2011-04-14 11:50:31 +03:00
Vladimir Panteleev 3520d34cc2 Output correct opcode error location 2011-04-14 11:40:03 +03:00
Vladimir Panteleev 58ab6e3bf0 Documentation updates 2011-04-14 05:50:17 +03:00
Vladimir Panteleev 413aa9b5f0 Update Changelog 2011-04-14 05:28:22 +03:00
Vladimir Panteleev 5da1e26835 Add support for memory-access and sign-extend opcodes 2011-04-14 05:02:03 +03:00
Vladimir Panteleev 53a4173fd3 Improve exception handling code 2011-04-14 05:01:40 +03:00
Vladimir Panteleev 5737243606 Update Changelog 2011-03-22 12:33:46 +02:00
Vladimir Panteleev be727e67c6 Documentation: remove -release from compilation instructions 2011-03-22 10:37:43 +02:00
Vladimir Panteleev 9d67c46835 abcfile: Use preallocated initial buffer for writeMethodBody 2011-03-22 09:50:27 +02:00
Vladimir Panteleev 756a2fd060 assembler: Remove a TODO for a bad idea 2011-03-22 09:47:30 +02:00
Vladimir Panteleev 8ad1f85a45 asprogram: sort faster 2011-03-22 09:18:14 +02:00
Vladimir Panteleev ad6ae5d087 autodata: Fix grouping of array fields (D2 porting bug) 2011-03-22 05:46:42 +02:00
Vladimir Panteleev 580a1364d0 Clarify some asserts 2011-03-22 04:39:25 +02:00
Vladimir Panteleev d4f90f9c01 Documentation: compile with optimization 2011-03-22 04:33:50 +02:00
18 changed files with 862 additions and 397 deletions
+36 -1
View File
@@ -1,6 +1,42 @@
RABCDAsm Changelog
==================
RABCDAsm v1.6 (2011.05.13)
--------------------------
* Overhaul of the structure of disassembler-generated files
* Private namespace names and `refid`s for nested classes are now generated
from their referenced contexts
* This also fixes problems caused by private namespaces being referenced
in multiple distinct contexts
* One `#include`d file per script instead of per class
* Nested classes, script initializers and other script traits are now in
the same file as their main class
* Inline methods are now in separate files, placed according to their usage
context
* Private namespaces are sorted by their autogenerated names in the
`.privatens.asasm` file
* No syntax changes, output is backwards-compatible
* Improved escaping of file names
* Output correct opcode error locations
* Disassembler will now refuse to overwrite files
* `rabcdasm` will now always generate output in the same directory as the
input file
* Failure to decode a method body is now not a fatal error
* Aggressively try to shorten double literals to their shortest representation
* Added the `swfbinexport` and `swfbinreplace` utilities, to aid in manipulating
contents of `DefineBinaryData` tags
* The package can now be built using a simple, dedicated build tool
* Documentation updates
RABCDAsm v1.5 (2011.04.14)
--------------------------
* Fixed v1.4 constant pool regression
* Added support for memory-access and sign-extend opcodes
* Speed optimizations
* Documentation updates
RABCDAsm v1.4 (2011.03.07)
--------------------------
@@ -34,7 +70,6 @@ RABCDAsm v1.1 (2010.06.30)
* Private namespaces are now referenced by auto-generated names
* Use `:` to delimit namespace and name in QNames for consistency
* Warning: this breaks compatibility with v1.0 disassemblies
* Fixed relative include paths
* Add optional byte offsets to labels, which allows lossless representation
of jumps inside instructions and outside the function bounds
+30 -25
View File
@@ -11,6 +11,8 @@ These are:
* `abcreplace` - replaces ABC in SWF files
* `swfdecompress` - decompresses zlib-compressed SWF files
* `swf7zcompress` - (re-)compress the contents of a SWF using 7-Zip
* `swfbinexport` / `swfbinreplace` - extract/replace contents of binary
data tags from SWF files
`abcexport` and `abcreplace` are reimplementations of similar utilities from
my [swfutilsex][] Java package, however these work faster as they do not parse
@@ -21,6 +23,9 @@ equivalent to [flasm][]'s `-x` option.
`swf7zcompress` is an utility to further reduce the size of SWF files. It uses
[7-Zip][] to compress the data better than the standard zlib library would. It
requires that the `7z` command-line program be installed and in `PATH`.
`swfbinexport` and `swfbinreplace` aid in the manipulation of
`DefineBinaryData` tags in SWF files (some files may contain nested SWF files
stored in these tags).
[RABCDAsm]: http://github.com/CyberShadow/RABCDAsm
[swfutilsex]: http://github.com/CyberShadow/swfutilsex
@@ -50,20 +55,20 @@ Compiling from source
RABCDAsm is written in the [D programming language, version 2][d2].
Assuming you have [dmd][] and [git][] installed, compiling should be as
straight-forward as:
Assuming you have [git][] and a D2 compiler, such as [dmd][] or [gdc][]
installed, compiling should be as straight-forward as:
git clone git://github.com/CyberShadow/RABCDAsm.git
cd RABCDAsm
dmd rabcdasm abcfile asprogram disassembler autodata murmurhash2a
dmd rabcasm abcfile asprogram assembler autodata murmurhash2a
dmd abcexport swffile zlibx
dmd abcreplace swffile zlibx
dmd swfdecompress swffile zlibx
dmd swf7zcompress swffile zlibx
dmd -run build_rabcdasm
Substitute `dmd` with `gdmd` if you're using gdc. You can use the `DC` and
`DCFLAGS` environment variables to override the detected compiler and default
compilation flags (`-O -inline`).
[d2]: http://www.digitalmars.com/d/2.0/
[dmd]: http://www.digitalmars.com/d/download.html
[gdc]: http://bitbucket.org/goshawk/gdc/
[git]: http://git-scm.com/
Pre-compiled binaries
@@ -296,9 +301,9 @@ be instantiated in two ways:
although variables are defined using a string syntax, they are not
inserted as a string using this syntax. Thus, the code:
#set str "Hello, world!"
...
pushstring $str
#set str "Hello, world!"
...
pushstring $str
will expand to `pushstring Hello, world!`, which will result in an error.
To correct the problem, add escaped quotes around the variable contents
@@ -384,7 +389,7 @@ RABCDAsm users.
2. If you plan on making non-trivial changes to SWF files, you should install
the [debug Flash Player][]. This will allow you to see validation and
run-time error messages, instead of simply getting a blank rectangle.
run-time error messages, instead of simply getting an empty window.
[debug Flash Player]: http://www.adobe.com/support/flashplayer/downloads.html
@@ -393,19 +398,19 @@ RABCDAsm users.
placed in the `OnBeforeResponse` function) will automatically save all SWF
files while preserving the directory structure.
if (oSession.oResponse.headers.ExistsAndContains("Content-Type",
"application/x-shockwave-flash")) {
// Set desired path here
var path:String = "C:\\Temp\\FiddlerCapture\\" +
oSession.host + oSession.PathAndQuery;
if (path.Contains('?'))
path = path.Substring(0, path.IndexOf('?'));
var dir:String = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
oSession.utilDecodeResponse();
oSession.SaveResponseBody(path);
}
if (oSession.oResponse.headers.ExistsAndContains("Content-Type",
"application/x-shockwave-flash")) {
// Set desired path here
var path:String = "C:\\Temp\\FiddlerCapture\\" +
oSession.host + oSession.PathAndQuery;
if (path.Contains('?'))
path = path.Substring(0, path.IndexOf('?'));
var dir:String = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
oSession.utilDecodeResponse();
oSession.SaveResponseBody(path);
}
Once you have edited a SWF file, you can use Fiddler's [AutoResponder][] to
replace the original file with your modified version.
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
+43 -37
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
@@ -21,8 +21,8 @@ module abcfile;
import std.string : format; // exception formatting
import std.exception;
/**
* Implements a shallow representation of an .abc file.
/**
* Implements a shallow representation of an .abc file.
* Loading and saving an .abc file using this class should produce
* output identical to the input.
*/
@@ -207,6 +207,9 @@ class ABCFile
Instruction[] instructions;
ExceptionInfo[] exceptions;
TraitsInfo[] traits;
string error;
ubyte[] rawBytes;
}
/// Destination for a jump or exception block boundary
@@ -388,7 +391,7 @@ enum MethodFlags : ubyte
NEED_REST = 0x04, // This flag creates an ActionScript 3.0 rest arguments array. Must not be used with NEED_ARGUMENTS. See Chapter 3.
HAS_OPTIONAL = 0x08, // Must be set if this method has optional parameters and the options field is present in this method_info structure.
SET_DXNS = 0x40, // Must be set if this method uses the dxns or dxnslate opcodes.
HAS_PARAM_NAMES = 0x80, // Must be set when the param_names field is present in this method_info structure.
HAS_PARAM_NAMES = 0x80, // Must be set when the param_names field is present in this method_info structure.
}
const string[8] MethodFlagNames = ["NEED_ARGUMENTS", "NEED_ACTIVATION", "NEED_REST", "HAS_OPTIONAL", "0x10", "0x20", "SET_DXNS", "HAS_PARAM_NAMES"];
@@ -709,16 +712,16 @@ const OpcodeInfo[256] opcodeInfo = [
/* 0x32 */ {"hasnext2", [OpcodeArgumentType.UIntLiteral, OpcodeArgumentType.UIntLiteral]},
/* 0x33 */ {"pushdecimal", [OpcodeArgumentType.Unknown]},
/* 0x34 */ {"pushdnan", [OpcodeArgumentType.Unknown]},
/* 0x35 */ {"li8", [OpcodeArgumentType.Unknown]},
/* 0x36 */ {"li16", [OpcodeArgumentType.Unknown]},
/* 0x37 */ {"li32", [OpcodeArgumentType.Unknown]},
/* 0x38 */ {"lf32", [OpcodeArgumentType.Unknown]},
/* 0x39 */ {"lf64", [OpcodeArgumentType.Unknown]},
/* 0x3A */ {"si8", [OpcodeArgumentType.Unknown]},
/* 0x3B */ {"si16", [OpcodeArgumentType.Unknown]},
/* 0x3C */ {"si32", [OpcodeArgumentType.Unknown]},
/* 0x3D */ {"sf32", [OpcodeArgumentType.Unknown]},
/* 0x3E */ {"sf64", [OpcodeArgumentType.Unknown]},
/* 0x35 */ {"li8", []},
/* 0x36 */ {"li16", []},
/* 0x37 */ {"li32", []},
/* 0x38 */ {"lf32", []},
/* 0x39 */ {"lf64", []},
/* 0x3A */ {"si8", []},
/* 0x3B */ {"si16", []},
/* 0x3C */ {"si32", []},
/* 0x3D */ {"sf32", []},
/* 0x3E */ {"sf64", []},
/* 0x3F */ {"0x3F", [OpcodeArgumentType.Unknown]},
/* 0x40 */ {"newfunction", [OpcodeArgumentType.Method]},
/* 0x41 */ {"call", [OpcodeArgumentType.UIntLiteral]},
@@ -736,9 +739,9 @@ const OpcodeInfo[256] opcodeInfo = [
/* 0x4D */ {"callinterface", [OpcodeArgumentType.Unknown]},
/* 0x4E */ {"callsupervoid", [OpcodeArgumentType.Multiname, OpcodeArgumentType.UIntLiteral]},
/* 0x4F */ {"callpropvoid", [OpcodeArgumentType.Multiname, OpcodeArgumentType.UIntLiteral]},
/* 0x50 */ {"sxi1", [OpcodeArgumentType.Unknown]},
/* 0x51 */ {"sxi8", [OpcodeArgumentType.Unknown]},
/* 0x52 */ {"sxi16", [OpcodeArgumentType.Unknown]},
/* 0x50 */ {"sxi1", []},
/* 0x51 */ {"sxi8", []},
/* 0x52 */ {"sxi16", []},
/* 0x53 */ {"applytype", [OpcodeArgumentType.UIntLiteral]},
/* 0x54 */ {"0x54", [OpcodeArgumentType.Unknown]},
/* 0x55 */ {"newobject", [OpcodeArgumentType.UIntLiteral]},
@@ -994,12 +997,12 @@ private final class ABCReader
value = readMethodBody();
}
catch (Exception e)
throw new Exception(format("Error at %d (0x%X): %s", pos, pos, e), e);
throw new Exception(format("Error at %d (0x%X):", pos, pos), e);
}
ubyte readU8()
{
assert(pos < buf.length);
enforce(pos < buf.length, "End of file reached");
return buf[pos++];
}
@@ -1047,7 +1050,7 @@ private final class ABCReader
void readExact(void* ptr, size_t len)
{
assert(pos+len <= buf.length);
enforce(pos+len <= buf.length, "End of file reached");
(cast(ubyte*)ptr)[0..len] = buf[pos..pos+len];
pos += len;
}
@@ -1278,6 +1281,7 @@ private final class ABCReader
size_t len = readU30();
uint[] instructionAtOffset = new uint[len];
r.rawBytes = buf[pos..pos+len];
void translateLabel(ref ABCFile.Label label)
{
@@ -1307,23 +1311,25 @@ private final class ABCReader
label.offset = absoluteOffset-instructionOffset;
}
size_t start = pos;
size_t end = pos + len;
uint offset() { return pos - start; }
try
{
size_t start = pos;
size_t end = pos + len;
uint offset() { return pos - start; }
instructionAtOffset[] = uint.max;
uint[] instructionOffsets;
while (pos < end)
{
uint instructionOffset = offset;
scope(failure) pos = start + instructionOffset;
instructionAtOffset[instructionOffset] = r.instructions.length;
ABCFile.Instruction instruction;
instruction.opcode = cast(Opcode)readU8();
instruction.arguments.length = opcodeInfo[instruction.opcode].argumentTypes.length;
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
switch (type)
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to decode OP_" ~ opcodeInfo[instruction.opcode].name);
@@ -1363,9 +1369,6 @@ private final class ABCReader
foreach (ref label; instruction.arguments[i].switchTargets)
label.absoluteOffset = instructionOffset + readS24();
break;
default:
assert(0);
}
r.instructions ~= instruction;
instructionOffsets ~= instructionOffset;
@@ -1390,8 +1393,13 @@ private final class ABCReader
default:
break;
}
pos = end;
}
catch (Exception e)
{
r.instructions = null;
r.error = e.msg;
}
pos = end;
r.exceptions.length = readU30();
foreach (ref value; r.exceptions)
@@ -1473,7 +1481,7 @@ private final class ABCWriter
foreach (ref value; abc.instances)
writeInstance(value);
assert(abc.classes.length == abc.instances.length);
assert(abc.classes.length == abc.instances.length, "Number of classes and instances differs");
foreach (ref value; abc.classes)
writeClass(value);
@@ -1645,7 +1653,7 @@ private final class ABCWriter
}
if (v.flags & MethodFlags.HAS_PARAM_NAMES)
{
assert(v.paramNames.length == v.paramTypes.length);
assert(v.paramNames.length == v.paramTypes.length, "Mismatching number of parameter names and types");
foreach (value; v.paramNames)
writeU30(value);
}
@@ -1755,7 +1763,8 @@ private final class ABCWriter
// we don't know the length before writing all the instructions - swap buffer with a temporary one
auto globalBuf = buf;
auto globalPos = pos;
buf = new ubyte[1024];
static ubyte[1024*16] methodBuf;
buf = methodBuf[];
pos = 0;
struct Fixup { ABCFile.Label target; uint pos, base; }
@@ -1772,7 +1781,7 @@ private final class ABCWriter
throw new Exception("Mismatching number of arguments");
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
switch (type)
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to encode OP_" ~ opcodeInfo[instruction.opcode].name);
@@ -1818,9 +1827,6 @@ private final class ABCWriter
writeS24(0);
}
break;
default:
assert(0);
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
+57 -81
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
@@ -18,10 +18,12 @@
module asprogram;
import std.algorithm;
import core.stdc.string;
import abcfile;
import autodata;
/**
/**
* Represents a hierarchically-organized ActionScript program,
* with all constants expanded and bytecode disassembled to separate instructions.
*/
@@ -41,6 +43,7 @@ final class ASProgram
uint privateIndex; // unique index for private namespaces
mixin AutoCompare;
mixin AutoToString;
mixin ProcessAllData;
}
@@ -183,7 +186,7 @@ final class ASProgram
}
struct Value
{
{
ASType vkind;
union
{
@@ -257,6 +260,9 @@ final class ASProgram
Instruction[] instructions;
Exception[] exceptions;
Trait[] traits;
string error;
ubyte[] rawBytes;
}
struct Instruction
@@ -575,6 +581,8 @@ private final class ABCtoAS
e.varName = multinames[exc.varName];
}
n.traits = convertTraits(vbody.traits);
n.error = vbody.error;
n.rawBytes = vbody.rawBytes;
return n;
}
@@ -585,7 +593,7 @@ private final class ABCtoAS
r.arguments.length = instruction.arguments.length;
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
switch (type)
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to convert OP_" ~ opcodeInfo[instruction.opcode].name);
@@ -633,9 +641,6 @@ private final class ABCtoAS
case OpcodeArgumentType.SwitchTargets:
r.arguments[i].switchTargets = instruction.arguments[i].switchTargets;
break;
default:
assert(0);
}
return r;
}
@@ -723,6 +728,19 @@ private final class AStoABC
return value == T.init;
}
static void move(T)(T[] array, size_t from, size_t to)
{
assert(from<array.length && to<array.length);
if (from == to)
return;
T t = array[from];
if (from < to)
memmove(array.ptr+from, array.ptr+from+1, (to-from)*T.sizeof);
else
memmove(array.ptr+to+1, array.ptr+to, (from-to)*T.sizeof);
array[to] = t;
}
/// Maintain an unordered set of values; sort/index by usage count
struct ConstantPool(T, bool haveNull = true)
{
@@ -750,13 +768,12 @@ private final class AStoABC
bool add(T value) // return true if added
{
enum ivalue = cast(I)value;
if (haveNull && isNull(value))
return false;
auto cp = ivalue in pool;
auto cp = cast(I)value in pool;
if (cp is null)
{
pool[ivalue] = Entry(1, value);
pool[cast(I)value] = Entry(1, value);
return true;
}
else
@@ -768,8 +785,7 @@ private final class AStoABC
bool notAdded(T value)
{
enum ivalue = cast(I)value;
auto ep = ivalue in pool;
auto ep = cast(I)value in pool;
if (ep)
ep.hits++;
return !((haveNull && isNull(value)) || ep);
@@ -790,10 +806,9 @@ private final class AStoABC
uint get(T value)
{
enum ivalue = cast(I)value;
if (haveNull && isNull(value))
return 0;
return pool[ivalue].index;
return pool[cast(I)value].index;
}
}
@@ -807,17 +822,8 @@ private final class AStoABC
uint addIndex, index;
void*[] parents;
int opCmp(Entry* o)
{
if (this.parents.contains(o.object))
return 1;
if (o.parents.contains(this.object))
return -1;
if (o.hits==hits)
return addIndex-o.addIndex;
else
return o.hits-hits;
}
mixin AutoToString;
mixin ProcessAllData;
}
Entry[void*] pool;
@@ -867,61 +873,37 @@ private final class AStoABC
void finalize()
{
// assign unique index
// create array
auto all = new Entry*[pool.length];
int i=0;
foreach (ref e; pool)
e.index = i++;
// topographical sort
bool done;
while (!done)
{
done = true;
foreach (ref a; pool)
foreach (parent; a.parents)
{
auto pb = parent in pool;
assert(pb !is null, "Can't find referenced object");
if (pb.index > a.index)
{
auto t = pb.index;
pb.index = a.index;
a.index = t;
done = false;
break;
}
}
}
// copy to array
auto all = new Entry[i];
foreach (ref e; pool)
all[e.index] = e;
all[i++] = &e;
// sort
//all.sort; // sort preserving topological integrity demands compared items to always be adjacent
done = false;
while (!done)
{
done = true;
sort!q{a.hits > b.hits || (a.hits == b.hits && a.addIndex < b.addIndex)}(all);
for (int j=0; j<all.length-1; j++)
if (all[j] > &all[j+1])
// topographical sort
topSort:
// update indices
foreach (j, e; all)
e.index = j;
foreach (ref a; pool)
foreach (parent; a.parents)
{
auto pb = parent in pool;
assert(pb !is null, "Can't find referenced object");
if (pb.index > a.index)
{
auto t = all[j];
all[j] = all[j+1];
all[j+1] = t;
done = false;
move(all, pb.index, a.index);
goto topSort;
}
}
}
objects.length = i;
foreach (j, ref e; all)
{
pool[e.object].index = j;
foreach (j, e; all)
objects[j] = cast(T)e.object;
}
}
uint get(T obj)
@@ -992,7 +974,7 @@ private final class AStoABC
throw new .Exception("Unknown Multiname kind");
}
bool r = multinames.add(multiname);
assert(r);
assert(r, "Recursive multiname reference");
}
}
@@ -1091,7 +1073,7 @@ private final class AStoABC
visitInstance(vclass.instance);
bool r = classes.add(vclass);
assert(r);
assert(r, "Recursive class reference");
}
}
@@ -1106,7 +1088,7 @@ private final class AStoABC
strings.add(name);
foreach (ref value; options)
visitValue(value);
foreach (name; paramNames)
foreach (name; paramNames)
strings.add(name);
if (vbody)
@@ -1118,7 +1100,7 @@ private final class AStoABC
{
foreach (ref instruction; vbody.instructions)
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
switch (type)
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to visit OP_" ~ opcodeInfo[instruction.opcode].name);
@@ -1157,9 +1139,6 @@ private final class AStoABC
case OpcodeArgumentType.SwitchDefaultTarget:
case OpcodeArgumentType.SwitchTargets:
break;
default:
assert(0);
}
foreach (ref exception; vbody.exceptions)
@@ -1470,7 +1449,7 @@ private final class AStoABC
r.arguments.length = instruction.arguments.length;
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
switch (type)
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to convert OP_" ~ opcodeInfo[instruction.opcode].name);
@@ -1518,9 +1497,6 @@ private final class AStoABC
case OpcodeArgumentType.SwitchTargets:
r.arguments[i].switchTargets = instruction.arguments[i].switchTargets;
break;
default:
assert(0);
}
return r;
}
+20 -38
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
@@ -54,7 +54,7 @@ final class Assembler
{
return fromFile(filename, cast(string)read(filename), arguments);
}
static File fromFile(string filename, string data, string[] arguments = null)
{
return fromData(filename, data, arguments, getDirName(filename));
@@ -75,7 +75,7 @@ final class Assembler
}
string positionStr()
{
{
auto lines = splitlines(buf);
foreach (i, line; lines)
if (pos <= line.ptr + line.length)
@@ -94,7 +94,7 @@ final class Assembler
return file.basePath;
return null;
}
string convertFilename(string filename)
{
if (filename.length == 0)
@@ -127,7 +127,7 @@ final class Assembler
do {} while (*++files[0].pos != '\n');
}
else
return;
return;
}
}
@@ -212,7 +212,7 @@ final class Assembler
static bool isWordChar(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.'; // TODO: use lookup table?
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.';
}
string readWord()
@@ -250,7 +250,7 @@ final class Assembler
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default:
default:
throw new Exception("Malformed hex digit " ~ x);
}
}
@@ -540,8 +540,8 @@ final class Assembler
default: s ~= *files[0].pos;
}
break;
default:
s ~= *files[0].pos;
default:
s ~= *files[0].pos;
}
}
@@ -974,10 +974,10 @@ final class Assembler
auto lp = name in labels;
if (lp is null)
throw new Exception("Unknown label " ~ name);
return ABCFile.Label(*lp, offset);
}
ASProgram.Instruction[] readInstructions(ref uint[string] _labels)
{
ASProgram.Instruction[] instructions;
@@ -1010,7 +1010,7 @@ final class Assembler
instruction.arguments.length = argTypes.length;
foreach (i, type; argTypes)
{
switch (type)
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to assemble OP_" ~ opcodeInfo[instruction.opcode].name);
@@ -1061,9 +1061,6 @@ final class Assembler
foreach (li, s; switchTargetLabels)
switchFixups ~= LocalFixup(files[0].position, instructions.length, i, s, li);
break;
default:
assert(0);
}
if (i < argTypes.length-1)
expectChar(',');
@@ -1074,24 +1071,14 @@ final class Assembler
foreach (ref f; jumpFixups)
{
try
instructions[f.ii].arguments[f.ai].jumpTarget = parseLabel(f.name, labels);
catch (Exception e)
{
setFile(f.where.load);
throw e;
}
scope(failure) setFile(f.where.load);
instructions[f.ii].arguments[f.ai].jumpTarget = parseLabel(f.name, labels);
}
foreach (ref f; switchFixups)
{
try
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = parseLabel(f.name, labels);
catch (Exception e)
{
setFile(f.where.load);
throw e;
}
scope(failure) setFile(f.where.load);
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = parseLabel(f.name, labels);
}
foreach (ref f; localClassFixups)
@@ -1108,13 +1095,8 @@ final class Assembler
ABCFile.Label readLabel()
{
auto word = readWord();
try
return parseLabel(word, labels);
catch (Exception e)
{
backpedal(word.length);
throw e;
}
scope(failure) backpedal(word.length);
return parseLabel(word, labels);
}
ASProgram.Exception e;
@@ -1216,13 +1198,13 @@ final class Assembler
}
catch (Exception e)
{
string s = files[0].positionStr ~ ": " ~ e.msg;
string s = files[0].positionStr ~ ": ";
if (fileCount == -1)
s ~= "\n\t(inclusion context unavailable)";
else
foreach (ref f; files[1..fileCount])
s ~= "\n\t(included from " ~ f.positionStr ~ ")";
throw new Exception(s);
throw new Exception(s, e);
}
classFixups = null;
+8 -8
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
@@ -77,7 +77,7 @@ template AutoToString()
static if (is(typeof(this)==class))
override string toString() { return _AutoDataToString(); }
else // struct
string toString() const { return _AutoDataToString(); }
string toString() { return _AutoDataToString(); }
string _AutoDataToString() const
{
@@ -107,11 +107,8 @@ template RawDataHandlerWrapper()
template getMixinRecursive(T, string name, string loopDepth)
{
static if (!hasAliasing!(T))
enum getMixinRecursive = getRawMixin!("&" ~ name, name ~ ".sizeof");
else
static if (is(T U : U[]))
enum getMixinRecursive =
enum getMixinRecursive =
"{ bool _AutoDataNullTest = " ~ name ~ " is null; " ~ getRawMixin!("&_AutoDataNullTest", "bool.sizeof") ~ "}" ~
(!hasAliasing!(U) ?
getRawMixin!(name ~ ".ptr", name ~ ".length")
@@ -119,7 +116,10 @@ template RawDataHandlerWrapper()
"foreach (ref _AutoDataArrayItem" ~ loopDepth ~ "; " ~ name ~ ") {" ~ getMixinRecursive!(U, "_AutoDataArrayItem" ~ loopDepth, loopDepth~"Item") ~ "}"
);
else
static if (is(typeof((new T).toHash())))
static if (!hasAliasing!(T))
enum getMixinRecursive = getRawMixin!("&" ~ name, name ~ ".sizeof");
else
static if (is(typeof(this)==struct) || is(typeof(this)==class))
enum getMixinRecursive = name ~ ".processData!(void, ``, ``)(handler);";
else
static assert(0, "Don't know how to process type: " ~ T.stringof);
@@ -182,7 +182,7 @@ struct CmpDataHandler(O)
static if (is(typeof(T.opCmp)))
enum dataCode = "{ int _AutoDataCmp = this." ~ name ~ ".opCmp(_AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
else
enum dataCode = "if (this." ~ name ~ " < _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "(-1);" ~
enum dataCode = "if (this." ~ name ~ " < _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "(-1);" ~
"if (this." ~ name ~ " > _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "( 1);";
enum code = arrCode ~ dataCode;
}
+71
View File
@@ -0,0 +1,71 @@
/*
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RABCDAsm 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RABCDAsm. If not, see <http://www.gnu.org/licenses/>.
*/
/// A simple tool to build RABCDAsm in one command.
/// You can use the DC and DCFLAGS environment variables to override the detected compiler and compilation flags.
module build_rabcdasm;
version(D_Version2)
{ /* All OK */ }
else
static assert(false, "Unsupported D version.\nThis software requires a D2 ( http://www.digitalmars.com/d/2.0/ ) compiler to build.");
version(DigitalMars)
const DEFAULT_COMPILER = "dmd";
else
const DEFAULT_COMPILER = "gdmd";
const DEFAULT_FLAGS = "-O -inline";
import std.process;
import std.string;
string[][string] programs;
static this()
{
programs["rabcasm" ] = ["abcfile", "asprogram", "assembler", "autodata", "murmurhash2a"];
programs["rabcdasm" ] = ["abcfile", "asprogram", "disassembler", "autodata", "murmurhash2a"];
programs["abcexport" ] = ["swffile", "zlibx"];
programs["abcreplace" ] = ["swffile", "zlibx"];
programs["swfbinexport" ] = ["swffile", "zlibx"];
programs["swfbinreplace"] = ["swffile", "zlibx"];
programs["swfdecompress"] = ["swffile", "zlibx"];
programs["swf7zcompress"] = ["swffile", "zlibx"];
}
int main()
{
string compiler = getenv("DC");
if (compiler is null)
compiler = DEFAULT_COMPILER;
string flags = getenv("DCFLAGS");
if (flags is null)
flags = DEFAULT_FLAGS;
foreach (program, modules; programs)
{
int ret = system(format("%s %s %s %s", compiler, flags, program, join(modules, " ")));
if (ret)
return ret;
}
return 0;
}
+494 -194
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
@@ -22,8 +22,10 @@ import std.file;
import std.string;
import std.conv;
import std.exception;
import std.algorithm;
import abcfile;
import asprogram;
import autodata;
final class StringBuilder
{
@@ -33,6 +35,8 @@ final class StringBuilder
this(string filename)
{
if (exists(filename))
throw new Exception(filename ~ " exists");
this.filename = filename;
buf.length = 1024;
}
@@ -68,6 +72,7 @@ final class StringBuilder
int indent;
bool indented;
string linePrefix;
void newLine()
{
@@ -87,35 +92,283 @@ final class StringBuilder
for (int i=0; i<indent; i++)
this ~= ' ';
indented = true;
if (linePrefix)
this ~= linePrefix;
}
}
}
final class RefBuilder : ASTraitsVisitor
{
string[void*] objName;
ASProgram.Class[string] classByName;
ASProgram.Method[string] methodByName;
struct ContextItem
{
enum Type { Multiname, String }
Type type;
string[uint] privateNamespaceNames;
uint[string] privateNamespaceByName;
union
{
ASProgram.Multiname multiname;
struct
{
string str;
bool filenameSuffix;
}
}
ASProgram.Multiname[] context;
struct Segment
{
char delim;
string str;
}
Segment[] toSegments(bool filename)
{
final switch(type)
{
case Type.Multiname:
assert(multiname.kind == ASType.QName);
if (multiname.vQName.ns.kind == ASType.PrivateNamespace)
throw new Exception("Stringifying unexpanded context");
auto nsName = multiname.vQName.ns.name;
if (nsName.length)
if (multiname.vQName.name.length)
return [Segment('/', nsName), Segment(filename ? '/' : ':', multiname.vQName.name)];
else
return [Segment('/', nsName)];
else
if (multiname.vQName.name.length)
return [Segment('/', multiname.vQName.name)];
else
assert(0);
case Type.String:
return [Segment(filenameSuffix ? '.' : '/', str)];
}
}
static ContextItem[] expand(RefBuilder refs, ContextItem[] context, bool noThrow) /// recursively expand all private namespaces
{
ContextItem[] newContext;
foreach (ref c; context)
{
auto cEx = c.expand(refs, noThrow);
if (cEx is null) return null;
newContext ~= cEx;
}
return newContext;
}
ContextItem[] expand(RefBuilder refs, bool noThrow)
{
if (type==Type.Multiname && multiname.vQName.ns.kind == ASType.PrivateNamespace)
{
auto pcontext = multiname.vQName.ns.privateIndex in refs.privateNamespaces.contexts;
if (pcontext is null)
if (noThrow)
return null;
else
throw new Exception("Expanding unknown private namespace " ~ to!string(multiname.vQName.ns.privateIndex));
auto expanded = expand(refs, *pcontext, noThrow);
if (expanded is null) return null;
return expanded ~ (multiname.vQName.name.length ? [ContextItem(multiname.vQName.name)] : null); // hack
}
else
return (&this)[0..1];
}
this(ASProgram.Multiname m)
{
this.type = Type.Multiname;
this.multiname = m;
}
this(string s, bool filenameSuffix = false)
{
this.type = Type.String;
this.str = s;
this.filenameSuffix = filenameSuffix;
}
mixin AutoCompare;
mixin AutoToString;
R processData(R, string prolog, string epilog, H)(ref H handler) const
{
mixin(prolog);
mixin(addAutoField("type"));
final switch (type)
{
case Type.Multiname:
mixin(addAutoField("multiname"));
break;
case Type.String:
mixin(addAutoField("str"));
mixin(addAutoField("filenameSuffix"));
break;
}
mixin(epilog);
}
}
ContextItem[] context; // potential optimization: use array-based stack
void pushContext(T...)(T v) { context ~= ContextItem(v); }
void popContext() { context = context[0..$-1]; }
struct ContextSet(T, bool ALLOW_DUPLICATES)
{
ContextItem[][T] contexts;
string[T] names, filenames;
void add(U)(U obj, ContextItem[] context)
{
auto p = cast(T)obj;
static if (ALLOW_DUPLICATES)
{
auto pexisting = p in contexts;
if (pexisting)
{
auto rootContext = contextRoot(*pexisting, context);
enforce(rootContext.length, format("Can't find common private namespace root between ", *pexisting, " and ", context));
contexts[p] = rootContext;
}
else
contexts[p] = context;
}
else
{
enforce(p !in contexts, format("Duplicate object reference: ", contexts[p], " and ", context));
contexts[p] = context.dup;
}
}
void coagulate(RefBuilder refs)
{
int[string] collisionCounter;
T[string] first;
foreach (obj; contexts.keys.sort)
{
auto context = contexts[obj];
auto bname = refs.contextToString(context, false);
auto bfilename = refs.contextToString(context, true);
auto pcounter = bname in collisionCounter;
int counter = pcounter ? *pcounter : 0;
if (counter==1)
{
auto firstObj = first[bname];
names[firstObj] ~= "#0";
filenames[firstObj] ~= "#0";
}
string suffix;
if (counter==0)
first[bname] = cast(T)obj;
else
suffix = '#' ~ to!string(counter);
names[obj] = bname ~ suffix;
filenames[obj] = bfilename ~ suffix;
collisionCounter[bname] = counter+1;
}
}
bool isAdded(U)(U obj) { return (cast(T)obj in contexts) ? true : false; }
ContextItem[] getContext(U)(U obj) { return contexts[cast(T)obj]; }
string getName(U)(U obj)
{
auto pname = cast(T)obj in names;
assert(pname, format("Unscanned object: ", obj));
return *pname;
}
version (Windows)
static string[string] filenameMappings;
string getFilename(U)(U obj)
{
auto pname = cast(T)obj in filenames;
assert(pname, format("Unscanned object: ", obj));
auto filename = *pname;
version (Windows)
{
string[] dirSegments = split(filename, "/");
for (int l=0; l<dirSegments.length; l++)
{
again:
string subpath = join(dirSegments[0..l+1], "/");
string subpathl = tolower(subpath);
string* canonicalp = subpathl in filenameMappings;
if (canonicalp && *canonicalp != subpath)
{
dirSegments[l] = dirSegments[l] ~ "_"; // not ~=
goto again;
}
filenameMappings[subpathl] = subpath;
}
filename = join(dirSegments, "/");
}
return filename ~ ".asasm";
}
}
ContextSet!(uint, true) privateNamespaces;
ContextSet!(void*, false) objects, scripts;
this(ASProgram as)
{
super(as);
}
bool[void*] orphans;
void addOrphan(T)(T obj) { orphans[cast(void*)obj] = true; }
bool isOrphan(T)(T obj) { return (cast(void*)obj in orphans) ? true : false; }
override void run()
{
foreach (i, ref v; as.scripts)
addMethod(v.sinit, "script" ~ to!string(i) ~ "_sinit");
foreach (vclass; as.orphanClasses)
addClass(vclass, "orphan");
foreach (method; as.orphanMethods)
addMethod(method, "orphan");
foreach (i, vclass; as.orphanClasses)
addOrphan(vclass);
foreach (i, method; as.orphanMethods)
addOrphan(method);
super.run();
foreach (i, v; as.scripts)
{
ContextItem[][] classContexts;
foreach (trait; v.traits)
{
auto c = ContextItem.expand(this, [ContextItem(trait.name)], true);
if (c)
classContexts ~= c;
}
context = reduce!contextRoot(new ContextItem[0], classContexts);
if (context.length==0)
pushContext("script_" ~ to!string(i));
scripts.add(v, context);
pushContext("sinit", true);
addMethod(v.sinit);
context = null;
}
foreach (i, vclass; as.orphanClasses)
if (!objects.isAdded(vclass))
{
pushContext("orphan_class_" ~ to!string(i));
addClass(vclass);
popContext();
}
foreach (i, method; as.orphanMethods)
if (!objects.isAdded(method))
{
pushContext("orphan_method_" ~ to!string(i));
addMethod(method);
popContext();
}
privateNamespaces.coagulate(this);
objects.coagulate(this);
scripts.coagulate(this);
}
override void visitTrait(ref ASProgram.Trait trait)
@@ -124,10 +377,9 @@ final class RefBuilder : ASTraitsVisitor
if (m.kind != ASType.QName)
throw new Exception("Trait name is not a QName");
pushContext(m);
visitMultiname(m);
context ~= m;
switch (trait.kind)
{
case TraitKind.Class:
@@ -140,45 +392,86 @@ final class RefBuilder : ASTraitsVisitor
addMethod(trait.vMethod.vmethod);
break;
case TraitKind.Getter:
addMethod(trait.vMethod.vmethod, "getter");
pushContext("getter");
addMethod(trait.vMethod.vmethod);
popContext();
break;
case TraitKind.Setter:
addMethod(trait.vMethod.vmethod, "setter");
pushContext("setter");
addMethod(trait.vMethod.vmethod);
popContext();
break;
default:
break;
}
super.visitTrait(trait);
context = context[0..$-1];
popContext();
}
string addPrivateNamespace(uint index, string bname)
static ContextItem[] contextRoot(ContextItem[] c1, ContextItem[] c2)
{
string name = bname;
static bool uninteresting(ContextItem[] c)
{
int n = 0;
uint* pindex;
while ((pindex = name in privateNamespaceByName) !is null && *pindex != index)
name = bname ~ to!string(++n);
return
(c.length==0) ||
(c.length==1 && c[0].type==ContextItem.Type.String && c[0].str.startsWith("script_") && c[0].str.endsWith("_sinit")) ||
(c.length==1 && c[0].type==ContextItem.Type.String && c[0].str.startsWith("orphan_method_")) ||
false;
}
auto pname = index in privateNamespaceNames;
if (pname)
if (uninteresting(c1)) return c2;
if (uninteresting(c2)) return c1;
static bool nsSimilar(ASProgram.Namespace ns1, ASProgram.Namespace ns2)
{
if (*pname != name)
throw new Exception("Ambiguous private namespace: " ~ *pname ~ " and " ~ name);
if (ns1.kind==ASType.PrivateNamespace || ns2.kind==ASType.PrivateNamespace)
return ns1.kind==ns2.kind && ns1.privateIndex==ns2.privateIndex;
// ignore ns kind in other cases
return ns1.name == ns2.name;
}
else
static bool similar(ref ContextItem i1, ref ContextItem i2)
{
privateNamespaceNames[index] = name;
privateNamespaceByName[name] = index;
if (i1.type != i2.type) return false;
if (i1.type == ContextItem.Type.String)
return i1.str == i2.str;
if (i1.multiname.vQName.name != i2.multiname.vQName.name) return false;
return nsSimilar(i1.multiname.vQName.ns, i2.multiname.vQName.ns);
}
return name;
int i=0;
while (i<c1.length && i<c2.length && similar(c1[i], c2[i])) i++;
auto c = c1[0..i];
if (i<c1.length && i<c2.length && c1[i].type==ContextItem.Type.Multiname && c2[i].type==ContextItem.Type.Multiname && nsSimilar(c1[i].multiname.vQName.ns, c2[i].multiname.vQName.ns) && c1[i].multiname.vQName.ns.name.length)
{
auto m = new ASProgram.Multiname;
m.kind = ASType.QName;
m.vQName.ns = c1[i].multiname.vQName.ns;
c ~= ContextItem(m);
}
return c;
}
void visitNamespace(ASProgram.Namespace ns)
{
if (ns.kind == ASType.PrivateNamespace && context.length>0 && context[0].vQName.ns.kind != ASType.PrivateNamespace)
addPrivateNamespace(ns.privateIndex, qNameToString(context[0]));
if (ns.kind == ASType.PrivateNamespace)
{
assert(context.length > 0, "No context");
//assert(ns.name is null, "Named private namespace");
int myPos = context.length;
foreach (i, ref item; context)
if (item.type == ContextItem.Type.Multiname && item.multiname.vQName.ns == ns)
{
myPos = i;
break;
}
if (myPos == 0)
return;
auto myContext = context[0..myPos].dup;
privateNamespaces.add(ns.privateIndex, myContext);
}
}
void visitNamespaceSet(ASProgram.Namespace[] nsSet)
@@ -226,100 +519,104 @@ final class RefBuilder : ASTraitsVisitor
case OpcodeArgumentType.Multiname:
visitMultiname(instruction.arguments[i].multinamev);
break;
case OpcodeArgumentType.Class:
pushContext("inline_class");
if (isOrphan(instruction.arguments[i].classv))
addClass(instruction.arguments[i].classv);
popContext();
break;
case OpcodeArgumentType.Method:
pushContext("inline_method");
if (isOrphan(instruction.arguments[i].methodv))
addMethod(instruction.arguments[i].methodv);
popContext();
break;
default:
break;
}
}
static string qNameToString(ASProgram.Multiname m)
string contextToString(ContextItem[] context, bool filename)
{
assert(m.kind == ASType.QName);
return (m.vQName.ns.name.length ? m.vQName.ns.name ~ ":" : "") ~ m.vQName.name;
context = ContextItem.expand(this, context, false);
foreach_reverse (i, c; context)
if (i>0 && c==context[i-1])
context = context[0..i] ~ context[i+1..$];
ContextItem.Segment[] segments;
foreach (ci; context)
segments ~= ci.toSegments(filename);
string escape(string s)
{
if (!filename)
return s;
string result;
foreach (c; s)
if (c == '.')
result ~= '/';
else
if (c == ':' || c == '\\' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|' || c < 0x20 || c >= 0x7F || c == ' ' || c == '%')
result ~= format("%%%02X", c);
else
result ~= c;
return result;
}
string[] strings = new string[segments.length];
foreach (i, ref s; segments)
strings[i] = (i>0 ? cast(string)[s.delim] : null) ~ escape(s.str);
return join(strings);
}
string contextToString(string field)
void addObject(T)(T obj) { objects.add(obj, context); }
void addClass(ASProgram.Class vclass)
{
string[] strings = new string[context.length + (field ? 1 : 0)];
foreach (i, m; context)
strings[i] = qNameToString(m);
if (field)
strings[$-1] = field;
char[] s = join(strings, "/").dup;
foreach (ref c; s)
if (c < 0x20 || c == '"')
c = '_';
return assumeUnique(s);
addObject(vclass);
pushContext("cinit");
addMethod(vclass.cinit);
popContext();
pushContext("iinit");
addMethod(vclass.instance.iinit);
popContext();
}
string addObject(T)(T obj, ref T[string] objByName, string field)
void addMethod(ASProgram.Method method)
{
auto name = contextToString(field);
auto uniqueName = name;
int i = 1;
while (uniqueName in objByName)
uniqueName = name ~ "_" ~ to!string(++i);
objByName[uniqueName] = obj;
objName[cast(void*)obj] = uniqueName;
return uniqueName;
}
void addClass(ASProgram.Class vclass, string field = null)
{
addObject(vclass, classByName, field);
addMethod(vclass.cinit, "cinit");
addMethod(vclass.instance.iinit, "iinit");
}
void addMethod(ASProgram.Method method, string field = null)
{
addObject(method, methodByName, field);
addObject(method);
if (method.vbody)
visitMethodBody(method.vbody);
}
string getObjectName(T)(T obj, ref T[string] objByName)
{
auto pname = cast(void*)obj in objName;
if (pname)
return *pname;
else
return addObject(obj, objByName, "orphan");
}
string getClassName(ASProgram.Class vclass)
{
return getObjectName(vclass, classByName);
}
string getMethodName(ASProgram.Method method)
{
return getObjectName(method, methodByName);
}
string getPrivateNamespaceName(uint index)
{
auto pname = index in privateNamespaceNames;
if (pname)
return *pname;
else
//throw new Exception("Nameless private namespace: " ~ to!string(index));
return addPrivateNamespace(index, "OrphanPrivateNamespace");
}
}
final class Disassembler
{
ASProgram as;
string name;
string name, dir;
RefBuilder refs;
version (Windows)
string[string] filenameMappings;
void newInclude(StringBuilder mainsb, string filename, void delegate(StringBuilder) callback)
{
if (mainsb.filename.split("/").length != 2)
throw new Exception("TODO");
StringBuilder sb = new StringBuilder(dir ~ "/" ~ filename);
callback(sb);
sb.save();
this(ASProgram as, string name)
mainsb ~= "#include ";
dumpString(mainsb, filename);
mainsb.newLine();
}
this(ASProgram as, string dir, string name)
{
this.as = as;
this.name = name;
this.dir = dir;
}
void disassemble()
@@ -327,7 +624,7 @@ final class Disassembler
refs = new RefBuilder(as);
refs.run();
StringBuilder sb = new StringBuilder(name ~ "/" ~ name ~ ".main.asasm");
StringBuilder sb = new StringBuilder(dir ~ "/" ~ name ~ ".main.asasm");
sb ~= "#include ";
dumpString(sb, name ~ ".privatens.asasm");
@@ -346,38 +643,38 @@ final class Disassembler
foreach (i, script; as.scripts)
{
dumpScript(sb, script, i);
sb.newLine();
newInclude(sb, refs.scripts.getFilename(script), (StringBuilder sb) {
dumpScript(sb, script, i);
});
}
sb.newLine();
if (as.orphanClasses.length)
{
sb.newLine();
sb ~= "; ===========================================================================";
sb ~= "; ============================= Orphan classes ==============================";
sb.newLine();
sb.newLine();
foreach (i, vclass; as.orphanClasses)
{
sb ~= "class";
dumpClass(sb, vclass);
sb.newLine();
}
newInclude(sb, refs.objects.getFilename(vclass), (StringBuilder sb) {
dumpClass(sb, vclass);
});
sb.newLine();
}
if (as.orphanMethods.length)
{
sb.newLine();
sb ~= "; ===========================================================================";
sb ~= "; ============================= Orphan methods ==============================";
sb.newLine();
sb.newLine();
foreach (i, method; as.orphanMethods)
{
sb ~= "method";
dumpMethod(sb, method);
sb.newLine();
}
newInclude(sb, refs.objects.getFilename(method), (StringBuilder sb) {
dumpMethod(sb, method, "method");
});
sb.newLine();
}
sb.indent--;
@@ -386,12 +683,34 @@ final class Disassembler
sb.save();
// now dump the private namespace indices
sb = new StringBuilder(name ~ "/" ~ name ~ ".privatens.asasm");
uint[] indices = refs.privateNamespaceNames.keys.sort;
sb = new StringBuilder(dir ~ "/" ~ name ~ ".privatens.asasm");
uint[] indices = refs.privateNamespaces.names.keys;
bool alphaSortDelegate(uint a, uint b) { return refs.privateNamespaces.names[a] < refs.privateNamespaces.names[b]; }
sort!alphaSortDelegate(indices);
foreach (index; indices)
{
sb ~= "#privatens " ~ to!string(index) ~ " ";
dumpString(sb, refs.privateNamespaceNames[index]);
void dumpContext(RefBuilder.ContextItem[] context)
{
sb ~= "; ";
foreach (i, c; context)
{
if (c.type == RefBuilder.ContextItem.Type.Multiname)
dumpMultiname(sb, c.multiname);
else
sb ~= c.str;
if (i < context.length-1)
sb ~= " -> ";
}
sb.newLine();
}
auto context = refs.privateNamespaces.contexts[index];
dumpContext(context);
/*auto contextEx = refs.ContextItem.expand(refs, context);
if (context != contextEx)
dumpContext(contextEx);*/
sb ~= format("#privatens %4d ", index);
dumpString(sb, refs.privateNamespaces.names[index]);
sb.newLine();
}
sb.save();
@@ -418,7 +737,15 @@ final class Disassembler
if (v == ABCFile.NULL_DOUBLE)
sb ~= "null";
else
sb ~= format("%.18g", v);
{
string s = format("%.18g", v);
static double forceDouble(double d) { static double n; n = d; return n; }
if (s != "nan" && s != "inf" && s != "-inf")
while (s.length>2 && s[$-1]!='.' && forceDouble(to!double(s[0..$-1]))==v)
s = s[0..$-1];
sb ~= s;
}
}
void dumpString(StringBuilder sb, string str)
@@ -468,7 +795,7 @@ final class Disassembler
if (kind == ASType.PrivateNamespace)
{
sb ~= ", ";
dumpString(sb, refs.getPrivateNamespaceName(privateIndex));
dumpString(sb, refs.privateNamespaces.getName(privateIndex));
}
sb ~= ')';
}
@@ -582,7 +909,6 @@ final class Disassembler
dumpUInt(sb, trait.vClass.slotId);
}
sb.indent++; sb.newLine();
sb ~= "class";
dumpClass(sb, trait.vClass.vclass);
break;
case TraitKind.Function:
@@ -592,8 +918,7 @@ final class Disassembler
dumpUInt(sb, trait.vFunction.slotId);
}
sb.indent++; sb.newLine();
sb ~= "method";
dumpMethod(sb, trait.vFunction.vfunction);
dumpMethod(sb, trait.vFunction.vfunction, "method");
break;
case TraitKind.Method:
case TraitKind.Getter:
@@ -604,8 +929,7 @@ final class Disassembler
dumpUInt(sb, trait.vMethod.dispId);
}
sb.indent++; sb.newLine();
sb ~= "method";
dumpMethod(sb, trait.vMethod.vmethod);
dumpMethod(sb, trait.vMethod.vmethod, "method");
break;
default:
throw new Exception("Unknown trait kind");
@@ -701,8 +1025,9 @@ final class Disassembler
}
}
void dumpMethod(StringBuilder sb, ASProgram.Method method)
void dumpMethod(StringBuilder sb, ASProgram.Method method, string label)
{
sb ~= label;
sb.indent++; sb.newLine();
if (method.name !is null)
{
@@ -710,11 +1035,11 @@ final class Disassembler
dumpString(sb, method.name);
sb.newLine();
}
auto refName = cast(void*)method in refs.objName;
auto refName = refs.objects.getName(method);
if (refName)
{
sb ~= "refid ";
dumpString(sb, *refName);
dumpString(sb, refName);
sb.newLine();
}
foreach (m; method.paramTypes)
@@ -747,64 +1072,23 @@ final class Disassembler
sb.indent--; sb ~= "end ; method"; sb.newLine();
}
string toFileName(string refid)
void dumpClass(StringBuilder sb, ASProgram.Class vclass)
{
char[] buf = refid.dup;
foreach (ref c; buf)
if (c == '.' || c == ':')
c = '/';
else
if (c == '\\' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|')
c = '_';
string filename = assumeUnique(buf);
sb ~= "class"; sb.indent++; sb.newLine();
version (Windows)
{
string[] dirSegments = split(filename, "/");
for (int l=0; l<dirSegments.length; l++)
{
again:
string subpath = join(dirSegments[0..l+1], "/");
string subpathl = tolower(subpath);
string* canonicalp = subpathl in filenameMappings;
if (canonicalp && *canonicalp != subpath)
{
dirSegments[l] = dirSegments[l] ~ "_"; // not ~=
goto again;
}
filenameMappings[subpathl] = subpath;
}
filename = join(dirSegments, "/");
}
return filename ~ ".asasm";
}
void dumpClass(StringBuilder mainsb, ASProgram.Class vclass)
{
if (mainsb.filename.split("/").length != 2)
throw new Exception("TODO: nested classes");
auto refName = cast(void*)vclass in refs.objName;
auto filename = toFileName(refs.getClassName(vclass));
StringBuilder sb = new StringBuilder(name ~ "/" ~ filename);
auto refName = refs.objects.getName(vclass);
if (refName)
{
sb ~= "refid ";
dumpString(sb, *refName);
dumpString(sb, refName);
sb.newLine();
}
sb ~= "instance ";
dumpInstance(sb, vclass.instance);
sb ~= "cinit"; dumpMethod(sb, vclass.cinit);
dumpMethod(sb, vclass.cinit, "cinit");
dumpTraits(sb, vclass.traits);
sb.save();
mainsb.indent++; mainsb.newLine();
mainsb ~= "#include ";
dumpString(mainsb, filename);
mainsb.newLine();
mainsb.indent--; mainsb ~= "end ; class"; mainsb.newLine();
sb.indent--; sb ~= "end ; class"; sb.newLine();
}
void dumpInstance(StringBuilder sb, ASProgram.Instance instance)
@@ -830,7 +1114,7 @@ final class Disassembler
dumpNamespace(sb, instance.protectedNs);
sb.newLine();
}
sb ~= "iinit"; dumpMethod(sb, instance.iinit);
dumpMethod(sb, instance.iinit, "iinit");
dumpTraits(sb, instance.traits);
sb.indent--; sb ~= "end ; instance"; sb.newLine();
}
@@ -840,7 +1124,7 @@ final class Disassembler
sb ~= "script ; ";
sb ~= to!string(index);
sb.indent++; sb.newLine();
sb ~= "sinit"; dumpMethod(sb, script.sinit);
dumpMethod(sb, script.sinit, "sinit");
dumpTraits(sb, script.traits);
sb.indent--; sb ~= "end ; script"; sb.newLine();
}
@@ -864,9 +1148,16 @@ final class Disassembler
sb ~= to!string(label.offset);
}
}
void dumpMethodBody(StringBuilder sb, ASProgram.MethodBody mbody)
{
if (mbody.error)
{
sb ~= "; Error while disassembling method: " ~ mbody.error;
sb.newLine();
sb.linePrefix = "; ";
}
sb ~= "body";
sb.indent++; sb.newLine();
dumpUIntField(sb, "maxstack", mbody.maxStack);
@@ -880,7 +1171,20 @@ final class Disassembler
// reserve exception labels
foreach (ref e; mbody.exceptions)
labels[e.from.index] = labels[e.to.index] = labels[e.target.index] = true;
dumpInstructions(sb, mbody.instructions, labels);
sb.indent++;
if (mbody.error)
foreach (i, b; mbody.rawBytes)
{
sb ~= format("0x%02X", b);
if (i%16==15 || i==mbody.rawBytes.length-1)
sb.newLine();
else
sb ~= " ";
}
else
dumpInstructions(sb, mbody.instructions, labels);
sb.indent--;
sb ~= "end ; code";
sb.newLine();
@@ -901,11 +1205,11 @@ final class Disassembler
}
dumpTraits(sb, mbody.traits);
sb.indent--; sb ~= "end ; body"; sb.newLine();
sb.linePrefix = null;
}
void dumpInstructions(StringBuilder sb, ASProgram.Instruction[] instructions, bool[] labels)
{
sb.indent++;
foreach (ref instruction; instructions)
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
switch (type)
@@ -921,7 +1225,7 @@ final class Disassembler
default:
break;
}
void checkLabel(uint ii)
{
if (labels[ii])
@@ -950,7 +1254,7 @@ final class Disassembler
sb ~= ' ';
foreach (i, type; argTypes)
{
switch (type)
final switch (type)
{
case OpcodeArgumentType.Unknown:
throw new Exception("Don't know how to disassemble OP_" ~ opcodeInfo[instruction.opcode].name);
@@ -984,10 +1288,10 @@ final class Disassembler
dumpMultiname(sb, instruction.arguments[i].multinamev);
break;
case OpcodeArgumentType.Class:
dumpString(sb, refs.getClassName(instruction.arguments[i].classv));
dumpString(sb, refs.objects.getName(instruction.arguments[i].classv));
break;
case OpcodeArgumentType.Method:
dumpString(sb, refs.getMethodName(instruction.arguments[i].methodv));
dumpString(sb, refs.objects.getName(instruction.arguments[i].methodv));
break;
case OpcodeArgumentType.JumpTarget:
@@ -1006,9 +1310,6 @@ final class Disassembler
}
sb ~= ']';
break;
default:
assert(0);
}
if (i < argTypes.length-1)
sb ~= ", ";
@@ -1017,7 +1318,6 @@ final class Disassembler
sb.newLine();
}
checkLabel(instructions.length);
sb.indent--;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
+2 -2
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
@@ -32,7 +32,7 @@ void main(string[] args)
{
scope abc = ABCFile.read(cast(ubyte[])read(arg));
scope as = ASProgram.fromABC(abc);
scope disassembler = new Disassembler(as, getName(getBaseName(arg)));
scope disassembler = new Disassembler(as, getName(arg), getName(getBaseName(arg)));
disassembler.disassemble();
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RABCDAsm 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RABCDAsm. If not, see <http://www.gnu.org/licenses/>.
*/
module swfbinexport;
import std.file;
import std.path;
import std.string;
import std.stdio;
import std.exception;
import swffile;
void main(string[] args)
{
if (args.length == 1)
throw new Exception("No file specified");
foreach (arg; args[1..$])
try
{
scope swf = SWFFile.read(cast(ubyte[])read(arg));
bool found;
foreach (ref tag; swf.tags)
if (tag.type == TagType.DefineBinaryData)
{
found = true;
enforce(tag.data.length >= 6);
ushort id = *cast(short*)tag.data.ptr;
ubyte[] bin = tag.data[6..$];
std.file.write(format("%s-%d.bin", getName(arg), id), bin);
}
enforce(found, "No DefineBinaryData tags found");
}
catch (Exception e)
writefln("Error while processing %s: %s", arg, e);
}
+40
View File
@@ -0,0 +1,40 @@
/*
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RABCDAsm 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RABCDAsm. If not, see <http://www.gnu.org/licenses/>.
*/
module swfbinreplace;
import std.file;
import std.conv;
import swffile;
void main(string[] args)
{
if (args.length != 4)
throw new Exception("Bad arguments. Usage: swfbinreplace file.swf id data.bin");
auto swf = SWFFile.read(cast(ubyte[])read(args[1]));
auto id = to!ushort(args[2]);
foreach (ref tag; swf.tags)
if (tag.type == TagType.DefineBinaryData && tag.data.length >= 6 && *cast(short*)tag.data.ptr == id)
{
auto bin = cast(ubyte[])read(args[3]);
tag.data = tag.data[0..6] ~ bin;
write(args[1], swf.write());
return;
}
throw new Exception("DefineBinaryData tag with specified ID not found in file");
}
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
+4 -4
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
* This file is part of RABCDAsm.
*
* RABCDAsm is free software: you can redistribute it and/or modify
@@ -21,8 +21,8 @@ module swffile;
import std.zlib;
import zlibx;
/**
* Implements a shallow representation of a .swf file.
/**
* Implements a shallow representation of a .swf file.
* Loading and saving a .swf file using this class should produce
* output identical to the input (aside zlib compression differences).
*/
@@ -230,7 +230,7 @@ private final class SWFWriter
buf ~= swf.frameSize.bytes;
buf ~= toArray(swf.frameRate);
buf ~= toArray(swf.frameCount);
foreach (ref tag; swf.tags)
{
ushort u = cast(ushort)(tag.type << 6);
+2 -2
View File
@@ -13,7 +13,7 @@ ubyte[] exactUncompress(ubyte[] srcbuf, size_t destlen)
auto destbuf = new ubyte[destlen];
uint err;
zs.next_in = srcbuf.ptr;
zs.avail_in = srcbuf.length;
@@ -37,7 +37,7 @@ ubyte[] exactUncompress(ubyte[] srcbuf, size_t destlen)
}
if (zs.avail_out != 0)
throw new Exception("Too little data in stream");
err = etc.c.zlib.inflateEnd(&zs);
if (err != Z_OK)
goto Lerr;