Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e49a6b3fec | ||
|
|
bfa02943c6 | ||
|
|
cf8835b87d | ||
|
|
4c411ac1f2 | ||
|
|
76cadf30cf | ||
|
|
2498673231 | ||
|
|
a1b9ea05c0 | ||
|
|
a766cf5919 | ||
|
|
e6f54db4c1 | ||
|
|
51e82bf985 | ||
|
|
282e8cbfc1 | ||
|
|
4f53b789ee | ||
|
|
0c5cbde348 | ||
|
|
ff799d002f | ||
|
|
1bf7a9a593 | ||
|
|
1167b14471 | ||
|
|
d3e8e47d54 | ||
|
|
3f9be2b192 | ||
|
|
a690c82b7d | ||
|
|
2d7886bb0b | ||
|
|
4cb179a1c6 | ||
|
|
0031be1f75 | ||
|
|
c7b7a5bce8 | ||
|
|
ac5d4b5e74 | ||
|
|
eb07838668 | ||
|
|
b80fe48bee | ||
|
|
a083ed265f | ||
|
|
986ea47d03 | ||
|
|
3520d34cc2 | ||
|
|
58ab6e3bf0 | ||
|
|
413aa9b5f0 | ||
|
|
5da1e26835 | ||
|
|
53a4173fd3 | ||
|
|
5737243606 | ||
|
|
be727e67c6 | ||
|
|
9d67c46835 | ||
|
|
756a2fd060 | ||
|
|
8ad1f85a45 | ||
|
|
ad6ae5d087 | ||
|
|
580a1364d0 | ||
|
|
d4f90f9c01 |
+36
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user