30 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
18 changed files with 782 additions and 293 deletions
+28 -1
View File
@@ -1,6 +1,34 @@
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)
--------------------------
@@ -42,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 -O -inline rabcdasm abcfile asprogram disassembler autodata murmurhash2a
dmd -O -inline rabcasm abcfile asprogram assembler autodata murmurhash2a
dmd -O -inline abcexport swffile zlibx
dmd -O -inline abcreplace swffile zlibx
dmd -O -inline swfdecompress swffile zlibx
dmd -O -inline 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
+23 -18
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"];
@@ -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)
@@ -1773,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);
@@ -1819,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
+17 -23
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
@@ -23,7 +23,7 @@ import core.stdc.string;
import abcfile;
import autodata;
/**
/**
* Represents a hierarchically-organized ActionScript program,
* with all constants expanded and bytecode disassembled to separate instructions.
*/
@@ -43,6 +43,7 @@ final class ASProgram
uint privateIndex; // unique index for private namespaces
mixin AutoCompare;
mixin AutoToString;
mixin ProcessAllData;
}
@@ -185,7 +186,7 @@ final class ASProgram
}
struct Value
{
{
ASType vkind;
union
{
@@ -259,6 +260,9 @@ final class ASProgram
Instruction[] instructions;
Exception[] exceptions;
Trait[] traits;
string error;
ubyte[] rawBytes;
}
struct Instruction
@@ -577,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;
}
@@ -587,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);
@@ -635,9 +641,6 @@ private final class ABCtoAS
case OpcodeArgumentType.SwitchTargets:
r.arguments[i].switchTargets = instruction.arguments[i].switchTargets;
break;
default:
assert(0);
}
return r;
}
@@ -765,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
@@ -783,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);
@@ -805,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;
}
}
@@ -1088,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)
@@ -1100,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);
@@ -1139,9 +1139,6 @@ private final class AStoABC
case OpcodeArgumentType.SwitchDefaultTarget:
case OpcodeArgumentType.SwitchTargets:
break;
default:
assert(0);
}
foreach (ref exception; vbody.exceptions)
@@ -1452,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);
@@ -1500,9 +1497,6 @@ private final class AStoABC
case OpcodeArgumentType.SwitchTargets:
r.arguments[i].switchTargets = instruction.arguments[i].switchTargets;
break;
default:
assert(0);
}
return r;
}
+11 -14
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;
}
}
@@ -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(',');
+5 -5
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
{
@@ -108,7 +108,7 @@ template RawDataHandlerWrapper()
template getMixinRecursive(T, string name, string loopDepth)
{
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 +119,7 @@ template RawDataHandlerWrapper()
static if (!hasAliasing!(T))
enum getMixinRecursive = getRawMixin!("&" ~ name, name ~ ".sizeof");
else
static if (is(typeof((new T).toHash())))
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;