Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68f6fa241c | ||
|
|
77b5366ada | ||
|
|
59a0b2c167 | ||
|
|
9bd1c698b5 | ||
|
|
e01cff3f24 | ||
|
|
988b17a401 | ||
|
|
3ed3fb1a27 | ||
|
|
1e7cbaca9b | ||
|
|
9b6dfe13b3 | ||
|
|
dc01be0604 | ||
|
|
5e7b2ea4e7 | ||
|
|
8ab69fc6b7 | ||
|
|
576294f949 | ||
|
|
21650d2056 | ||
|
|
0983145a0d | ||
|
|
62c8fce63b | ||
|
|
7097774834 | ||
|
|
fe93f5e764 | ||
|
|
0fbfba1186 | ||
|
|
6bdf6dae39 | ||
|
|
62ef183d95 | ||
|
|
f6eb080be7 | ||
|
|
a17a03e6f6 | ||
|
|
0486c1a54a | ||
|
|
a2d22e7606 | ||
|
|
6568793aaf | ||
|
|
b0bdb250c5 | ||
|
|
99e49a9385 | ||
|
|
6313739b5e | ||
|
|
8ed3295199 | ||
|
|
31ad8dcef7 | ||
|
|
ef8e86388a | ||
|
|
d4f7a070b6 | ||
|
|
e5e01ea90f | ||
|
|
fbebd86f47 | ||
|
|
e9fbbef9b7 | ||
|
|
9c28d20e99 | ||
|
|
3449822efd | ||
|
|
3ded602166 | ||
|
|
08008d5643 | ||
|
|
98e9198c36 | ||
|
|
f6dbbf08ee | ||
|
|
4251e409a2 | ||
|
|
7ad6949b45 | ||
|
|
9bd472e4f8 | ||
|
|
efada3d5c1 | ||
|
|
05d9624f68 | ||
|
|
a2ea734202 | ||
|
|
32909f3893 | ||
|
|
472a9cec57 | ||
|
|
6cbb185114 | ||
|
|
f32f6fb128 | ||
|
|
d6d7ecf47e | ||
|
|
0cefc587e6 | ||
|
|
a45b4e2bdc | ||
|
|
e383172765 | ||
|
|
c62b5438d7 | ||
|
|
a1a214ae72 | ||
|
|
222fa1109c | ||
|
|
9fa2d122fa | ||
|
|
93adc953c2 | ||
|
|
dc222f9e43 | ||
|
|
fb2f96b81a | ||
|
|
14f5e669ee | ||
|
|
4e3d43df5f | ||
|
|
c1d6b29d94 | ||
|
|
8fc7253bba | ||
|
|
ac2bde2550 | ||
|
|
99dfd797ba | ||
|
|
f4824cd3b4 | ||
|
|
87047b920c | ||
|
|
f17e97848b | ||
|
|
336665a213 | ||
|
|
fdb8489382 | ||
|
|
afe42b5d1f | ||
|
|
3a9829467f | ||
|
|
8e969d9e76 | ||
|
|
d755065785 | ||
|
|
ea3dfd30b1 | ||
|
|
86a4a2e0b8 | ||
|
|
704045594a | ||
|
|
5b4c31d2a0 | ||
|
|
a56d074dad | ||
|
|
d722bdcb3c | ||
|
|
ef9e768b07 | ||
|
|
d321be2b25 | ||
|
|
6543d17fc4 | ||
|
|
e20332aeba | ||
|
|
d77e2b679e | ||
|
|
9d0d0355e5 | ||
|
|
5795d5f374 | ||
|
|
8e8d204269 | ||
|
|
20f416d9d8 | ||
|
|
e3634e59cd | ||
|
|
3c5101f6c0 | ||
|
|
c2042660f2 | ||
|
|
57d05c6c35 | ||
|
|
532700df03 | ||
|
|
fe9b49bfd3 | ||
|
|
e7b65d6d50 | ||
|
|
a304843b5b | ||
|
|
ebe6639e8a | ||
|
|
09be72ea9c | ||
|
|
151bf87038 |
+17
@@ -0,0 +1,17 @@
|
||||
*.exe
|
||||
*.dll
|
||||
*.pdb
|
||||
*.ilk
|
||||
*.exp
|
||||
*.lib
|
||||
*.suo
|
||||
|
||||
/rabcasm
|
||||
/rabcdasm
|
||||
/abcexport
|
||||
/abcreplace
|
||||
/swfbinexport
|
||||
/swfbinreplace
|
||||
/swfdecompress
|
||||
/swf7zcompress
|
||||
/swflzmacompress
|
||||
+95
-13
@@ -1,6 +1,88 @@
|
||||
RABCDAsm Changelog
|
||||
==================
|
||||
|
||||
RABCDAsm v1.18 (2016.01.16)
|
||||
---------------------------
|
||||
|
||||
* Fix disassembly of `pushbyte` instructions (the AVM specification
|
||||
incorrectly lists the argument as unsigned).
|
||||
* Bump `#version` directive of new disassemblies to 4:
|
||||
- Versions below 4 treat `pushbyte` as unsigned, and throw an exception if
|
||||
the argument is outside the range `0` ... `255`.
|
||||
- Versions 4 and above treat `pushbyte` as signed, and throw an exception
|
||||
if the argument is outside the range `-128` ... `127`.
|
||||
* Fix buffer reuse bugs when using macros
|
||||
* Dump floating-point numbers in hex notation if necessary to ensure precision
|
||||
* Cease emitting a number in a comment after opening `script` tags
|
||||
* Detect a known DMD bug in `build_rabcdasm`
|
||||
* Ignore invalid file size in header
|
||||
|
||||
RABCDAsm v1.17 (2014.09.10)
|
||||
---------------------------
|
||||
|
||||
* Do not attempt to disassemble unreachable code
|
||||
* Improve handling of disassembly errors:
|
||||
methods will be partially disassembled as far as possible.
|
||||
* Fix LZMA errors with uncompressable data.
|
||||
|
||||
RABCDAsm v1.16 (2014.04.21)
|
||||
---------------------------
|
||||
|
||||
* Fix handling of TypeName-kind Multinames with null parameters
|
||||
* Fix v1.15 regression in handling very long paths on Windows
|
||||
(DMD 2.066 is required when building from source for this to work)
|
||||
|
||||
RABCDAsm v1.15 (2014.01.11)
|
||||
---------------------------
|
||||
|
||||
* Fix building on systems with a noexec `/tmp/`
|
||||
* Improve compatibility with 3rd-party players
|
||||
* Don't emit forward references in TypeName-kind Multinames
|
||||
* Improve performance and memory usage
|
||||
|
||||
RABCDAsm v1.14 (2013.08.21)
|
||||
---------------------------
|
||||
|
||||
* Improved refid generation
|
||||
* Worked around liblzma dictionary size limitation, which prevented
|
||||
decompression of some LZMA-compressed files
|
||||
* Added an option to `swflzmacompress` to update the SWF version number
|
||||
* Added `finddef` instruction
|
||||
* Added unimplemented `{get|set|delete}propertylate` instructions
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.13 (2012.09.29)
|
||||
---------------------------
|
||||
|
||||
* Fixed handling of truncated SWF tags
|
||||
|
||||
RABCDAsm v1.12 (2012.09.08)
|
||||
---------------------------
|
||||
|
||||
* Relaxed filename sanitization for names like "controller"
|
||||
* Added range validation for byte literals
|
||||
* Fixed LZMA support misdetection caused by stale rdmd cache
|
||||
* Added coerce_b, coerce_i and coerce_d opcodes
|
||||
* Fixed handling of recursively-nested function literals
|
||||
* Fixed handling of homonym non-private namespaces
|
||||
* #privatens directives are now ignored, and no longer generated
|
||||
* Improved refid generation
|
||||
* Bumped #version to 3 (v1.12 disassemblies are not backwards-compatible)
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.11 (2012.03.15)
|
||||
---------------------------
|
||||
|
||||
* Fixed v1.10 hierarchy flattening regression
|
||||
* Improved refid generation for protected namespaces
|
||||
* Improved handling of overflown signed integers
|
||||
* Fixed metadata handling (caused by error in ABC format specification)
|
||||
* Added #version directive (v1.11 disassemblies are not backwards-compatible)
|
||||
* Removed path length limitation on Windows
|
||||
* Improved filename sanitization
|
||||
* Updated asasm.hrc
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.10 (2012.02.29)
|
||||
---------------------------
|
||||
|
||||
@@ -40,27 +122,27 @@ 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* Inline methods are now in separate files, placed according to their usage
|
||||
context
|
||||
* Private namespaces are sorted by their autogenerated names in the
|
||||
* 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
|
||||
* `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
|
||||
* 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
|
||||
|
||||
@@ -77,7 +159,7 @@ RABCDAsm v1.4 (2011.03.07)
|
||||
|
||||
* Source code ported to D2
|
||||
* Add support for forward-references for TypeName-kind Multinames
|
||||
* Correctly order classes by dependencies (extends/implements) and reference
|
||||
* Correctly order classes by dependencies (extends/implements) and reference
|
||||
count
|
||||
* Finish Metadata support
|
||||
* Documentation updates
|
||||
@@ -86,7 +168,7 @@ RABCDAsm v1.3 (2010.11.11)
|
||||
--------------------------
|
||||
|
||||
* Fixed double precision problem
|
||||
* This also fixes problems with illegal default values for function
|
||||
* This also fixes problems with illegal default values for function
|
||||
parameters (default values for integer parameters are stored as doubles,
|
||||
which might become out-of-range due to inadequate double precision)
|
||||
* Added Changelog
|
||||
@@ -95,8 +177,8 @@ RABCDAsm v1.3 (2010.11.11)
|
||||
RABCDAsm v1.2 (2010.11.06)
|
||||
--------------------------
|
||||
|
||||
* Fixed ref generation for orphan objects which were only referenced
|
||||
by other orphans
|
||||
* Fixed ref generation for orphan objects which were only referenced by other
|
||||
orphans
|
||||
* Better error handling in `abcexport`; warn when no DoABC tags found
|
||||
* Documentation updates
|
||||
|
||||
@@ -106,7 +188,7 @@ 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
|
||||
* Fixed relative include paths
|
||||
* Add optional byte offsets to labels, which allows lossless representation
|
||||
* Add optional byte offsets to labels, which allows lossless representation
|
||||
of jumps inside instructions and outside the function bounds
|
||||
* Documentation updates
|
||||
|
||||
|
||||
@@ -1,450 +1,459 @@
|
||||
Robust ABC (ActionScript Bytecode) [Dis-]Assembler
|
||||
==================================================
|
||||
|
||||
[RABCDAsm][] is a collection of utilities including an ActionScript 3
|
||||
assembler/disassembler, and a few tools to manipulate SWF files.
|
||||
These are:
|
||||
|
||||
* `rabcdasm` - ABC disassembler
|
||||
* `rabcasm` - ABC assembler
|
||||
* `abcexport` - extracts ABC from SWF files
|
||||
* `abcreplace` - replaces ABC in SWF files
|
||||
* `swfdecompress` - decompresses zlib-compressed SWF files
|
||||
* `swf7zcompress` - (re-)compress the contents of a SWF using 7-Zip
|
||||
* `swflzmacompress` - compress the contents of a SWF using LZMA
|
||||
* `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
|
||||
the SWF files as deeply.
|
||||
`swfdecompress` is ancillary and is only useful for debugging and studying of
|
||||
the SWF file format, and not required for ABC manipulation. It is functionally
|
||||
equivalent to [flasm][]'s `-x` option. If you frequently work on compressed
|
||||
SWF files, you may want to decompress them to speed processing up.
|
||||
`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`.
|
||||
`swflzmacompress` compresses SWF files using the [LZMA][] algorithm, support
|
||||
for which was introduced in Flash 11. It will only work with SWF files with
|
||||
version 13 or higher.
|
||||
`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
|
||||
[flasm]: http://flasm.sourceforge.net/
|
||||
[7-Zip]: http://www.7-zip.org/
|
||||
[LZMA]: http://en.wikipedia.org/wiki/Lempel-Ziv-Markov_chain_algorithm
|
||||
|
||||
Motivation and goals
|
||||
--------------------
|
||||
|
||||
This package was created due to lack of similar software out there.
|
||||
Particularly, I needed an utility which would allow me to edit ActionScript 3
|
||||
bytecode with the following properties:
|
||||
|
||||
1. Speed. Less waiting means more productivity. `rabcasm` can assemble large
|
||||
projects (>200000 LOC) in under a second on modern machines.
|
||||
2. Comfortably-editable output. Each class is decompiled to its own file, with
|
||||
files arranged in subdirectories representing the package hierarchy. Class
|
||||
files are `#include`d from the main file.
|
||||
3. Most importantly - robustness! If the Adobe AVM can load and run the file,
|
||||
then it must be editable - no matter if the file is obfuscated or otherwise
|
||||
mutilated to prevent reverse-engineering. RABCDAsm achieves this by using
|
||||
a textual representation closer to the ABC file format, rather than to what
|
||||
an ActionScript compiler would generate.
|
||||
|
||||
Compiling from source
|
||||
---------------------
|
||||
|
||||
RABCDAsm is written in the [D programming language, version 2][d2].
|
||||
|
||||
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 -run build_rabcdasm.d
|
||||
|
||||
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 (`-w -O -inline`).
|
||||
|
||||
To be able to manipulate SWF files packed with LZMA compression, you'll need to
|
||||
have the liblzma library and development files installed on your system.
|
||||
|
||||
[d2]: http://dlang.org/
|
||||
[dmd]: http://www.digitalmars.com/d/download.html
|
||||
[gdc]: http://bitbucket.org/goshawk/gdc/
|
||||
[git]: http://git-scm.com/
|
||||
|
||||
Pre-compiled binaries
|
||||
---------------------
|
||||
|
||||
You can find pre-compiled Windows binaries in the [Downloads section on
|
||||
GitHub][downloads]. However, please don't expect them to be up-to-date
|
||||
with the latest source versions.
|
||||
|
||||
[downloads]: http://github.com/CyberShadow/RABCDAsm/downloads
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To begin hacking on a SWF file:
|
||||
|
||||
abcexport file.swf
|
||||
|
||||
This will create `file-0.abc` ... `file-N.abc` (often just `file-0.abc`). Each
|
||||
file corresponds to an ABC block inside the SWF file.
|
||||
|
||||
To disassemble one of the `.abc` files:
|
||||
|
||||
rabcdasm file-0.abc
|
||||
|
||||
This will create a `file-0` directory, which will contain `file-0.main.asasm`
|
||||
(the main program file), `file-0.privatens.asasm` (private namespace alias
|
||||
definitions), and files for ActionScript scripts, classes, and orphan and
|
||||
script-level methods.
|
||||
|
||||
To assemble the `.asasm` files back, and update the SWF file:
|
||||
|
||||
rabcasm file-0/file-0.main.asasm
|
||||
abcreplace file.swf 0 file-0/file-0.main.abc
|
||||
|
||||
The second `abcreplace` argument represents the index of the ABC block in the
|
||||
SWF file, and corresponds to the number in the filename created by `abcexport`.
|
||||
|
||||
`swfbinexport` and `swfbinreplace` are used in the same manner as `abcexport`
|
||||
and `abcreplace`.
|
||||
|
||||
Syntax
|
||||
======
|
||||
|
||||
The syntax of the disassembly was designed to be very simple and allow fast
|
||||
and easy parsing. It is a close representation of the `.abc` file format, and
|
||||
thus it is somewhat verbose. All constant pool elements (signed/unsigned
|
||||
integers, doubles, strings, namespaces, namespace sets, multinames) are always
|
||||
*expanded inline*, for ease of editing. Similarly, classes, instances, methods
|
||||
and method bodies are also defined inline, in the context of their "parent"
|
||||
object. By-index references of classes and methods (used in the `newclass`,
|
||||
`newfunction` and `callstatic` instructions) are represented via
|
||||
automatically-generated unique "reference strings", declared as `refid` fields.
|
||||
|
||||
If you haven't yet, I strongly recommend that you look through Adobe's
|
||||
[ActionScript Virtual Machine 2 (AVM2) Overview][avm2]. You will most likely
|
||||
need to consult it for the instruction reference anyway (although you can also
|
||||
use [this handy list][avm2i] as well). You will find it difficult to understand
|
||||
the disassembly without good understanding of concepts such as namespaces and
|
||||
multinames.
|
||||
|
||||
[avm2]: http://www.adobe.com/devnet-archive/actionscript/articles/avm2overview.pdf
|
||||
[avm2i]: http://www.anotherbigidea.com/javaswf/avm2/AVM2Instructions.html
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
In order to guarantee unambiguity and data preservation, all strings read from
|
||||
the input file - including identifiers (variable/function/class names) - are
|
||||
represented as string literals. Thus, the syntax does not have any "reserved
|
||||
words" or such - an unrecognized word is treated as an error, not as an
|
||||
identifier.
|
||||
|
||||
Whitespace (outside string literals, of course) is completely ignored, except
|
||||
where required to separate words. Comments are Intel-assembler-style: a single
|
||||
`;` demarks a comment until the next end-of-line. Control directives (such as
|
||||
`#include`) are allowed anywhere where whitespace is allowed.
|
||||
|
||||
The syntax is comprised of hierarchical blocks. Each block contains a number
|
||||
of fields - starting with a keyword specifying the field type. A block is
|
||||
terminated with the `end` keyword. Some fields contain a limited number of
|
||||
parameters, and others are, or contain blocks.
|
||||
|
||||
Hierarchy
|
||||
---------
|
||||
|
||||
The topmost block in the hierarchy is the `program` block. This must be the
|
||||
first block in the file (thus, `program` must be the first word in the file as
|
||||
well). The `program` block contains `script` fields, and `class` / `method`
|
||||
fields for "orphan" classes and methods (not owned by other objects in the
|
||||
hierarchy). Orphan methods are usually anonymous functions. The file version is
|
||||
also specified in the `program` block, using the `minorversion` and
|
||||
`majorversion` fields (both unsigned integers).
|
||||
|
||||
`script` blocks have one mandatory `sinit` field (the script initialization
|
||||
method) and `trait` fields.
|
||||
|
||||
A "trait" can be one of several kinds. The kind is specified right after the
|
||||
`trait` keyword, followed by the trait name (a multiname). Following the name
|
||||
are the trait fields, varying by trait kind:
|
||||
|
||||
* `slot` / `const` : `slotid` (unsigned integer), `type` (multiname), `value`
|
||||
* `class` : `slotid`, `class` (the actual class block)
|
||||
* `function` : `slotid`, `method` (the actual method block)
|
||||
* `method` / `getter` / `setter` : `dispid` (unsigned integer), `method`
|
||||
|
||||
Additionally, all traits may have `flag` fields, describing the trait's
|
||||
attributes (`FINAL` / `OVERRIDE` / `METADATA`), and `metadata` blocks.
|
||||
|
||||
`metadata` blocks (which are ignored by the AVM) consist of a name string, and
|
||||
a series of `item` fields - each item having a key and value string.
|
||||
|
||||
`class` blocks have mandatory `instance` and `cinit` fields, defining the class
|
||||
instance and the class initializer method respectively. They may also have
|
||||
`trait` fields and a `refid` field (the `refid` field is not part of the file
|
||||
format - it's an unique string to allow referencing the class, see above).
|
||||
|
||||
`instance` blocks - always declared inline of their `class` block - must
|
||||
contain one `iinit` field (the instance initializer method), and may contain
|
||||
one `extends` field (multiname), `implements` fields (multinames), `flag`
|
||||
fields (`SEALED` / `FINAL` / `INTERFACE` / `PROTECTEDNS`), one `protectedns`
|
||||
field (namespace), and `trait` fields.
|
||||
|
||||
`method` blocks may contain one `name` field (multiname), a `refid` field,
|
||||
`param` fields (multinames - this represents the parameter types), one
|
||||
`returns` field (multiname), `flag` fields (`NEED_ARGUMENTS` /
|
||||
`NEED_ACTIVATION` / `NEED_REST` / `HAS_OPTIONAL` / `SET_DXNS` /
|
||||
`HAS_PARAM_NAMES`), `optional` fields (values), `paramname` fields (strings),
|
||||
and a `body` field (method body).
|
||||
|
||||
`body` blocks - always declared inline of their `method` block - must contain
|
||||
the `maxstack`, `localcount`, `initscopedepth` and `maxscopedepth` fields
|
||||
(unsigned integers), and a `code` field. It may also contain `try` and `trait`
|
||||
fields.
|
||||
|
||||
`code` blocks - always declared inline of their `body` block - are somewhat
|
||||
different in syntax from other blocks - mostly in that they may contain labels.
|
||||
Labels follow the most common syntax - a word followed by a `:` character,
|
||||
optionally followed by a relative byte offset (in case of pointers inside
|
||||
instructions). Multiple instruction arguments are comma-separated. Instruction
|
||||
arguments' types depend on the instruction - see the `OpcodeInfo` array in
|
||||
`abcfile.d` for a reference.
|
||||
|
||||
`try` blocks - always declared inline of their `body` block - represent an
|
||||
"exception" (try/catch) block. They contain five mandatory fields: `from`,
|
||||
`to` and `target` (names of labels representing start and end of the "try"
|
||||
block, and start of the "catch" block respectively), and `type` and `name`
|
||||
(multinames), representing the type and name of the exception variable.
|
||||
|
||||
Values have the syntax *type* `(` *value* `)` . *type* can be one of `Integer`,
|
||||
`UInteger`, `Double`, `Utf8`, `Namespace`, `PackageNamespace`,
|
||||
`PackageInternalNs`, `ProtectedNamespace`, `ExplicitNamespace`,
|
||||
`StaticProtectedNs`, `PrivateNamespace`, `True`, `False`, `Null` or
|
||||
`Undefined`. The type of the value depends on *type*. Types `True`, `False`,
|
||||
`Null` and `Undefined` have no value.
|
||||
|
||||
Constants
|
||||
---------
|
||||
|
||||
Multinames have the syntax *type* `(` *parameters* `)` . *type* can be one of
|
||||
`QName` / `QNameA`, `RTQName` / `RTQNameA`, `RTQNameL` / `RTQNameLA`,
|
||||
`Multiname` / `MultinameA`, `MultinameL` / `MultinameLA`, or `TypeName`.
|
||||
*parameters* depends on *type*:
|
||||
|
||||
* `QName` / `QNameA` `(` *namespace* `,` *string* `)`
|
||||
* `RTQName` / `RTQNameA` `(` *string* `)`
|
||||
* `RTQNameL` / `RTQNameLA` `(` `)`
|
||||
* `Multiname` / `MultinameA` `(` *string* `,` *namespace-set* `)`
|
||||
* `MultinameL` / `MultinameLA` `(` *namespace-set* `)`
|
||||
* `TypeName` `(` *multiname* `<` *multiname [* `,` *multiname ... ]* `>` `)`
|
||||
|
||||
Namespace sets have the syntax `[` *[ namespace [* `,` *namespace ... ] ]* `]`
|
||||
(that is, a comma-separated list of namespaces in square brackets). Empty
|
||||
namespace sets can be specified using `[]`.
|
||||
|
||||
Namespaces have the syntax *type* `(` *parameters* `)` . For types other than
|
||||
`PrivateNamespace` there is only one parameter - a string. `PrivateNamespace`
|
||||
namespaces have a second parameter, a named alias for a particular private
|
||||
namespace. Internally (the ABC file format), private namespaces are
|
||||
distinguished by a numerical index - `rabcdasm` will attempt to give them
|
||||
descriptive names based on their context. Aliases can be defined using the
|
||||
`#privatens` directive. `rabcdasm` will create a separate file containing the
|
||||
aliases (`file-0.privatens.asasm`).
|
||||
|
||||
Strings have a syntax similar to C string literals. Strings start and end with
|
||||
a `"`. Supported escape sequences (a backslash followed by a letter) are `\n`
|
||||
(generates ASCII 0x0A), `\r` (ASCII 0x0D), and `\x` followed by two hexadecimal
|
||||
digits, which inserts the ASCII character with that code. Any other characters
|
||||
following a backslash generate that character - thus, you can escape
|
||||
backslashes using `\\` and double quotes using `\"`. When decompiling,
|
||||
high-ASCII characters (usually UTF-8) are not escaped - if you see gibberish
|
||||
instead of international text, configure your editor to open the files in UTF-8
|
||||
encoding.
|
||||
|
||||
Additionally, constant pool types (signed/unsigned integers, doubles, strings,
|
||||
namespaces, namespace sets and multinames) may also have the value `null`
|
||||
(which represents the index 0 in the ABC file). Note that `null` is
|
||||
conceptually different from zero, an empty string or empty namespace set.
|
||||
|
||||
Macros
|
||||
------
|
||||
|
||||
RABCDAsm has some basic macro-like capabilities, controlled by directives and
|
||||
variables. These bear some similarity to the C preprocessor, however these
|
||||
are processed in-loop rather than as a separate pre-processing step.
|
||||
|
||||
### Directives
|
||||
|
||||
Directives start with a `#`, followed by a word identifying the directive:
|
||||
|
||||
* `#include` *string* - inserts the contents of the file by the specified
|
||||
filename inline. Functionally equivalent to `#mixin #get` *string* , but
|
||||
faster.
|
||||
* `#mixin` *string* - inserts the contents of the specified string inline.
|
||||
Not very useful on its own.
|
||||
* `#call` *string* `(` *[ string [* `,` *string ... ] ]* `)` - same as
|
||||
`#mixin`, however it additionally sets the special variables `$1`, `$2`
|
||||
etc. to the contents of the specified arguments. When the end of the
|
||||
inserted string is reached, the old values of `$1`, `$2` etc. are restored.
|
||||
* `#get` *string* - inserts **a string containing** the contents of the
|
||||
file by the specified filename inline. Similar to #include, but it inserts
|
||||
a string (surrounded by `"` etc.) instead.
|
||||
* `#set` *word* *string* - assigns the contents of the string to the
|
||||
variable *word*.
|
||||
* `#unset` *word* - deletes the variable *word*.
|
||||
* `#privatens` defines a private namespace alias, as described above.
|
||||
|
||||
### Variables
|
||||
|
||||
Variables are manipulated with the `#set` and `#unset` directives, and can
|
||||
be instantiated in two ways:
|
||||
|
||||
1. `$`*name* - this inserts the contents of the variable inline. Note that
|
||||
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
|
||||
|
||||
will expand to `pushstring Hello, world!`, which will result in an error.
|
||||
To correct the problem, add escaped quotes around the variable contents
|
||||
( `#set str "\"Hello, world!\""` ), or use the second syntax:
|
||||
|
||||
2. `$"`*name*`"` - this inserts a string containing the contents of the
|
||||
variable inline. This syntax also works for `#call` arguments (e.g.
|
||||
`$"1"`).
|
||||
|
||||
### Example
|
||||
|
||||
Here's an example of how to use the above features to create a macro which
|
||||
logs a string literal and the contents of a register:
|
||||
|
||||
#set log "
|
||||
findpropstrict QName(PackageNamespace(\"\"), \"log\")
|
||||
pushstring $\"1\"
|
||||
getlocal $2
|
||||
callpropvoid QName(PackageNamespace(\"\"), \"log\"), 2
|
||||
"
|
||||
|
||||
; ...
|
||||
|
||||
pushbyte 2
|
||||
pushbyte 2
|
||||
add_i
|
||||
setlocal1
|
||||
#call $"log"("two plus two equals", "1")
|
||||
|
||||
Highlighting
|
||||
------------
|
||||
|
||||
Included with the project is the file `asasm.hrc`, a simple syntax definition
|
||||
for the [Colorer take5][] syntax highlighting library. It should be
|
||||
straight-forward to adapt it to other syntax highlighting systems.
|
||||
|
||||
[Colorer take5]: http://colorer.sourceforge.net/
|
||||
|
||||
Hacking
|
||||
=======
|
||||
|
||||
ABC is internally represented in two forms. The `ABCFile` class stores the raw
|
||||
data structures, as they appear in the binary file. `ASProgram` uses pointers
|
||||
instead of indexes, allowing easy manipulation without having to worry about
|
||||
record order or constant pools. Conversion between various states is done as
|
||||
follows:
|
||||
|
||||
file.abc
|
||||
| ^
|
||||
------ ABCReader | | ABCWriter ----
|
||||
/ v | \
|
||||
/ ABCFile \
|
||||
/ | ^ \
|
||||
rabcdasm---------- ABCtoAS | | AStoABC --------rabcasm
|
||||
\ v | /
|
||||
\ ASProgram /
|
||||
\ | ^ /
|
||||
--- Disassembler | | Assembler ----
|
||||
v |
|
||||
file.asasm
|
||||
|
||||
`AStoABC` will rebuild the constant pools, in a manner similar to Adobe's
|
||||
compilers (reverse-sorted by reference count). The exact order will almost
|
||||
surely be different, however.
|
||||
|
||||
Should you need to write an utility to manipulate ABC, you can use the existing
|
||||
code to load the file to either an `ABCFile` or `ASProgram` instance, and
|
||||
perform the necessary manipulations using those classes.
|
||||
|
||||
Tips
|
||||
====
|
||||
|
||||
The following tips come from the author's experience and may be useful for
|
||||
RABCDAsm users.
|
||||
|
||||
1. Once you have disassembled a SWF file you intend to modify, you should
|
||||
immediately add the directory to a distributed source control system, such as
|
||||
[Git][] or [Mercurial][]. This will allow you to easily track and undo your
|
||||
changes, and easily merge your changes with new versions of SWF files.
|
||||
|
||||
[Git]: http://git-scm.com/
|
||||
[Mercurial]: http://mercurial.selenic.com/
|
||||
|
||||
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 an empty window.
|
||||
|
||||
[debug Flash Player]: http://www.adobe.com/support/flashplayer/downloads.html
|
||||
|
||||
3. The [Fiddler][] Web Debugging Proxy can be very useful for analyzing
|
||||
websites with SWF content. The following script fragment (which is to be
|
||||
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);
|
||||
}
|
||||
|
||||
A more robust version of the above snippet is available as a Fiddler plugin
|
||||
[here][FiddlerAutoCapture].
|
||||
|
||||
Once you have edited a SWF file, you can use Fiddler's [AutoResponder][] to
|
||||
replace the original file with your modified version.
|
||||
|
||||
[Fiddler]: http://www.fiddler2.com/fiddler2/
|
||||
[AutoResponder]: http://www.fiddler2.com/fiddler2/help/AutoResponder.asp
|
||||
[FiddlerAutoCapture]: https://github.com/CyberShadow/FiddlerAutoCapture
|
||||
|
||||
Limitations
|
||||
===========
|
||||
|
||||
* None known.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
RABCDAsm is distributed under the terms of the GPL v3 or later, with the
|
||||
exception of `murmurhash2a.d`, `zlibx.d` and LZMA components, which are in the
|
||||
public domain, and `asasm.hrc`, which is tri-licensed under the MPL 1.1/GPL
|
||||
2.0/LGPL 2.1. The full text of the GNU General Public License can be found in
|
||||
the file `COPYING`.
|
||||
Robust ABC (ActionScript Bytecode) [Dis-]Assembler
|
||||
==================================================
|
||||
|
||||
[RABCDAsm][] is a collection of utilities including an ActionScript 3
|
||||
assembler/disassembler, and a few tools to manipulate SWF files. These are:
|
||||
|
||||
* `rabcdasm` - ABC disassembler
|
||||
* `rabcasm` - ABC assembler
|
||||
* `abcexport` - extracts ABC from SWF files
|
||||
* `abcreplace` - replaces ABC in SWF files
|
||||
* `swfdecompress` - decompresses zlib-compressed SWF files
|
||||
* `swf7zcompress` - (re-)compress the contents of a SWF using 7-Zip
|
||||
* `swflzmacompress` - compress the contents of a SWF using LZMA
|
||||
* `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
|
||||
the SWF files as deeply.
|
||||
`swfdecompress` is ancillary and is only useful for debugging and studying of
|
||||
the SWF file format, and not required for ABC manipulation. It is functionally
|
||||
equivalent to [flasm][]'s `-x` option. If you frequently work on compressed
|
||||
SWF files, you may want to decompress them to speed processing up.
|
||||
`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`.
|
||||
`swflzmacompress` compresses SWF files using the [LZMA][] algorithm, support
|
||||
for which was introduced in Flash 11. It will only work with SWF files with
|
||||
version 13 or higher.
|
||||
`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
|
||||
[flasm]: http://flasm.sourceforge.net/
|
||||
[7-Zip]: http://www.7-zip.org/
|
||||
[LZMA]: http://en.wikipedia.org/wiki/Lempel-Ziv-Markov_chain_algorithm
|
||||
|
||||
Motivation and goals
|
||||
--------------------
|
||||
|
||||
This package was created due to lack of similar software out there.
|
||||
Particularly, I needed an utility which would allow me to edit ActionScript 3
|
||||
bytecode with the following properties:
|
||||
|
||||
1. Speed. Less waiting means more productivity. `rabcasm` can assemble large
|
||||
projects (>200000 LOC) in under a second on modern machines.
|
||||
2. Comfortably-editable output. Each class is decompiled to its own file,
|
||||
with files arranged in subdirectories representing the package hierarchy.
|
||||
Class files are `#include`d from the main file.
|
||||
3. Most importantly - robustness! If the Adobe AVM can load and run the file,
|
||||
then it must be editable - no matter if the file is obfuscated or
|
||||
otherwise mutilated to prevent reverse-engineering. RABCDAsm achieves this
|
||||
by using a textual representation closer to the ABC file format, rather
|
||||
than to what an ActionScript compiler would generate.
|
||||
|
||||
Compiling from source
|
||||
---------------------
|
||||
|
||||
RABCDAsm is written in the [D programming language, version 2][d2].
|
||||
|
||||
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 -run build_rabcdasm.d
|
||||
|
||||
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`).
|
||||
|
||||
To be able to manipulate SWF files packed with LZMA compression, you'll need
|
||||
to have the [liblzma library][] and development files installed on your system.
|
||||
|
||||
Note: DMD 2.066 is required for long path support on Windows since RABCDAsm
|
||||
1.16.
|
||||
|
||||
[d2]: http://dlang.org/
|
||||
[dmd]: http://www.digitalmars.com/d/download.html
|
||||
[gdc]: http://bitbucket.org/goshawk/gdc/
|
||||
[git]: http://git-scm.com/
|
||||
[liblzma library]: http://tukaani.org/xz/
|
||||
|
||||
Pre-compiled binaries
|
||||
---------------------
|
||||
|
||||
You can find pre-compiled Windows binaries on [my website][downloads].
|
||||
However, please don't expect them to be up-to-date with the latest source
|
||||
versions.
|
||||
|
||||
[downloads]: http://files.thecybershadow.net/RABCDAsm/
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To begin hacking on a SWF file:
|
||||
|
||||
abcexport file.swf
|
||||
|
||||
This will create `file-0.abc` ... `file-N.abc` (often just `file-0.abc`). Each
|
||||
file corresponds to an ABC block inside the SWF file.
|
||||
|
||||
To disassemble one of the `.abc` files:
|
||||
|
||||
rabcdasm file-0.abc
|
||||
|
||||
This will create a `file-0` directory, which will contain `file-0.main.asasm`
|
||||
(the main program file) and files for ActionScript scripts, classes, and
|
||||
orphan and script-level methods.
|
||||
|
||||
To assemble the `.asasm` files back, and update the SWF file:
|
||||
|
||||
rabcasm file-0/file-0.main.asasm
|
||||
abcreplace file.swf 0 file-0/file-0.main.abc
|
||||
|
||||
The second `abcreplace` argument represents the index of the ABC block in the
|
||||
SWF file, and corresponds to the number in the filename created by `abcexport`.
|
||||
|
||||
`swfbinexport` and `swfbinreplace` are used in the same manner as `abcexport`
|
||||
and `abcreplace`.
|
||||
|
||||
Syntax
|
||||
======
|
||||
|
||||
The syntax of the disassembly was designed to be very simple and allow fast
|
||||
and easy parsing. It is a close representation of the `.abc` file format, and
|
||||
thus it is somewhat verbose. All constant pool elements (signed/unsigned
|
||||
integers, doubles, strings, namespaces, namespace sets, multinames) are always
|
||||
*expanded inline*, for ease of editing. Similarly, classes, instances, methods
|
||||
and method bodies are also defined inline, in the context of their "parent"
|
||||
object. By-index references of classes and methods (used in the `newclass`,
|
||||
`newfunction` and `callstatic` instructions) are represented via
|
||||
automatically-generated unique "reference strings", declared as `refid` fields.
|
||||
|
||||
If you haven't yet, I strongly recommend that you look through Adobe's
|
||||
[ActionScript Virtual Machine 2 (AVM2) Overview][avm2]. You will most likely
|
||||
need to consult it for the instruction reference anyway (although you can also
|
||||
use [this handy list][avm2i] as well). You will find it difficult to
|
||||
understand the disassembly without good understanding of concepts such as
|
||||
namespaces and multinames.
|
||||
|
||||
[avm2]: http://www.adobe.com/devnet-archive/actionscript/articles/avm2overview.pdf
|
||||
[avm2i]: http://www.anotherbigidea.com/javaswf/avm2/AVM2Instructions.html
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
In order to guarantee unambiguity and data preservation, all strings read from
|
||||
the input file - including identifiers (variable/function/class names) - are
|
||||
represented as string literals. Thus, the syntax does not have any "reserved
|
||||
words" or such - an unrecognized word is treated as an error, not as an
|
||||
identifier.
|
||||
|
||||
Whitespace (outside string literals, of course) is completely ignored, except
|
||||
where required to separate words. Comments are Intel-assembler-style: a single
|
||||
`;` demarks a comment until the next end-of-line. Control directives (such as
|
||||
`#include`) are allowed anywhere where whitespace is allowed.
|
||||
|
||||
The syntax is comprised of hierarchical blocks. Each block contains a number
|
||||
of fields - starting with a keyword specifying the field type. A block is
|
||||
terminated with the `end` keyword. Some fields contain a limited number of
|
||||
parameters, and others are, or contain blocks.
|
||||
|
||||
Hierarchy
|
||||
---------
|
||||
|
||||
The topmost block in the hierarchy is the `program` block. This must be the
|
||||
first block in the file (thus, `program` must be the first word in the file as
|
||||
well). The `program` block contains `script` fields, and `class` / `method`
|
||||
fields for "orphan" classes and methods (not owned by other objects in the
|
||||
hierarchy). Orphan methods are usually anonymous functions. The file version
|
||||
is also specified in the `program` block, using the `minorversion` and
|
||||
`majorversion` fields (both unsigned integers).
|
||||
|
||||
`script` blocks have one mandatory `sinit` field (the script initialization
|
||||
method) and `trait` fields.
|
||||
|
||||
A "trait" can be one of several kinds. The kind is specified right after the
|
||||
`trait` keyword, followed by the trait name (a multiname). Following the name
|
||||
are the trait fields, varying by trait kind:
|
||||
|
||||
* `slot` / `const` : `slotid` (unsigned integer), `type` (multiname), `value`
|
||||
* `class` : `slotid`, `class` (the actual class block)
|
||||
* `function` : `slotid`, `method` (the actual method block)
|
||||
* `method` / `getter` / `setter` : `dispid` (unsigned integer), `method`
|
||||
|
||||
Additionally, all traits may have `flag` fields, describing the trait's
|
||||
attributes (`FINAL` / `OVERRIDE` / `METADATA`), and `metadata` blocks.
|
||||
|
||||
`metadata` blocks (which are ignored by the AVM) consist of a name string, and
|
||||
a series of `item` fields - each item having a key and value string.
|
||||
|
||||
`class` blocks have mandatory `instance` and `cinit` fields, defining the
|
||||
class instance and the class initializer method respectively. They may also
|
||||
have `trait` fields and a `refid` field (the `refid` field is not part of the
|
||||
file format - it's an unique string to allow referencing the class, see above).
|
||||
|
||||
`instance` blocks - always declared inline of their `class` block - must
|
||||
contain one `iinit` field (the instance initializer method), and may contain
|
||||
one `extends` field (multiname), `implements` fields (multinames), `flag`
|
||||
fields (`SEALED` / `FINAL` / `INTERFACE` / `PROTECTEDNS`), one `protectedns`
|
||||
field (namespace), and `trait` fields.
|
||||
|
||||
`method` blocks may contain one `name` field (multiname), a `refid` field,
|
||||
`param` fields (multinames - this represents the parameter types), one
|
||||
`returns` field (multiname), `flag` fields (`NEED_ARGUMENTS` /
|
||||
`NEED_ACTIVATION` / `NEED_REST` / `HAS_OPTIONAL` / `SET_DXNS` /
|
||||
`HAS_PARAM_NAMES`), `optional` fields (values), `paramname` fields (strings),
|
||||
and a `body` field (method body).
|
||||
|
||||
`body` blocks - always declared inline of their `method` block - must contain
|
||||
the `maxstack`, `localcount`, `initscopedepth` and `maxscopedepth` fields
|
||||
(unsigned integers), and a `code` field. It may also contain `try` and `trait`
|
||||
fields.
|
||||
|
||||
`code` blocks - always declared inline of their `body` block - are somewhat
|
||||
different in syntax from other blocks - mostly in that they may contain
|
||||
labels. Labels follow the most common syntax - a word followed by a `:`
|
||||
character, optionally followed by a relative byte offset (in case of pointers
|
||||
inside instructions). Multiple instruction arguments are comma-separated.
|
||||
Instruction arguments' types depend on the instruction - see the `OpcodeInfo`
|
||||
array in `abcfile.d` for a reference.
|
||||
|
||||
`try` blocks - always declared inline of their `body` block - represent an
|
||||
"exception" (try/catch) block. They contain five mandatory fields: `from`,
|
||||
`to` and `target` (names of labels representing start and end of the "try"
|
||||
block, and start of the "catch" block respectively), and `type` and `name`
|
||||
(multinames), representing the type and name of the exception variable.
|
||||
|
||||
Values have the syntax *type* `(` *value* `)` . *type* can be one of
|
||||
`Integer`, `UInteger`, `Double`, `Utf8`, `Namespace`, `PackageNamespace`,
|
||||
`PackageInternalNs`, `ProtectedNamespace`, `ExplicitNamespace`,
|
||||
`StaticProtectedNs`, `PrivateNamespace`, `True`, `False`, `Null` or
|
||||
`Undefined`. The type of the value depends on *type*. Types `True`, `False`,
|
||||
`Null` and `Undefined` have no value.
|
||||
|
||||
Constants
|
||||
---------
|
||||
|
||||
Multinames have the syntax *type* `(` *parameters* `)` . *type* can be one of
|
||||
`QName` / `QNameA`, `RTQName` / `RTQNameA`, `RTQNameL` / `RTQNameLA`,
|
||||
`Multiname` / `MultinameA`, `MultinameL` / `MultinameLA`, or `TypeName`.
|
||||
*parameters* depends on *type*:
|
||||
|
||||
* `QName` / `QNameA` `(` *namespace* `,` *string* `)`
|
||||
* `RTQName` / `RTQNameA` `(` *string* `)`
|
||||
* `RTQNameL` / `RTQNameLA` `(` `)`
|
||||
* `Multiname` / `MultinameA` `(` *string* `,` *namespace-set* `)`
|
||||
* `MultinameL` / `MultinameLA` `(` *namespace-set* `)`
|
||||
* `TypeName` `(` *multiname* `<` *multiname [* `,` *multiname ... ]* `>` `)`
|
||||
|
||||
Namespace sets have the syntax `[` *[ namespace [* `,` *namespace ... ] ]* `]`
|
||||
(that is, a comma-separated list of namespaces in square brackets). Empty
|
||||
namespace sets can be specified using `[]`.
|
||||
|
||||
Namespaces have the syntax *type* `(` *string [* `,` *string ]* `)` . The
|
||||
first string indicates the namespace name. In the case that there are multiple
|
||||
distinct namespaces with the same type and name (as `PrivateNamespace`
|
||||
namespaces usually are), a second parameter may be present to uniquely
|
||||
distinguish them. Internally (the ABC file format), namespaces are
|
||||
distinguished by their numerical index. When disassembling, `rabcdasm` will
|
||||
attempt to assign descriptive labels to homonym namespaces based on their
|
||||
context.
|
||||
|
||||
Strings have a syntax similar to C string literals. Strings start and end with
|
||||
a `"`. Supported escape sequences (a backslash followed by a letter) are `\n`
|
||||
(generates ASCII 0x0A), `\r` (ASCII 0x0D), and `\x` followed by two
|
||||
hexadecimal digits, which inserts the ASCII character with that code. Any
|
||||
other characters following a backslash generate that character - thus, you can
|
||||
escape backslashes using `\\` and double quotes using `\"`. When decompiling,
|
||||
high-ASCII characters (usually UTF-8) are not escaped - if you see gibberish
|
||||
instead of international text, configure your editor to open the files in
|
||||
UTF-8 encoding.
|
||||
|
||||
Additionally, constant pool types (signed/unsigned integers, doubles, strings,
|
||||
namespaces, namespace sets and multinames) may also have the value `null`
|
||||
(which represents the index 0 in the ABC file). Note that `null` is
|
||||
conceptually different from zero, an empty string or empty namespace set.
|
||||
|
||||
Macros
|
||||
------
|
||||
|
||||
RABCDAsm has some basic macro-like capabilities, controlled by directives and
|
||||
variables. These bear some similarity to the C preprocessor, however these are
|
||||
processed in-loop rather than as a separate pre-processing step.
|
||||
|
||||
### Directives
|
||||
|
||||
Directives start with a `#`, followed by a word identifying the directive:
|
||||
|
||||
* `#include` *string* - inserts the contents of the file by the specified
|
||||
filename inline. Functionally equivalent to `#mixin #get` *string* , but
|
||||
faster.
|
||||
* `#mixin` *string* - inserts the contents of the specified string inline.
|
||||
Not very useful on its own.
|
||||
* `#call` *string* `(` *[ string [* `,` *string ... ] ]* `)` - same as
|
||||
`#mixin`, however it additionally sets the special variables `$1`, `$2`
|
||||
etc. to the contents of the specified arguments. When the end of the
|
||||
inserted string is reached, the old values of `$1`, `$2` etc. are restored.
|
||||
* `#get` *string* - inserts **a string containing** the contents of the file
|
||||
by the specified filename inline. Similar to #include, but it inserts a
|
||||
string (surrounded by `"` etc.) instead.
|
||||
* `#set` *word* *string* - assigns the contents of the string to the
|
||||
variable *word*.
|
||||
* `#unset` *word* - deletes the variable *word*.
|
||||
* `#privatens` *number* *string* - deprecated, currently ignored.
|
||||
* `#version` specifies the syntax version of the disassembly. Newer RABCDAsm
|
||||
versions may emit disassembly output that is not backwards-compatible, but
|
||||
should still understand older disassemblies. The versions are:
|
||||
1. The first version.
|
||||
2. Introduced in v1.11 to work around error in ABC format specification.
|
||||
3. Introduced in v1.12 to support multiple non-private namespaces with
|
||||
the same name. This is the current version.
|
||||
|
||||
### Variables
|
||||
|
||||
Variables are manipulated with the `#set` and `#unset` directives, and can be
|
||||
instantiated in two ways:
|
||||
|
||||
1. `$`*name* - this inserts the contents of the variable inline. Note that
|
||||
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
|
||||
|
||||
will expand to `pushstring Hello, world!`, which will result in an error.
|
||||
To correct the problem, add escaped quotes around the variable contents
|
||||
( `#set str "\"Hello, world!\""` ), or use the second syntax:
|
||||
|
||||
2. `$"`*name*`"` - this inserts a string containing the contents of the
|
||||
variable inline. This syntax also works for `#call` arguments (e.g.
|
||||
`$"1"`).
|
||||
|
||||
### Example
|
||||
|
||||
Here's an example of how to use the above features to create a macro which
|
||||
logs a string literal and the contents of a register:
|
||||
|
||||
#set log "
|
||||
findpropstrict QName(PackageNamespace(\"\"), \"log\")
|
||||
pushstring $\"1\"
|
||||
getlocal $2
|
||||
callpropvoid QName(PackageNamespace(\"\"), \"log\"), 2
|
||||
"
|
||||
|
||||
; ...
|
||||
|
||||
pushbyte 2
|
||||
pushbyte 2
|
||||
add_i
|
||||
setlocal1
|
||||
#call $"log"("two plus two equals", "1")
|
||||
|
||||
Highlighting
|
||||
------------
|
||||
|
||||
Included with the project is the file `asasm.hrc`, a simple syntax definition
|
||||
for the [Colorer take5][] syntax highlighting library. It should be
|
||||
straight-forward to adapt it to other syntax highlighting systems.
|
||||
|
||||
[Colorer take5]: http://colorer.sourceforge.net/
|
||||
|
||||
Hacking
|
||||
=======
|
||||
|
||||
ABC is internally represented in two forms. The `ABCFile` class stores the raw
|
||||
data structures, as they appear in the binary file. `ASProgram` uses pointers
|
||||
instead of indexes, allowing easy manipulation without having to worry about
|
||||
record order or constant pools. Conversion between various states is done as
|
||||
follows:
|
||||
|
||||
file.abc
|
||||
| ^
|
||||
------ ABCReader | | ABCWriter ----
|
||||
/ v | \
|
||||
/ ABCFile \
|
||||
/ | ^ \
|
||||
rabcdasm---------- ABCtoAS | | AStoABC --------rabcasm
|
||||
\ v | /
|
||||
\ ASProgram /
|
||||
\ | ^ /
|
||||
--- Disassembler | | Assembler ----
|
||||
v |
|
||||
file.asasm
|
||||
|
||||
`AStoABC` will rebuild the constant pools, in a manner similar to Adobe's
|
||||
compilers (reverse-sorted by reference count). The exact order will almost
|
||||
surely be different, however.
|
||||
|
||||
Should you need to write an utility to manipulate ABC, you can use the
|
||||
existing code to load the file to either an `ABCFile` or `ASProgram` instance,
|
||||
and perform the necessary manipulations using those classes.
|
||||
|
||||
Tips
|
||||
====
|
||||
|
||||
The following tips come from the author's experience and may be useful for
|
||||
RABCDAsm users.
|
||||
|
||||
1. Once you have disassembled a SWF file you intend to modify, you should
|
||||
immediately add the directory to a distributed source control system, such
|
||||
as [Git][] or [Mercurial][]. This will allow you to easily track and undo
|
||||
your changes, and easily merge your changes with new versions of SWF files.
|
||||
|
||||
[Git]: http://git-scm.com/
|
||||
[Mercurial]: http://mercurial.selenic.com/
|
||||
|
||||
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 an empty window.
|
||||
|
||||
[debug Flash Player]: http://www.adobe.com/support/flashplayer/downloads.html
|
||||
|
||||
3. The [Fiddler][] Web Debugging Proxy can be very useful for analyzing
|
||||
websites with SWF content. The following script fragment (which is to be
|
||||
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);
|
||||
}
|
||||
|
||||
A more robust version of the above snippet is available as a Fiddler plugin
|
||||
[here][FiddlerAutoCapture].
|
||||
|
||||
Once you have edited a SWF file, you can use Fiddler's [AutoResponder][] to
|
||||
replace the original file with your modified version.
|
||||
|
||||
[Fiddler]: http://www.fiddler2.com/fiddler2/
|
||||
[AutoResponder]: http://www.fiddler2.com/fiddler2/help/AutoResponder.asp
|
||||
[FiddlerAutoCapture]: https://github.com/CyberShadow/FiddlerAutoCapture
|
||||
|
||||
Limitations
|
||||
===========
|
||||
|
||||
* None known.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
RABCDAsm is distributed under the terms of the GPL v3 or later, with the
|
||||
exception of `murmurhash2a.d`, `zlibx.d` and LZMA components, which are in the
|
||||
public domain, and `asasm.hrc`, which is tri-licensed under the MPL 1.1/GPL
|
||||
2.0/LGPL 2.1. The full text of the GNU General Public License can be found in
|
||||
the file `COPYING`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011, 2012 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011, 2012, 2013, 2014 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -131,13 +131,8 @@ class ABCFile
|
||||
|
||||
struct Metadata
|
||||
{
|
||||
struct Item
|
||||
{
|
||||
uint key, value;
|
||||
}
|
||||
|
||||
uint name;
|
||||
Item[] items;
|
||||
uint[] keys, values;
|
||||
}
|
||||
|
||||
struct Instance
|
||||
@@ -182,12 +177,12 @@ class ABCFile
|
||||
}
|
||||
uint[] metadata;
|
||||
|
||||
TraitKind kind() { return cast(TraitKind)(kindAttr&0xF); }
|
||||
void kind(TraitKind value) { kindAttr = (kindAttr&0xF0) | value; }
|
||||
@property TraitKind kind() { return cast(TraitKind)(kindAttr&0xF); }
|
||||
@property void kind(TraitKind value) { kindAttr = (kindAttr&0xF0) | value; }
|
||||
|
||||
// TraitAttributes bitmask
|
||||
ubyte attr() { return cast(ubyte)(kindAttr >> 4); }
|
||||
void attr(ubyte value) { kindAttr = cast(ubyte)((kindAttr&0xF) | (value<<4)); }
|
||||
@property ubyte attr() { return cast(ubyte)(kindAttr >> 4); }
|
||||
@property void attr(ubyte value) { kindAttr = cast(ubyte)((kindAttr&0xF) | (value<<4)); }
|
||||
}
|
||||
|
||||
struct Class
|
||||
@@ -213,8 +208,7 @@ class ABCFile
|
||||
ExceptionInfo[] exceptions;
|
||||
TraitsInfo[] traits;
|
||||
|
||||
string error;
|
||||
ubyte[] rawBytes;
|
||||
Error[] errors;
|
||||
}
|
||||
|
||||
/// Destination for a jump or exception block boundary
|
||||
@@ -224,18 +218,26 @@ class ABCFile
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint index; /// instruction index
|
||||
int offset; /// signed offset relative to said instruction
|
||||
uint index = uint.max; /// instruction index
|
||||
int offset = int.max; /// signed offset relative to said instruction
|
||||
}
|
||||
private ptrdiff_t absoluteOffset; /// internal temporary value used during reading and writing
|
||||
}
|
||||
}
|
||||
|
||||
/// Disassembly/decoding error
|
||||
struct Error
|
||||
{
|
||||
Label loc;
|
||||
string msg;
|
||||
}
|
||||
|
||||
struct Instruction
|
||||
{
|
||||
Opcode opcode;
|
||||
union Argument
|
||||
{
|
||||
byte bytev;
|
||||
ubyte ubytev;
|
||||
long intv;
|
||||
ulong uintv;
|
||||
@@ -444,6 +446,7 @@ const string[4] TraitAttributeNames = ["FINAL", "OVERRIDE", "METADATA", "0x08"];
|
||||
|
||||
enum Opcode : ubyte
|
||||
{
|
||||
OP_raw = 0x00, /// Used internally by RABCDAsm
|
||||
OP_bkpt = 0x01,
|
||||
OP_nop = 0x02,
|
||||
OP_throw = 0x03,
|
||||
@@ -639,6 +642,7 @@ enum OpcodeArgumentType
|
||||
{
|
||||
Unknown,
|
||||
|
||||
ByteLiteral,
|
||||
UByteLiteral,
|
||||
IntLiteral,
|
||||
UIntLiteral,
|
||||
@@ -664,7 +668,7 @@ struct OpcodeInfo
|
||||
}
|
||||
|
||||
const OpcodeInfo[256] opcodeInfo = [
|
||||
/* 0x00 */ {"0x00", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x00 */ {"db", [OpcodeArgumentType.UByteLiteral]},
|
||||
/* 0x01 */ {"bkpt", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x02 */ {"nop", []},
|
||||
/* 0x03 */ {"throw", []},
|
||||
@@ -700,7 +704,7 @@ const OpcodeInfo[256] opcodeInfo = [
|
||||
/* 0x21 */ {"pushundefined", []},
|
||||
/* 0x22 */ {"pushuninitialized", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x23 */ {"nextvalue", []},
|
||||
/* 0x24 */ {"pushbyte", [OpcodeArgumentType.UByteLiteral]},
|
||||
/* 0x24 */ {"pushbyte", [OpcodeArgumentType.ByteLiteral]},
|
||||
/* 0x25 */ {"pushshort", [OpcodeArgumentType.IntLiteral]},
|
||||
/* 0x26 */ {"pushtrue", []},
|
||||
/* 0x27 */ {"pushfalse", []},
|
||||
@@ -759,7 +763,7 @@ const OpcodeInfo[256] opcodeInfo = [
|
||||
/* 0x5C */ {"0x5C", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x5D */ {"findpropstrict", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x5E */ {"findproperty", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x5F */ {"finddef", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x5F */ {"finddef", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x60 */ {"getlex", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x61 */ {"setproperty", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x62 */ {"getlocal", [OpcodeArgumentType.UIntLiteral]},
|
||||
@@ -767,11 +771,11 @@ const OpcodeInfo[256] opcodeInfo = [
|
||||
/* 0x64 */ {"getglobalscope", []},
|
||||
/* 0x65 */ {"getscopeobject", [OpcodeArgumentType.UByteLiteral]},
|
||||
/* 0x66 */ {"getproperty", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x67 */ {"getpropertylate", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x67 */ {"getpropertylate", []},
|
||||
/* 0x68 */ {"initproperty", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x69 */ {"setpropertylate", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x69 */ {"setpropertylate", []},
|
||||
/* 0x6A */ {"deleteproperty", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x6B */ {"deletepropertylate", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x6B */ {"deletepropertylate", []},
|
||||
/* 0x6C */ {"getslot", [OpcodeArgumentType.UIntLiteral]},
|
||||
/* 0x6D */ {"setslot", [OpcodeArgumentType.UIntLiteral]},
|
||||
/* 0x6E */ {"getglobalslot", [OpcodeArgumentType.UIntLiteral]},
|
||||
@@ -793,10 +797,10 @@ const OpcodeInfo[256] opcodeInfo = [
|
||||
/* 0x7E */ {"0x7E", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x7F */ {"0x7F", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x80 */ {"coerce", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x81 */ {"coerce_b", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x81 */ {"coerce_b", []},
|
||||
/* 0x82 */ {"coerce_a", []},
|
||||
/* 0x83 */ {"coerce_i", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x84 */ {"coerce_d", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x83 */ {"coerce_i", []},
|
||||
/* 0x84 */ {"coerce_d", []},
|
||||
/* 0x85 */ {"coerce_s", []},
|
||||
/* 0x86 */ {"astype", [OpcodeArgumentType.Multiname]},
|
||||
/* 0x87 */ {"astypelate", []},
|
||||
@@ -931,6 +935,16 @@ static this()
|
||||
OpcodeByName = OpcodeByName.rehash;
|
||||
}
|
||||
|
||||
bool[256] genLookup(Opcode[] opcodes)
|
||||
{
|
||||
bool[256] result;
|
||||
foreach (op; opcodes)
|
||||
result[op] = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const bool[256] stopsExecution = genLookup([Opcode.OP_returnvalue, Opcode.OP_returnvoid, Opcode.OP_throw, Opcode.OP_jump, Opcode.OP_lookupswitch]);
|
||||
|
||||
private final class ABCReader
|
||||
{
|
||||
ubyte[] buf;
|
||||
@@ -1023,6 +1037,11 @@ private final class ABCReader
|
||||
|
||||
/// Note: may return values larger than 0xFFFFFFFF.
|
||||
ulong readU32()
|
||||
out(result)
|
||||
{
|
||||
assert(result <= ABCFile.MAX_UINT);
|
||||
}
|
||||
body
|
||||
{
|
||||
ulong next() { return readU8(); } // force ulong
|
||||
|
||||
@@ -1042,10 +1061,15 @@ private final class ABCReader
|
||||
}
|
||||
|
||||
long readS32()
|
||||
out(result)
|
||||
{
|
||||
ulong l = readU32();
|
||||
if (l & 0xFFFFFFFF00000000) // preserve unused bits
|
||||
return cast(long)l;
|
||||
assert(result >= ABCFile.MIN_INT && result <= ABCFile.MAX_INT);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto l = readU32();
|
||||
if (l & 0xFFFFFFFF_00000000) // preserve unused bits
|
||||
return l | 0xFFFFFFF0_00000000;
|
||||
else
|
||||
return cast(int)l;
|
||||
}
|
||||
@@ -1185,12 +1209,11 @@ private final class ABCReader
|
||||
{
|
||||
ABCFile.Metadata r;
|
||||
r.name = readU30();
|
||||
r.items.length = readU30();
|
||||
foreach (ref value; r.items)
|
||||
{
|
||||
value.key = readU30();
|
||||
value.value = readU30();
|
||||
}
|
||||
r.keys.length = r.values.length = readU30();
|
||||
foreach (ref key; r.keys)
|
||||
key = readU30();
|
||||
foreach (ref value; r.values)
|
||||
value = readU30();
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -1287,8 +1310,197 @@ private final class ABCReader
|
||||
r.instructions = null;
|
||||
|
||||
size_t len = readU30();
|
||||
size_t start = pos;
|
||||
size_t end = pos + len;
|
||||
|
||||
pos = end;
|
||||
r.exceptions.length = readU30();
|
||||
foreach (ref value; r.exceptions)
|
||||
value = readExceptionInfo();
|
||||
|
||||
size_t postExceptions = pos;
|
||||
|
||||
enum TraceState : ubyte
|
||||
{
|
||||
unexplored,
|
||||
pending,
|
||||
instruction,
|
||||
instructionBody,
|
||||
error,
|
||||
}
|
||||
|
||||
auto traceState = new TraceState[len];
|
||||
auto instructions = new ABCFile.Instruction[len];
|
||||
|
||||
@property size_t offset() { return pos - start; }
|
||||
|
||||
bool havePending;
|
||||
|
||||
void queue(size_t traceOffset)
|
||||
{
|
||||
if (traceOffset < len && traceState[traceOffset] == TraceState.unexplored)
|
||||
{
|
||||
traceState[traceOffset] = TraceState.pending;
|
||||
havePending = true;
|
||||
}
|
||||
}
|
||||
|
||||
queue(0);
|
||||
|
||||
foreach (ref value; r.exceptions)
|
||||
queue(value.target.absoluteOffset);
|
||||
|
||||
while (havePending)
|
||||
{
|
||||
havePending = false;
|
||||
pos = start;
|
||||
while (pos < end)
|
||||
{
|
||||
if (traceState[offset] == TraceState.pending)
|
||||
{
|
||||
size_t instructionOffset;
|
||||
|
||||
try
|
||||
{
|
||||
while (pos < end)
|
||||
{
|
||||
instructionOffset = offset;
|
||||
|
||||
enforce(traceState[instructionOffset] != TraceState.instructionBody, "Overlapping instruction");
|
||||
if (traceState[instructionOffset] == TraceState.instruction)
|
||||
break; // already decoded
|
||||
|
||||
ABCFile.Instruction instruction;
|
||||
instruction.opcode = cast(Opcode)readU8();
|
||||
enforce(instruction.opcode != Opcode.OP_raw, "Null opcode");
|
||||
instruction.arguments.length = opcodeInfo[instruction.opcode].argumentTypes.length;
|
||||
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
|
||||
final switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to decode OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
|
||||
case OpcodeArgumentType.ByteLiteral:
|
||||
instruction.arguments[i].bytev = readU8();
|
||||
break;
|
||||
case OpcodeArgumentType.UByteLiteral:
|
||||
instruction.arguments[i].ubytev = readU8();
|
||||
break;
|
||||
case OpcodeArgumentType.IntLiteral:
|
||||
instruction.arguments[i].intv = readS32();
|
||||
break;
|
||||
case OpcodeArgumentType.UIntLiteral:
|
||||
instruction.arguments[i].uintv = readU32();
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.Int:
|
||||
case OpcodeArgumentType.UInt:
|
||||
case OpcodeArgumentType.Double:
|
||||
case OpcodeArgumentType.String:
|
||||
case OpcodeArgumentType.Namespace:
|
||||
case OpcodeArgumentType.Multiname:
|
||||
case OpcodeArgumentType.Class:
|
||||
case OpcodeArgumentType.Method:
|
||||
{
|
||||
auto index = readU30();
|
||||
size_t length;
|
||||
switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Int: length = abc.ints .length; break;
|
||||
case OpcodeArgumentType.UInt: length = abc.uints .length; break;
|
||||
case OpcodeArgumentType.Double: length = abc.doubles .length; break;
|
||||
case OpcodeArgumentType.String: length = abc.strings .length; break;
|
||||
case OpcodeArgumentType.Namespace: length = abc.namespaces.length; break;
|
||||
case OpcodeArgumentType.Multiname: length = abc.multinames.length; break;
|
||||
case OpcodeArgumentType.Class: length = abc.classes .length; break;
|
||||
case OpcodeArgumentType.Method: length = abc.methods .length; break;
|
||||
default: assert(false);
|
||||
}
|
||||
enforce(index < length, "Out-of-bounds constant index");
|
||||
instruction.arguments[i].index = index;
|
||||
break;
|
||||
}
|
||||
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
{
|
||||
auto delta = readS24();
|
||||
auto target = offset + delta;
|
||||
instruction.arguments[i].jumpTarget.absoluteOffset = target;
|
||||
queue(target);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
{
|
||||
auto target = instructionOffset + readS24();
|
||||
instruction.arguments[i].jumpTarget.absoluteOffset = target;
|
||||
queue(target);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
instruction.arguments[i].switchTargets.length = readU30()+1;
|
||||
foreach (ref label; instruction.arguments[i].switchTargets)
|
||||
{
|
||||
label.absoluteOffset = instructionOffset + readS24();
|
||||
queue(label.absoluteOffset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
enforce(offset <= len, "Out-of-bounds code read error");
|
||||
|
||||
instructions[instructionOffset] = instruction;
|
||||
traceState[instructionOffset] = TraceState.instruction;
|
||||
traceState[instructionOffset+1..offset] = TraceState.instructionBody;
|
||||
|
||||
if (stopsExecution[instruction.opcode])
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
traceState[instructionOffset] = TraceState.error;
|
||||
ABCFile.Label loc;
|
||||
loc.absoluteOffset = instructionOffset;
|
||||
r.errors ~= ABCFile.Error(loc, e.msg);
|
||||
|
||||
pos = start + instructionOffset + 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
size_t[] instructionOffsets;
|
||||
auto instructionAtOffset = new uint[len];
|
||||
r.rawBytes = buf[pos..pos+len];
|
||||
instructionAtOffset[] = uint.max;
|
||||
|
||||
void addInstruction(ref ABCFile.Instruction i, size_t offset)
|
||||
{
|
||||
instructionAtOffset[offset] = to!uint(r.instructions.length);
|
||||
r.instructions ~= i;
|
||||
instructionOffsets ~= offset;
|
||||
}
|
||||
|
||||
foreach (o, state; traceState)
|
||||
{
|
||||
assert(state != TraceState.pending);
|
||||
if (state == TraceState.instruction)
|
||||
addInstruction(instructions[o], o);
|
||||
else
|
||||
if (state == TraceState.unexplored || state == TraceState.error)
|
||||
{
|
||||
ABCFile.Instruction instruction;
|
||||
instruction.opcode = Opcode.OP_raw;
|
||||
instruction.arguments.length = 1;
|
||||
instruction.arguments[0].ubytev = buf[start + o];
|
||||
addInstruction(instruction, o);
|
||||
}
|
||||
else
|
||||
assert(state == TraceState.instructionBody);
|
||||
}
|
||||
|
||||
void translateLabel(ref ABCFile.Label label)
|
||||
{
|
||||
@@ -1318,105 +1530,37 @@ private final class ABCReader
|
||||
label.offset = to!int(absoluteOffset-instructionOffset);
|
||||
}
|
||||
|
||||
size_t start = pos;
|
||||
size_t end = pos + len;
|
||||
// convert jump target offsets to instruction indices
|
||||
foreach (ii, ref instruction; r.instructions)
|
||||
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
|
||||
switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
translateLabel(instruction.arguments[i].jumpTarget);
|
||||
break;
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
foreach (ref x; instruction.arguments[i].switchTargets)
|
||||
translateLabel(x);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
size_t offset() { return pos - start; }
|
||||
// convert error offsets to instruction indices
|
||||
foreach (ref e; r.errors)
|
||||
translateLabel(e.loc);
|
||||
|
||||
try
|
||||
{
|
||||
instructionAtOffset[] = uint.max;
|
||||
size_t[] instructionOffsets;
|
||||
while (pos < end)
|
||||
{
|
||||
auto instructionOffset = offset;
|
||||
scope(failure) pos = start + instructionOffset;
|
||||
instructionAtOffset[instructionOffset] = to!uint(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)
|
||||
final switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to decode OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
|
||||
case OpcodeArgumentType.UByteLiteral:
|
||||
instruction.arguments[i].ubytev = readU8();
|
||||
break;
|
||||
case OpcodeArgumentType.IntLiteral:
|
||||
instruction.arguments[i].intv = readS32();
|
||||
break;
|
||||
case OpcodeArgumentType.UIntLiteral:
|
||||
instruction.arguments[i].uintv = readU32();
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.Int:
|
||||
case OpcodeArgumentType.UInt:
|
||||
case OpcodeArgumentType.Double:
|
||||
case OpcodeArgumentType.String:
|
||||
case OpcodeArgumentType.Namespace:
|
||||
case OpcodeArgumentType.Multiname:
|
||||
case OpcodeArgumentType.Class:
|
||||
case OpcodeArgumentType.Method:
|
||||
instruction.arguments[i].index = readU30();
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
int delta = readS24();
|
||||
instruction.arguments[i].jumpTarget.absoluteOffset = offset + delta;
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
instruction.arguments[i].jumpTarget.absoluteOffset = instructionOffset + readS24();
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
instruction.arguments[i].switchTargets.length = readU30()+1;
|
||||
foreach (ref label; instruction.arguments[i].switchTargets)
|
||||
label.absoluteOffset = instructionOffset + readS24();
|
||||
break;
|
||||
}
|
||||
r.instructions ~= instruction;
|
||||
instructionOffsets ~= instructionOffset;
|
||||
}
|
||||
|
||||
if (pos > end)
|
||||
throw new Exception("Out-of-bounds code read error");
|
||||
|
||||
// convert jump target offsets to instruction indices
|
||||
foreach (ii, ref instruction; r.instructions)
|
||||
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
|
||||
switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
translateLabel(instruction.arguments[i].jumpTarget);
|
||||
break;
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
foreach (ref x; instruction.arguments[i].switchTargets)
|
||||
translateLabel(x);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
r.instructions = null;
|
||||
r.error = e.msg;
|
||||
instructionAtOffset[] = 0;
|
||||
}
|
||||
pos = end;
|
||||
|
||||
r.exceptions.length = readU30();
|
||||
// convert exception offsets to instruction indices
|
||||
foreach (ref value; r.exceptions)
|
||||
{
|
||||
value = readExceptionInfo();
|
||||
translateLabel(value.from);
|
||||
translateLabel(value.to);
|
||||
translateLabel(value.target);
|
||||
}
|
||||
|
||||
pos = postExceptions;
|
||||
|
||||
r.traits.length = readU30();
|
||||
foreach (ref value; r.traits)
|
||||
value = readTrait();
|
||||
@@ -1676,12 +1820,12 @@ private final class ABCWriter
|
||||
void writeMetadata(ref ABCFile.Metadata v)
|
||||
{
|
||||
writeU30(v.name);
|
||||
writeU30(v.items.length);
|
||||
foreach (ref value; v.items)
|
||||
{
|
||||
writeU30(value.key);
|
||||
writeU30(value.value);
|
||||
}
|
||||
assert(v.keys.length == v.values.length);
|
||||
writeU30(v.keys.length);
|
||||
foreach (key; v.keys)
|
||||
writeU30(key);
|
||||
foreach (value; v.values)
|
||||
writeU30(value);
|
||||
}
|
||||
|
||||
void writeInstance(ref ABCFile.Instance v)
|
||||
@@ -1794,6 +1938,9 @@ private final class ABCWriter
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to encode OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
|
||||
case OpcodeArgumentType.ByteLiteral:
|
||||
writeU8(instruction.arguments[i].bytev);
|
||||
break;
|
||||
case OpcodeArgumentType.UByteLiteral:
|
||||
writeU8(instruction.arguments[i].ubytev);
|
||||
break;
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011, 2012 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -41,6 +41,7 @@ void main(string[] args)
|
||||
while (*p++) {} // skip name
|
||||
tag.data = tag.data[0..p-tag.data.ptr] ~ abc;
|
||||
}
|
||||
tag.length = cast(uint)tag.data.length;
|
||||
write(args[1], swf.write());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<hrc version="take5" xmlns="http://colorer.sf.net/2003/hrc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://colorer.sf.net/2003/hrc http://colorer.sf.net/2003/hrc.xsd">
|
||||
|
||||
|
||||
<type name="asasm">
|
||||
<annotation><documentation>
|
||||
ActionScript Assembler Syntax (RABCDAsm variant)
|
||||
@@ -31,20 +31,24 @@
|
||||
|
||||
<!-- Decimal numbers -->
|
||||
<regexp match="/\b[0-9]+\b/" region0="def:NumberDec"/>
|
||||
|
||||
|
||||
<!-- Paired brackets -->
|
||||
<block start="/(\()/" end="/(\))/" scheme="asasm" region00="def:Symbol" region01="def:PairStart" region10="def:Symbol" region11="def:PairEnd"/>
|
||||
<block start="/(\[)/" end="/(\])/" scheme="asasm" region00="def:Symbol" region01="def:PairStart" region10="def:Symbol" region11="def:PairEnd"/>
|
||||
<block start="/(\<)/" end="/(\>)/" scheme="asasm" region00="def:Symbol" region01="def:PairStart" region10="def:Symbol" region11="def:PairEnd"/>
|
||||
|
||||
<!-- Outliner for refids -->
|
||||
<regexp match="/(\brefid\b)\s*((?{def:StringEdge}"))((?{def:String}[^"]*))((?{def:StringEdge}"))/" region1="def:Keyword" region2="def:PairStart" region3="def:Outlined" region4="def:PairEnd"/> <!-- BUG: does not support escape sequences -->
|
||||
<!--regexp match="/(\brefid\b)\s*((?{def:StringEdge}"))((?{def:String}[^"]*))((?{def:StringEdge}"))/" region1="def:Keyword" region2="def:PairStart" region3="def:Outlined" region4="def:PairEnd"/--> <!-- BUG: does not support escape sequences -->
|
||||
<regexp
|
||||
match="/(\brefid\b)\M\s*"(.*)"$/"
|
||||
region1="def:Keyword"
|
||||
region2="def:Outlined"/>
|
||||
<!--block start="/(\brefid\b)\s*(?{def:StringEdge}")/" end="/(?{def:StringEdge}")/" scheme="StringContent" region="def:Outlined" inner-region="yes" region00="def:PairStart" region10="def:PairEnd"/-->
|
||||
|
||||
<!-- Symbol/keyword highlighting below -->
|
||||
|
||||
<keywords region="def:Symbol">
|
||||
<symb name=","/>
|
||||
<symb name=","/>
|
||||
</keywords>
|
||||
|
||||
<keywords ignorecase="no" region="def:Directive">
|
||||
@@ -55,6 +59,7 @@
|
||||
<word name="#privatens"/>
|
||||
<word name="#set"/>
|
||||
<word name="#unset"/>
|
||||
<word name="#version"/>
|
||||
</keywords>
|
||||
|
||||
<keywords ignorecase="no" region="def:Keyword">
|
||||
|
||||
+130
-139
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011, 2012, 2013, 2014 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -40,7 +40,7 @@ final class ASProgram
|
||||
ASType kind;
|
||||
string name;
|
||||
|
||||
uint privateIndex; // unique index for private namespaces
|
||||
uint id; // unique index for private/homonym namespaces
|
||||
|
||||
mixin AutoCompare;
|
||||
mixin AutoToString;
|
||||
@@ -154,23 +154,23 @@ final class ASProgram
|
||||
Value[] options;
|
||||
string[] paramNames;
|
||||
|
||||
uint id; // file index
|
||||
|
||||
MethodBody vbody;
|
||||
|
||||
override string toString() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
static class Metadata
|
||||
{
|
||||
struct Item
|
||||
{
|
||||
string key, value;
|
||||
|
||||
mixin AutoCompare;
|
||||
mixin ProcessAllData;
|
||||
}
|
||||
|
||||
string name;
|
||||
Item[] items;
|
||||
string[] keys, values;
|
||||
|
||||
mixin AutoCompare;
|
||||
mixin AutoToString;
|
||||
mixin ProcessAllData;
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ final class ASProgram
|
||||
|
||||
Instance instance;
|
||||
|
||||
override string toString()
|
||||
override string toString() const
|
||||
{
|
||||
return instance.name.toString();
|
||||
}
|
||||
@@ -261,8 +261,7 @@ final class ASProgram
|
||||
Exception[] exceptions;
|
||||
Trait[] traits;
|
||||
|
||||
string error;
|
||||
ubyte[] rawBytes;
|
||||
ABCFile.Error[] errors;
|
||||
}
|
||||
|
||||
struct Instruction
|
||||
@@ -270,6 +269,7 @@ final class ASProgram
|
||||
Opcode opcode;
|
||||
union Argument
|
||||
{
|
||||
byte bytev;
|
||||
ubyte ubytev;
|
||||
|
||||
long intv;
|
||||
@@ -379,12 +379,12 @@ private final class ABCtoAS
|
||||
return o;
|
||||
}
|
||||
|
||||
ASProgram.Namespace convertNamespace(ref ABCFile.Namespace namespace, int privateIndex)
|
||||
ASProgram.Namespace convertNamespace(ref ABCFile.Namespace namespace, int id)
|
||||
{
|
||||
auto n = new ASProgram.Namespace();
|
||||
n.kind = namespace.kind;
|
||||
n.name = abc.strings[namespace.name];
|
||||
n.privateIndex = privateIndex;
|
||||
n.id = id;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ private final class ABCtoAS
|
||||
}
|
||||
}
|
||||
|
||||
ASProgram.Method convertMethod(ref ABCFile.MethodInfo method)
|
||||
ASProgram.Method convertMethod(ref ABCFile.MethodInfo method, uint id)
|
||||
{
|
||||
auto n = new ASProgram.Method();
|
||||
n.paramTypes.length = method.paramTypes.length;
|
||||
@@ -470,6 +470,7 @@ private final class ABCtoAS
|
||||
n.paramNames.length = method.paramNames.length;
|
||||
foreach (j, name; method.paramNames)
|
||||
n.paramNames[j] = abc.strings[name];
|
||||
n.id = id;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -477,12 +478,12 @@ private final class ABCtoAS
|
||||
{
|
||||
auto n = new ASProgram.Metadata();
|
||||
n.name = abc.strings[md.name];
|
||||
n.items.length = md.items.length;
|
||||
foreach (j, ref item; md.items)
|
||||
{
|
||||
n.items[j].key = abc.strings[item.key];
|
||||
n.items[j].value = abc.strings[item.value];
|
||||
}
|
||||
n.keys.length = md.keys.length;
|
||||
foreach (j, key; md.keys)
|
||||
n.keys[j] = abc.strings[key];
|
||||
n.values.length = md.values.length;
|
||||
foreach (j, value; md.values)
|
||||
n.values[j] = abc.strings[value];
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -582,8 +583,7 @@ private final class ABCtoAS
|
||||
e.varName = multinames[exc.varName];
|
||||
}
|
||||
n.traits = convertTraits(vbody.traits);
|
||||
n.error = vbody.error;
|
||||
n.rawBytes = vbody.rawBytes;
|
||||
n.errors = vbody.errors;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -599,6 +599,9 @@ private final class ABCtoAS
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to convert OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
|
||||
case OpcodeArgumentType.ByteLiteral:
|
||||
r.arguments[i].bytev = instruction.arguments[i].bytev;
|
||||
break;
|
||||
case OpcodeArgumentType.UByteLiteral:
|
||||
r.arguments[i].ubytev = instruction.arguments[i].ubytev;
|
||||
break;
|
||||
@@ -673,8 +676,8 @@ private final class ABCtoAS
|
||||
postConvertMultiname(multiname, multinames[i]);
|
||||
|
||||
methods.length = methodAdded.length = abc.methods.length;
|
||||
foreach (i, ref method; abc.methods)
|
||||
methods[i] = convertMethod(method);
|
||||
foreach (uint i, ref method; abc.methods)
|
||||
methods[i] = convertMethod(method, i);
|
||||
|
||||
metadata.length = abc.metadata.length;
|
||||
foreach (i, ref md; abc.metadata)
|
||||
@@ -742,135 +745,85 @@ private final class AStoABC : ASVisitor
|
||||
}
|
||||
|
||||
/// Maintain an unordered set of values; sort/index by usage count
|
||||
struct ConstantPool(T, bool haveNull = true)
|
||||
struct Pool(T, bool byRef, bool haveNull = true)
|
||||
{
|
||||
alias immutable(T) I;
|
||||
static if (byRef)
|
||||
alias void* Key;
|
||||
else
|
||||
static if (is(T == double))
|
||||
alias ulong Key; // https://issues.dlang.org/show_bug.cgi?id=13420
|
||||
else
|
||||
alias immutable(T) Key;
|
||||
|
||||
Key toKey(T value)
|
||||
{
|
||||
static if (is(T == double))
|
||||
return *cast(ulong*)&value;
|
||||
else
|
||||
return cast(Key)value;
|
||||
}
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hits;
|
||||
T value;
|
||||
uint index;
|
||||
size_t addIndex, index;
|
||||
Key[] parents;
|
||||
|
||||
mixin AutoCompare;
|
||||
|
||||
R processData(R, string prolog, string epilog, H)(ref H handler) const
|
||||
{
|
||||
mixin(prolog);
|
||||
mixin(addAutoField("hits", true));
|
||||
mixin(addAutoField("value"));
|
||||
mixin(epilog);
|
||||
}
|
||||
mixin AutoToString;
|
||||
mixin ProcessAllData;
|
||||
}
|
||||
|
||||
Entry[immutable(T)] pool;
|
||||
Entry[Key] pool;
|
||||
T[] values;
|
||||
|
||||
bool add(T value) // return true if added
|
||||
{
|
||||
if (haveNull && isNull(value))
|
||||
if ((haveNull || byRef) && isNull(value))
|
||||
return false;
|
||||
auto cp = cast(I)value in pool;
|
||||
if (cp is null)
|
||||
auto p = toKey(value) in pool;
|
||||
if (p is null)
|
||||
{
|
||||
pool[cast(I)value] = Entry(1, value);
|
||||
pool[toKey(value)] = Entry(1, value, pool.length);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cp.hits++;
|
||||
p.hits++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool notAdded(T value)
|
||||
{
|
||||
auto ep = cast(I)value in pool;
|
||||
static if (haveNull || byRef)
|
||||
if (isNull(value))
|
||||
return false;
|
||||
auto ep = toKey(value) in pool;
|
||||
if (ep)
|
||||
ep.hits++;
|
||||
return !((haveNull && isNull(value)) || ep);
|
||||
}
|
||||
|
||||
void finalize()
|
||||
{
|
||||
auto all = pool.values;
|
||||
all.sort;
|
||||
enum { NullOffset = haveNull ? 1 : 0 }
|
||||
values.length = all.length + NullOffset;
|
||||
foreach (uint i, ref c; all)
|
||||
{
|
||||
pool[cast(I)c.value].index = i + NullOffset;
|
||||
values[i + NullOffset] = c.value;
|
||||
}
|
||||
}
|
||||
|
||||
uint get(T value)
|
||||
{
|
||||
if (haveNull && isNull(value))
|
||||
return 0;
|
||||
return pool[cast(I)value].index;
|
||||
}
|
||||
}
|
||||
|
||||
/// Pair an index with class instances
|
||||
struct ReferencePool(T : Object)
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
uint hits;
|
||||
void* object;
|
||||
uint addIndex, index;
|
||||
void*[] parents;
|
||||
|
||||
mixin AutoToString;
|
||||
mixin ProcessAllData;
|
||||
}
|
||||
|
||||
Entry[void*] pool;
|
||||
T[] objects;
|
||||
|
||||
bool add(T obj) // return true if added
|
||||
{
|
||||
if (obj is null)
|
||||
return false;
|
||||
auto p = cast(void*)obj;
|
||||
auto rp = p in pool;
|
||||
if (rp is null)
|
||||
{
|
||||
pool[p] = Entry(1, p, to!uint(pool.length));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
rp.hits++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool notAdded(T obj)
|
||||
{
|
||||
auto ep = (cast(void*)obj) in pool;
|
||||
if (ep)
|
||||
ep.hits++;
|
||||
return !(obj is null || ep);
|
||||
return !ep;
|
||||
}
|
||||
|
||||
/// "from" is child of "to", thus "from" must come after "to"
|
||||
void registerDependency(T from, T to)
|
||||
{
|
||||
auto pfrom = (cast(void*)from) in pool;
|
||||
auto pfrom = toKey(from) in pool;
|
||||
assert(pfrom, "Unknown dependency source");
|
||||
auto vto = cast(void*)to;
|
||||
auto vto = toKey(to);
|
||||
auto pto = vto in pool;
|
||||
assert(pto, "Unknown dependency target");
|
||||
assert(!pfrom.parents.contains(vto), "Dependency already set");
|
||||
assert(!pfrom.parents.contains!Key(vto), "Dependency already set");
|
||||
pfrom.parents ~= vto;
|
||||
}
|
||||
|
||||
T[] getPreliminaryObjects()
|
||||
T[] getPreliminaryValues()
|
||||
{
|
||||
return cast(T[])pool.keys;
|
||||
}
|
||||
|
||||
enum { NullOffset = haveNull ? 1 : 0 }
|
||||
|
||||
void finalize()
|
||||
{
|
||||
// create array
|
||||
@@ -880,7 +833,18 @@ private final class AStoABC : ASVisitor
|
||||
all[i++] = &e;
|
||||
|
||||
// sort
|
||||
sort!q{a.hits > b.hits || (a.hits == b.hits && a.addIndex < b.addIndex)}(all);
|
||||
static if (is(T == double))
|
||||
{
|
||||
// Need ref to work around https://issues.dlang.org/show_bug.cgi?id=13998
|
||||
static ulong repr(ref double d) { static assert(double.sizeof == ulong.sizeof); return *cast(ulong*)(&d); }
|
||||
static bool sortPred(Entry* a, Entry* b) { return a.hits > b.hits || (a.hits == b.hits && repr(a.value) < repr(b.value)); }
|
||||
}
|
||||
else
|
||||
static if (byRef)
|
||||
enum sortPred = q{a.hits > b.hits || (a.hits == b.hits && a.addIndex < b.addIndex)};
|
||||
else
|
||||
enum sortPred = q{a.hits > b.hits || (a.hits == b.hits && a.value < b.value)};
|
||||
all.sort!sortPred();
|
||||
|
||||
// topographical sort
|
||||
topSort:
|
||||
@@ -901,18 +865,29 @@ private final class AStoABC : ASVisitor
|
||||
}
|
||||
}
|
||||
|
||||
objects.length = i;
|
||||
values.length = NullOffset + i;
|
||||
foreach (j, e; all)
|
||||
objects[j] = cast(T)e.object;
|
||||
values[NullOffset + j] = e.value;
|
||||
}
|
||||
|
||||
uint get(T obj)
|
||||
uint get(T value)
|
||||
{
|
||||
assert(obj !is null, "Trying to get index of null object");
|
||||
return pool[cast(void*)obj].index;
|
||||
if (haveNull && isNull(value))
|
||||
return 0;
|
||||
return NullOffset + to!uint(pool[toKey(value)].index);
|
||||
}
|
||||
}
|
||||
|
||||
template ConstantPool(T, bool haveNull=true)
|
||||
{
|
||||
alias Pool!(T, false, haveNull) ConstantPool;
|
||||
}
|
||||
|
||||
template ReferencePool(T : Object)
|
||||
{
|
||||
alias Pool!(T, true, false) ReferencePool;
|
||||
}
|
||||
|
||||
ConstantPool!(long) ints;
|
||||
ConstantPool!(ulong) uints;
|
||||
ConstantPool!(double) doubles;
|
||||
@@ -1003,7 +978,7 @@ private final class AStoABC : ASVisitor
|
||||
{
|
||||
ASProgram.Class[ASProgram.Multiname] classByName;
|
||||
|
||||
ASProgram.Class[] classObjects = classes.getPreliminaryObjects();
|
||||
ASProgram.Class[] classObjects = classes.getPreliminaryValues();
|
||||
foreach (c; classObjects)
|
||||
{
|
||||
assert(!(c.instance.name in classByName), "Duplicate class name " ~ c.instance.name.toString());
|
||||
@@ -1021,6 +996,18 @@ private final class AStoABC : ASVisitor
|
||||
}
|
||||
}
|
||||
|
||||
void registerMultinameDependencies()
|
||||
{
|
||||
foreach (m; multinames.getPreliminaryValues())
|
||||
if (m.kind == ASType.TypeName)
|
||||
{
|
||||
multinames.registerDependency(m, m.vTypeName.name);
|
||||
foreach (t; m.vTypeName.params)
|
||||
if (t)
|
||||
multinames.registerDependency(m, t);
|
||||
}
|
||||
}
|
||||
|
||||
this(ASProgram as)
|
||||
{
|
||||
super(as);
|
||||
@@ -1032,6 +1019,7 @@ private final class AStoABC : ASVisitor
|
||||
super.run();
|
||||
|
||||
registerClassDependencies();
|
||||
registerMultinameDependencies();
|
||||
|
||||
ints.finalize();
|
||||
uints.finalize();
|
||||
@@ -1110,18 +1098,18 @@ private final class AStoABC : ASVisitor
|
||||
{
|
||||
auto n = &abc.metadata[i];
|
||||
n.name = strings.get(m.name);
|
||||
n.items.length = m.items.length;
|
||||
foreach (j, ref item; m.items)
|
||||
{
|
||||
n.items[j].key = strings.get(m.items[j].key);
|
||||
n.items[j].value = strings.get(m.items[j].value);
|
||||
}
|
||||
n.keys.length = m.keys.length;
|
||||
foreach (j, key; m.keys)
|
||||
n.keys[j] = strings.get(key);
|
||||
n.values.length = m.values.length;
|
||||
foreach (j, value; m.values)
|
||||
n.values[j] = strings.get(value);
|
||||
}
|
||||
|
||||
ASProgram.MethodBody[] bodies;
|
||||
|
||||
abc.methods.length = methods.objects.length;
|
||||
foreach (i, o; methods.objects)
|
||||
abc.methods.length = methods.values.length;
|
||||
foreach (i, o; methods.values)
|
||||
{
|
||||
auto n = &abc.methods[i];
|
||||
n.paramTypes.length = o.paramTypes.length;
|
||||
@@ -1144,8 +1132,8 @@ private final class AStoABC : ASVisitor
|
||||
bodies ~= o.vbody;
|
||||
}
|
||||
|
||||
abc.instances.length = classes.objects.length;
|
||||
foreach (i, c; classes.objects)
|
||||
abc.instances.length = classes.values.length;
|
||||
foreach (i, c; classes.values)
|
||||
{
|
||||
auto o = c.instance;
|
||||
auto n = &abc.instances[i];
|
||||
@@ -1161,8 +1149,8 @@ private final class AStoABC : ASVisitor
|
||||
n.traits = convertTraits(o.traits);
|
||||
}
|
||||
|
||||
abc.classes.length = classes.objects.length;
|
||||
foreach (i, o; classes.objects)
|
||||
abc.classes.length = classes.values.length;
|
||||
foreach (i, o; classes.values)
|
||||
{
|
||||
auto n = &abc.classes[i];
|
||||
n.cinit = methods.get(o.cinit);
|
||||
@@ -1256,6 +1244,9 @@ private final class AStoABC : ASVisitor
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to convert OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
|
||||
case OpcodeArgumentType.ByteLiteral:
|
||||
r.arguments[i].bytev = instruction.arguments[i].bytev;
|
||||
break;
|
||||
case OpcodeArgumentType.UByteLiteral:
|
||||
r.arguments[i].ubytev = instruction.arguments[i].ubytev;
|
||||
break;
|
||||
@@ -1458,11 +1449,10 @@ class ASVisitor : ASTraitsVisitor
|
||||
if (metadata)
|
||||
{
|
||||
visitString(metadata.name);
|
||||
foreach (ref item; metadata.items)
|
||||
{
|
||||
visitString(item.key);
|
||||
visitString(item.value);
|
||||
}
|
||||
foreach (key; metadata.keys)
|
||||
visitString(key);
|
||||
foreach (value; metadata.values)
|
||||
visitString(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1542,6 +1532,7 @@ class ASVisitor : ASTraitsVisitor
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to visit OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
|
||||
case OpcodeArgumentType.ByteLiteral:
|
||||
case OpcodeArgumentType.UByteLiteral:
|
||||
case OpcodeArgumentType.IntLiteral:
|
||||
case OpcodeArgumentType.UIntLiteral:
|
||||
|
||||
+486
-231
File diff suppressed because it is too large
Load Diff
+43
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011, 2012 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011, 2012, 2014 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -34,7 +34,7 @@ template AutoCompare()
|
||||
alias typeof(this) _AutoDataTypeReference;
|
||||
alias Object _AutoDataOtherTypeReference;
|
||||
|
||||
override hash_t toHash() const { return _AutoDataHash(); }
|
||||
override hash_t toHash() const { try { return _AutoDataHash(); } catch(object.Exception e) { assert(0, e.msg); } }
|
||||
override bool opEquals(Object o) const { return _AutoDataEquals(o); }
|
||||
override int opCmp(Object o) const { return _AutoDataCmp(o); }
|
||||
}
|
||||
@@ -78,7 +78,7 @@ template AutoToString()
|
||||
static if (is(typeof(this)==class))
|
||||
override string toString() const { return _AutoDataToString(); }
|
||||
else // struct
|
||||
string toString() { return _AutoDataToString(); }
|
||||
string toString() const { return _AutoDataToString(); }
|
||||
|
||||
string _AutoDataToString() const
|
||||
{
|
||||
@@ -202,7 +202,7 @@ struct CmpDataHandler(O)
|
||||
else
|
||||
static if (is(typeof(T.opCmp)))
|
||||
enum dataCode = nullCheck!(T, name, reverseStr)
|
||||
~ "{ int _AutoDataCmp = this." ~ name ~ ".opCmp(_AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
|
||||
~ "{ int _AutoDataCmp = this." ~ name ~ ".opCmp(cast()_AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
|
||||
else
|
||||
enum dataCode = "if (this." ~ name ~ " < _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "(-1);" ~
|
||||
"if (this." ~ name ~ " > _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "( 1);";
|
||||
@@ -212,8 +212,46 @@ struct CmpDataHandler(O)
|
||||
|
||||
struct ToStringDataHandler
|
||||
{
|
||||
template getMixinSingle(T, string name)
|
||||
{
|
||||
/*
|
||||
enum getMixinSingle = "
|
||||
static if (is(typeof(_AutoDataResult ~= " ~ name ~ ".toString())))
|
||||
_AutoDataResult ~= " ~ name ~ ".toString();
|
||||
else
|
||||
_AutoDataResult ~= to!string(" ~ name ~ ");
|
||||
";
|
||||
*/
|
||||
static if (is(typeof(T.init is null)))
|
||||
enum getMixinSingle = "_AutoDataResult ~= " ~ name ~ " ? to!string(" ~ name ~ ") : `null`;";
|
||||
else
|
||||
enum getMixinSingle = "_AutoDataResult ~= to!string(" ~ name ~ ");";
|
||||
}
|
||||
|
||||
template getMixinBody(T, string name)
|
||||
{
|
||||
// TODO: arrays of arrays
|
||||
static if (is(T U : U[]) && !is(T : const(char)[]))
|
||||
{
|
||||
enum getMixinBody = "
|
||||
_AutoDataResult ~= ` [ `;
|
||||
foreach (_AutoDataArrayIndex, _AutoDataArrayItem; " ~ name ~ ")
|
||||
{
|
||||
if (_AutoDataArrayIndex) _AutoDataResult ~= ` , `;
|
||||
" ~ getMixinSingle!(U, "_AutoDataArrayItem") ~ "
|
||||
}
|
||||
_AutoDataResult ~= ` ] `;
|
||||
";
|
||||
}
|
||||
else
|
||||
enum getMixinBody = getMixinSingle!(T, name);
|
||||
}
|
||||
|
||||
template getMixin(T, string name, bool reverseSort)
|
||||
{
|
||||
enum getMixin = "_AutoDataResult ~= `" ~ name ~ " = ` ~ to!string(this." ~ name ~ ") ~ ` `;";
|
||||
enum getMixin =
|
||||
"_AutoDataResult ~= `" ~ name ~ " = `;" ~
|
||||
getMixinBody!(T, name) ~
|
||||
"_AutoDataResult ~= ` `;";
|
||||
}
|
||||
}
|
||||
|
||||
+35
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011, 2012 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011, 2012, 2013 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
/// 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.
|
||||
/// You can also pass program names or compilation options on the command-line to override the default ones.
|
||||
|
||||
module build_rabcdasm;
|
||||
|
||||
@@ -33,7 +34,7 @@ version(DigitalMars)
|
||||
else
|
||||
const DEFAULT_COMPILER = "gdmd";
|
||||
|
||||
const DEFAULT_FLAGS = "-w -O -inline";
|
||||
const DEFAULT_FLAGS = "-O -inline";
|
||||
const LZMA_FLAGS = "-version=HAVE_LZMA";
|
||||
|
||||
import std.exception;
|
||||
@@ -52,17 +53,30 @@ void compile(string program)
|
||||
|
||||
void test(string code, string extraFlags=null)
|
||||
{
|
||||
const FN = "test.d";
|
||||
const BASE = "build_rabcdasm_buildtest";
|
||||
const FN = BASE ~ ".d";
|
||||
std.file.write(FN, code);
|
||||
scope(exit) remove(FN);
|
||||
enforce(system(format("rdmd --compiler=%s %s %s %s", compiler, flags, extraFlags, FN)) == 0, "Test failed");
|
||||
scope(exit) foreach (de; dirEntries(".", BASE ~ "*", SpanMode.shallow)) remove(de.name);
|
||||
enforce(system(format("rdmd --force --compiler=%s -od. %s %s %s", compiler, flags, extraFlags, FN)) == 0, "Test failed");
|
||||
stderr.writeln(" >>> OK");
|
||||
}
|
||||
|
||||
int main()
|
||||
void testBug(string description, int bugId, string code)
|
||||
{
|
||||
scope(failure)
|
||||
{
|
||||
stderr.writefln("Compiler bug detected: %s ( https://issues.dlang.org/show_bug.cgi?id=%d ).", description, bugId);
|
||||
stderr.writeln("Try again with a different D compiler, compiler version, or build flags (DCFLAGS environment variable)");
|
||||
}
|
||||
test(code);
|
||||
}
|
||||
|
||||
int main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto programs = ["rabcasm", "rabcdasm", "abcexport", "abcreplace", "swfbinexport", "swfbinreplace", "swfdecompress", "swf7zcompress"];
|
||||
|
||||
compiler = getenv("DC");
|
||||
if (compiler is null)
|
||||
compiler = DEFAULT_COMPILER;
|
||||
@@ -71,11 +85,25 @@ int main()
|
||||
if (flags is null)
|
||||
flags = DEFAULT_FLAGS;
|
||||
|
||||
string[] optionArgs, programArgs;
|
||||
foreach (arg; args[1..$])
|
||||
(arg.startsWith("-") ? optionArgs : programArgs) ~= arg;
|
||||
|
||||
if (optionArgs.length)
|
||||
flags = optionArgs.join(" ");
|
||||
if (programArgs.length)
|
||||
programs = programArgs;
|
||||
|
||||
stderr.writeln("* Checking for working compiler...");
|
||||
test(`
|
||||
void main() {}
|
||||
`);
|
||||
|
||||
stderr.writeln("* Checking for known compiler bugs...");
|
||||
testBug("[REG 2.064] Wrong code with -O on x86_64 for char comparisons", 11508, `
|
||||
import assembler; int main() { foreach (c; "_") if (!Assembler.isWordChar(c)) return 1; return 0; }
|
||||
`);
|
||||
|
||||
bool haveLZMA;
|
||||
|
||||
stderr.writeln("* Checking for LZMA...");
|
||||
@@ -103,7 +131,7 @@ int main()
|
||||
if (haveLZMA)
|
||||
flags ~= " " ~ LZMA_FLAGS;
|
||||
|
||||
foreach (program; ["rabcasm", "rabcdasm", "abcexport", "abcreplace", "swfbinexport", "swfbinreplace", "swfdecompress", "swf7zcompress"])
|
||||
foreach (program; programs)
|
||||
compile(program);
|
||||
|
||||
if (haveLZMA)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2012, 2014 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 common;
|
||||
|
||||
import std.array;
|
||||
import std.path;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
|
||||
string longPath(string s)
|
||||
{
|
||||
version(Windows)
|
||||
{
|
||||
if (s.startsWith(`\\`))
|
||||
return s;
|
||||
else
|
||||
return `\\?\` ~ s.absolutePath().buildNormalizedPath().replace(`/`, `\`);
|
||||
}
|
||||
else
|
||||
return s;
|
||||
}
|
||||
|
||||
File openFile(string fn, string mode)
|
||||
{
|
||||
File f;
|
||||
static if (is(typeof(&f.windowsHandleOpen)))
|
||||
{
|
||||
import core.sys.windows.windows;
|
||||
|
||||
import std.exception;
|
||||
import std.utf;
|
||||
import std.windows.syserror;
|
||||
|
||||
string winMode;
|
||||
foreach (c; mode)
|
||||
switch (c)
|
||||
{
|
||||
case 'r':
|
||||
case 'w':
|
||||
case 'a':
|
||||
case '+':
|
||||
winMode ~= c;
|
||||
break;
|
||||
case 'b':
|
||||
case 't':
|
||||
break;
|
||||
default:
|
||||
assert(false, "Unknown character in mode");
|
||||
}
|
||||
DWORD access, creation;
|
||||
bool append;
|
||||
switch (winMode)
|
||||
{
|
||||
case "r" : access = GENERIC_READ ; creation = OPEN_EXISTING; break;
|
||||
case "r+": access = GENERIC_READ | GENERIC_WRITE; creation = OPEN_EXISTING; break;
|
||||
case "w" : access = GENERIC_WRITE; creation = OPEN_ALWAYS ; break;
|
||||
case "w+": access = GENERIC_READ | GENERIC_WRITE; creation = OPEN_ALWAYS ; break;
|
||||
case "a" : access = GENERIC_WRITE; creation = OPEN_ALWAYS ; append = true; break;
|
||||
case "a+": assert(false, "Not implemented"); // requires two file pointers
|
||||
default: assert(false, "Bad file mode: " ~ mode);
|
||||
}
|
||||
|
||||
auto pathW = toUTF16z(longPath(fn));
|
||||
auto h = CreateFileW(pathW, access, FILE_SHARE_READ, null, creation, 0, HANDLE.init);
|
||||
enforce(h != INVALID_HANDLE_VALUE, "Failed to open file \"" ~ fn ~ "\": " ~ sysErrorString(GetLastError()));
|
||||
|
||||
assert(!append, "'a' mode not implemented");
|
||||
|
||||
f.windowsHandleOpen(h, mode);
|
||||
}
|
||||
else
|
||||
f.open(fn, mode);
|
||||
return f;
|
||||
}
|
||||
@@ -223,7 +223,7 @@ struct lzma_index_iter
|
||||
size_t s;
|
||||
lzma_vli v;
|
||||
}
|
||||
InternalData internal[6];
|
||||
InternalData[6] internal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+688
-302
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2012, 2013, 2014, 2016 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -23,15 +23,16 @@ version(HAVE_LZMA) {} else static assert(0, "LZMA is not available (HAVE_LZMA ve
|
||||
import deimos.lzma;
|
||||
import std.conv;
|
||||
import std.exception;
|
||||
import std.string : format;
|
||||
|
||||
version (Windows)
|
||||
{ pragma(lib, "liblzma"); }
|
||||
else
|
||||
{ pragma(lib, "lzma"); }
|
||||
|
||||
align(1)
|
||||
struct LZMAHeader
|
||||
align(1) struct LZMAHeader
|
||||
{
|
||||
align(1):
|
||||
ubyte compressionParameters;
|
||||
uint dictionarySize;
|
||||
long decompressedSize = -1;
|
||||
@@ -40,61 +41,92 @@ static assert(LZMAHeader.sizeof == 13);
|
||||
|
||||
ubyte[] lzmaDecompress(LZMAHeader header, in ubyte[] compressedData)
|
||||
{
|
||||
enforce(header.decompressedSize > 0, "Decompression with unknown size is unsupported");
|
||||
|
||||
lzma_stream strm;
|
||||
lzma_stream strm;
|
||||
lzmaEnforce(lzma_alone_decoder(&strm, ulong.max), "lzma_alone_decoder");
|
||||
scope(exit) lzma_end(&strm);
|
||||
|
||||
auto outBuf = new ubyte[to!size_t(header.decompressedSize)];
|
||||
strm.next_out = outBuf.ptr;
|
||||
strm.avail_out = outBuf.length;
|
||||
auto outBuf = new ubyte[1024];
|
||||
size_t pos = 0;
|
||||
|
||||
void decompress(in ubyte[] chunk)
|
||||
{
|
||||
strm.next_in = chunk.ptr;
|
||||
strm.avail_in = chunk.length;
|
||||
lzmaEnforce(lzma_code(&strm, lzma_action.LZMA_RUN), "lzma_code");
|
||||
strm.next_in = chunk.ptr;
|
||||
strm.avail_in = chunk.length;
|
||||
|
||||
again:
|
||||
strm.next_out = outBuf.ptr + pos;
|
||||
strm.avail_out = outBuf.length - pos;
|
||||
auto ret = lzma_code(&strm, lzma_action.LZMA_RUN);
|
||||
pos = strm.next_out - outBuf.ptr;
|
||||
|
||||
if (ret == lzma_ret.LZMA_OK && strm.avail_in && !strm.avail_out)
|
||||
{
|
||||
outBuf.length = outBuf.length * 2;
|
||||
goto again;
|
||||
}
|
||||
lzmaEnforce!true(ret, "lzma_code (LZMA_RUN)");
|
||||
enforce(strm.avail_in == 0, "Not all data was read");
|
||||
}
|
||||
|
||||
header.decompressedSize = -1; // Required as Flash uses End-of-Stream marker
|
||||
fixDictSize(header.dictionarySize);
|
||||
decompress(cast(ubyte[])(&header)[0..1]);
|
||||
decompress(compressedData);
|
||||
|
||||
lzmaEnforce(lzma_code(&strm, lzma_action.LZMA_FINISH), "lzma_code");
|
||||
lzmaEnforce!true(lzma_code(&strm, lzma_action.LZMA_FINISH), "lzma_code (LZMA_FINISH)");
|
||||
|
||||
enforce(strm.avail_out == 0, "Decompressed size mismatch");
|
||||
// enforce(strm.avail_out == 0,
|
||||
// "Decompressed size mismatch (expected %d/0x%X, got %d/0x%X)".format(
|
||||
// outBuf.length, outBuf.length,
|
||||
// outBuf.length - strm.avail_out, outBuf.length - strm.avail_out,
|
||||
// ));
|
||||
|
||||
outBuf = outBuf[0..pos];
|
||||
|
||||
return outBuf;
|
||||
}
|
||||
|
||||
ubyte[] lzmaCompress(in ubyte[] decompressedData, LZMAHeader* header)
|
||||
{
|
||||
lzma_options_lzma opts;
|
||||
enforce(lzma_lzma_preset(&opts, 9 | LZMA_PRESET_EXTREME) == false, "lzma_lzma_preset error");
|
||||
lzma_options_lzma opts;
|
||||
enforce(lzma_lzma_preset(&opts, 9 | LZMA_PRESET_EXTREME) == false, "lzma_lzma_preset error");
|
||||
|
||||
lzma_stream strm;
|
||||
lzma_stream strm;
|
||||
lzmaEnforce(lzma_alone_encoder(&strm, &opts), "lzma_alone_encoder");
|
||||
scope(exit) lzma_end(&strm);
|
||||
|
||||
auto outBuf = new ubyte[decompressedData.length];
|
||||
auto outBuf = new ubyte[decompressedData.length * 11 / 10 + 1024];
|
||||
strm.next_out = outBuf.ptr;
|
||||
strm.avail_out = outBuf.length;
|
||||
strm.next_in = decompressedData.ptr;
|
||||
strm.avail_in = decompressedData.length;
|
||||
lzmaEnforce(lzma_code(&strm, lzma_action.LZMA_RUN), "lzma_code");
|
||||
scope(failure) { import std.stdio; writeln("avail_in=", strm.avail_in); }
|
||||
lzmaEnforce(lzma_code(&strm, lzma_action.LZMA_RUN), "lzma_code (LZMA_RUN)");
|
||||
enforce(strm.avail_in == 0, "Not all data was read");
|
||||
enforce(strm.avail_out != 0, "Ran out of compression space");
|
||||
|
||||
lzmaEnforce(lzma_code(&strm, lzma_action.LZMA_FINISH), "lzma_code");
|
||||
lzmaEnforce!true(lzma_code(&strm, lzma_action.LZMA_FINISH), "lzma_code (LZMA_FINISH)");
|
||||
|
||||
*header = *cast(LZMAHeader*)outBuf.ptr;
|
||||
return outBuf[LZMAHeader.sizeof..to!size_t(strm.total_out)];
|
||||
}
|
||||
|
||||
private void lzmaEnforce(lzma_ret v, string f)
|
||||
private void lzmaEnforce(bool STREAM_END_OK=false)(lzma_ret v, string f)
|
||||
{
|
||||
if (v != lzma_ret.LZMA_OK && v != lzma_ret.LZMA_STREAM_END)
|
||||
throw new Exception(text(f, " error: ", v));
|
||||
if (v != lzma_ret.LZMA_OK && (!STREAM_END_OK || v != lzma_ret.LZMA_STREAM_END))
|
||||
throw new Exception(text(f, " error: ", v));
|
||||
}
|
||||
|
||||
/// Work around an artificial lzma_alone_decoder limitation in liblzma
|
||||
/// which prevents it from accepting any streams with a dictionary size
|
||||
/// that is not 2^n or 2^n + 2^(n-1).
|
||||
/// See xz\src\liblzma\common\alone_decoder.c (git commit e7b424d267), line 87
|
||||
private void fixDictSize(ref uint d)
|
||||
{
|
||||
--d;
|
||||
d |= d >> 2;
|
||||
d |= d >> 3;
|
||||
d |= d >> 4;
|
||||
d |= d >> 8;
|
||||
d |= d >> 16;
|
||||
++d;
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011, 2012 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -33,6 +33,7 @@ void main(string[] args)
|
||||
{
|
||||
auto bin = cast(ubyte[])read(args[3]);
|
||||
tag.data = tag.data[0..6] ~ bin;
|
||||
tag.length = cast(uint)tag.data.length;
|
||||
write(args[1], swf.write());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011, 2012 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011, 2012, 2016 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -20,6 +20,7 @@ module swffile;
|
||||
|
||||
import std.conv;
|
||||
import std.exception;
|
||||
import std.string : format;
|
||||
import std.zlib;
|
||||
import zlibx;
|
||||
version (HAVE_LZMA) import lzma;
|
||||
@@ -39,6 +40,7 @@ final class SWFFile
|
||||
|
||||
align(1) struct Header
|
||||
{
|
||||
align(1):
|
||||
char[3] signature;
|
||||
ubyte ver;
|
||||
uint fileLength;
|
||||
@@ -47,6 +49,7 @@ final class SWFFile
|
||||
|
||||
align(1) struct LZMAHeader
|
||||
{
|
||||
align(1):
|
||||
uint compressedLength;
|
||||
ubyte compressionParameters;
|
||||
uint dictionarySize;
|
||||
@@ -63,6 +66,7 @@ final class SWFFile
|
||||
{
|
||||
ushort type;
|
||||
ubyte[] data;
|
||||
uint length; // may be >data.length if file is truncated
|
||||
bool forceLongLength;
|
||||
}
|
||||
|
||||
@@ -112,7 +116,9 @@ private final class SWFReader
|
||||
else
|
||||
enforce(false, "This version was built without LZMA support");
|
||||
}
|
||||
enforce(swf.header.fileLength == buf.length, "Incorrect file length in file header");
|
||||
//enforce(swf.header.fileLength == buf.length,
|
||||
// "Incorrect file length in file header (expected %d, got %d)"
|
||||
// .format(swf.header.fileLength , buf.length));
|
||||
swf.frameSize = readRect();
|
||||
swf.frameRate = readU16();
|
||||
swf.frameCount = readU16();
|
||||
@@ -127,10 +133,12 @@ private final class SWFReader
|
||||
pos += raw.length;
|
||||
}
|
||||
|
||||
/// May read less than len on EOF
|
||||
void[] readRaw(size_t len)
|
||||
{
|
||||
auto data = buf[pos..pos+len];
|
||||
pos += len;
|
||||
auto end = pos+len;
|
||||
auto data = buf[pos..end<$?end:$];
|
||||
pos = end;
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -172,6 +180,7 @@ private final class SWFReader
|
||||
if (length < 0x3F)
|
||||
t.forceLongLength = true;
|
||||
}
|
||||
t.length = length;
|
||||
t.data = cast(ubyte[])readRaw(length);
|
||||
return t;
|
||||
}
|
||||
@@ -261,16 +270,16 @@ private final class SWFWriter
|
||||
foreach (ref tag; swf.tags)
|
||||
{
|
||||
ushort u = cast(ushort)(tag.type << 6);
|
||||
if (tag.data.length < 0x3F && !tag.forceLongLength)
|
||||
if (tag.length < 0x3F && !tag.forceLongLength)
|
||||
{
|
||||
u |= tag.data.length;
|
||||
u |= tag.length;
|
||||
buf ~= toArray(u);
|
||||
}
|
||||
else
|
||||
{
|
||||
u |= 0x3F;
|
||||
buf ~= toArray(u);
|
||||
uint l = to!uint(tag.data.length);
|
||||
uint l = to!uint(tag.length);
|
||||
buf ~= toArray(l);
|
||||
}
|
||||
buf ~= tag.data;
|
||||
|
||||
+35
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010, 2011, 2012 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011, 2012, 2013 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -18,18 +18,50 @@
|
||||
|
||||
module swflzmacompress;
|
||||
|
||||
import std.exception;
|
||||
import std.file;
|
||||
import std.getopt;
|
||||
import std.string;
|
||||
import swffile;
|
||||
|
||||
void main(string[] args)
|
||||
{
|
||||
bool force, updateVersion;
|
||||
getopt(args,
|
||||
"--force", &force,
|
||||
"--update-version", &updateVersion,
|
||||
);
|
||||
|
||||
if (args.length == 1)
|
||||
throw new Exception("No file specified");
|
||||
enum MIN_LZMA_VER = 13;
|
||||
foreach (arg; args[1..$])
|
||||
{
|
||||
auto swf = SWFFile.read(cast(ubyte[])read(arg));
|
||||
if (swf.header.signature[0] == cast(ubyte)'Z')
|
||||
throw new Exception("Already LZMA-compressed");
|
||||
enforce(swf.header.signature[0] != cast(ubyte)'Z', "Already LZMA-compressed");
|
||||
if (swf.header.ver < MIN_LZMA_VER)
|
||||
{
|
||||
if (updateVersion)
|
||||
{
|
||||
if (swf.header.ver < 8 && !force)
|
||||
throw new Exception(format(
|
||||
"SWF version %d has different file format than version %d, " ~
|
||||
"required for LZMA. Resulting file may not work. " ~
|
||||
"Use --force to override and update version anyway.",
|
||||
swf.header.ver, MIN_LZMA_VER
|
||||
));
|
||||
swf.header.ver = MIN_LZMA_VER;
|
||||
}
|
||||
else
|
||||
if (!force)
|
||||
throw new Exception(format(
|
||||
"SWF version %d is too old to support SWF LZMA compression, " ~
|
||||
"which requires version %d. " ~
|
||||
"Use --update-version to update the version number, " ~
|
||||
"or --force to compress anyway without updating it.",
|
||||
swf.header.ver, MIN_LZMA_VER
|
||||
));
|
||||
}
|
||||
swf.header.signature[0] = cast(ubyte)'Z'; // LZMA
|
||||
write(arg, swf.write());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user