Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f0ef64b83 | ||
|
|
701e5b6f80 | ||
|
|
fc46ad02e9 | ||
|
|
e7a5dd92c6 | ||
|
|
352456d257 | ||
|
|
d6544ae7cb | ||
|
|
37cd4b415b | ||
|
|
62d0756dbc | ||
|
|
48b221535f | ||
|
|
beb7229bf8 | ||
|
|
296196ff71 | ||
|
|
170e7d6a8b | ||
|
|
2c63eb592a | ||
|
|
16a5f649de | ||
|
|
111cd75eb4 | ||
|
|
e103048af6 | ||
|
|
61df70dd0e | ||
|
|
51536e4add | ||
|
|
fb5c03ada5 | ||
|
|
2226b0059f | ||
|
|
69cf64b85c | ||
|
|
2b49e77267 | ||
|
|
fab198a648 | ||
|
|
4e4fd2b56d | ||
|
|
7e4f7f5bac | ||
|
|
7a92e9fe81 | ||
|
|
41002f7085 | ||
|
|
3b131fa932 | ||
|
|
34316bd4c9 | ||
|
|
e49a6b3fec | ||
|
|
bfa02943c6 | ||
|
|
cf8835b87d | ||
|
|
4c411ac1f2 | ||
|
|
76cadf30cf | ||
|
|
2498673231 | ||
|
|
a1b9ea05c0 | ||
|
|
a766cf5919 | ||
|
|
e6f54db4c1 | ||
|
|
51e82bf985 | ||
|
|
282e8cbfc1 | ||
|
|
4f53b789ee | ||
|
|
0c5cbde348 | ||
|
|
ff799d002f | ||
|
|
1bf7a9a593 | ||
|
|
1167b14471 | ||
|
|
d3e8e47d54 | ||
|
|
3f9be2b192 | ||
|
|
a690c82b7d | ||
|
|
2d7886bb0b | ||
|
|
4cb179a1c6 | ||
|
|
0031be1f75 | ||
|
|
c7b7a5bce8 | ||
|
|
ac5d4b5e74 | ||
|
|
eb07838668 | ||
|
|
b80fe48bee | ||
|
|
a083ed265f | ||
|
|
986ea47d03 | ||
|
|
3520d34cc2 | ||
|
|
58ab6e3bf0 | ||
|
|
413aa9b5f0 | ||
|
|
5da1e26835 | ||
|
|
53a4173fd3 | ||
|
|
5737243606 | ||
|
|
be727e67c6 | ||
|
|
9d67c46835 | ||
|
|
756a2fd060 | ||
|
|
8ad1f85a45 | ||
|
|
ad6ae5d087 | ||
|
|
580a1364d0 | ||
|
|
d4f90f9c01 | ||
|
|
cecff913f1 | ||
|
|
7a4c78dac3 | ||
|
|
ee47075fda | ||
|
|
100f6b6bf4 |
+102
-45
@@ -1,45 +1,102 @@
|
||||
RABCDAsm Changelog
|
||||
==================
|
||||
|
||||
RABCDAsm v1.4 (2011.02.xx)
|
||||
--------------------------
|
||||
|
||||
* Add support for forward-references for TypeName-kind Multinames
|
||||
* Correctly order classes by dependencies (extends/implements) and reference
|
||||
count
|
||||
* Finish Metadata support
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.3 (2010.11.11)
|
||||
--------------------------
|
||||
|
||||
* Fixed double precision problem
|
||||
* 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
|
||||
* Documentation markdown fixes
|
||||
|
||||
RABCDAsm v1.2 (2010.11.06)
|
||||
--------------------------
|
||||
|
||||
* 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
|
||||
|
||||
RABCDAsm v1.1 (2010.06.30)
|
||||
--------------------------
|
||||
|
||||
* Private namespaces are now referenced by auto-generated names
|
||||
* Use `:` to delimit namespace and name in QNames for consistency
|
||||
* Warning: this breaks compatibility with v1.0 disassemblies
|
||||
* Fixed relative include paths
|
||||
* Add optional byte offsets to labels, which allows lossless representation
|
||||
of jumps inside instructions and outside the function bounds
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.0 (2010.05.05)
|
||||
--------------------------
|
||||
|
||||
* Initial release.
|
||||
RABCDAsm Changelog
|
||||
==================
|
||||
|
||||
RABCDAsm v1.8 (2011.07.06)
|
||||
--------------------------
|
||||
|
||||
* Fixed handling of orphan methods with multiple references
|
||||
* Treat out-of-bounds namespace/multiname indices as null
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.7 (2011.06.14)
|
||||
--------------------------
|
||||
|
||||
* Fixed handling of invalid UTF-8 in strings
|
||||
* Fixed handling of null namespaces and multinames
|
||||
* Fixed handling of exception info in corrupted method bodies
|
||||
* Fixed handling of orphan private namespaces
|
||||
* Fixed handling of out-of-bounds class/method indices
|
||||
* Disassembler now creates one file per class and script-level method
|
||||
* Disassembler now outputs blank lines after `si32` etc. instructions as well
|
||||
* `abcexport` now delimits the index with a dash in output filenames
|
||||
* Improved shortening of `double` literals
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.6 (2011.05.13)
|
||||
--------------------------
|
||||
|
||||
* Overhaul of the structure of disassembler-generated files
|
||||
* Private namespace names and `refid`s for nested classes are now generated
|
||||
from their referenced contexts
|
||||
* This also fixes problems caused by private namespaces being referenced
|
||||
in multiple distinct contexts
|
||||
* One `#include`d file per script instead of per class
|
||||
* Nested classes, script initializers and other script traits are now in
|
||||
the same file as their main class
|
||||
* Inline methods are now in separate files, placed according to their usage
|
||||
context
|
||||
* Private namespaces are sorted by their autogenerated names in the
|
||||
`.privatens.asasm` file
|
||||
* No syntax changes, output is backwards-compatible
|
||||
* Improved escaping of file names
|
||||
* Output correct opcode error locations
|
||||
* Disassembler will now refuse to overwrite files
|
||||
* `rabcdasm` will now always generate output in the same directory as the
|
||||
input file
|
||||
* Failure to decode a method body is now not a fatal error
|
||||
* Aggressively try to shorten double literals to their shortest representation
|
||||
* Added the `swfbinexport` and `swfbinreplace` utilities, to aid in manipulating
|
||||
contents of `DefineBinaryData` tags
|
||||
* The package can now be built using a simple, dedicated build tool
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.5 (2011.04.14)
|
||||
--------------------------
|
||||
|
||||
* Fixed v1.4 constant pool regression
|
||||
* Added support for memory-access and sign-extend opcodes
|
||||
* Speed optimizations
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.4 (2011.03.07)
|
||||
--------------------------
|
||||
|
||||
* Source code ported to D2
|
||||
* Add support for forward-references for TypeName-kind Multinames
|
||||
* Correctly order classes by dependencies (extends/implements) and reference
|
||||
count
|
||||
* Finish Metadata support
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.3 (2010.11.11)
|
||||
--------------------------
|
||||
|
||||
* Fixed double precision problem
|
||||
* 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
|
||||
* Documentation markdown fixes
|
||||
|
||||
RABCDAsm v1.2 (2010.11.06)
|
||||
--------------------------
|
||||
|
||||
* 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
|
||||
|
||||
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
|
||||
of jumps inside instructions and outside the function bounds
|
||||
* Documentation updates
|
||||
|
||||
RABCDAsm v1.0 (2010.05.05)
|
||||
--------------------------
|
||||
|
||||
* Initial release.
|
||||
|
||||
@@ -11,16 +11,21 @@ These are:
|
||||
* `abcreplace` - replaces ABC in SWF files
|
||||
* `swfdecompress` - decompresses zlib-compressed SWF files
|
||||
* `swf7zcompress` - (re-)compress the contents of a SWF using 7-Zip
|
||||
* `swfbinexport` / `swfbinreplace` - extract/replace contents of binary
|
||||
data tags from SWF files
|
||||
|
||||
`abcexport` and `abcreplace` are reimplementations of similar utilities from
|
||||
my [swfutilsex][] Java package, however these work faster as they do not parse
|
||||
the SWF files as deeply.
|
||||
`swfdecompress` is ancilliary and is only useful for debugging and studying of
|
||||
`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.
|
||||
`swf7zcompress` is an utility to further reduce the size of SWF files. It uses
|
||||
[7-Zip][] to compress the data better than the standard zlib library would. It
|
||||
requires that the `7z` command-line program be installed and in `PATH`.
|
||||
`swfbinexport` and `swfbinreplace` aid in the manipulation of
|
||||
`DefineBinaryData` tags in SWF files (some files may contain nested SWF files
|
||||
stored in these tags).
|
||||
|
||||
[RABCDAsm]: http://github.com/CyberShadow/RABCDAsm
|
||||
[swfutilsex]: http://github.com/CyberShadow/swfutilsex
|
||||
@@ -48,27 +53,23 @@ bytecode with the following properties:
|
||||
Compiling from source
|
||||
---------------------
|
||||
|
||||
RABCDAsm is written in the [D programming language, version 1][d1]. It uses one
|
||||
third-party library, [std2][] (for some fancy template stuff).
|
||||
RABCDAsm is written in the [D programming language, version 2][d2].
|
||||
|
||||
Assuming you have [dmd][], [git][] and [svn][] installed, compiling should be
|
||||
as straight-forward as:
|
||||
Assuming you have [git][] and a D2 compiler, such as [dmd][] or [gdc][]
|
||||
installed, compiling should be as straight-forward as:
|
||||
|
||||
git clone git://github.com/CyberShadow/RABCDAsm.git
|
||||
cd RABCDAsm
|
||||
svn co http://svn.dsource.org/projects/std2/trunk/std2/std2
|
||||
dmd rabcdasm abcfile asprogram disassembler autodata murmurhash2a
|
||||
dmd rabcasm abcfile asprogram assembler autodata murmurhash2a
|
||||
dmd abcexport swffile zlibx
|
||||
dmd abcreplace swffile zlibx
|
||||
dmd swfdecompress swffile zlibx
|
||||
dmd swf7zcompress swffile zlibx
|
||||
dmd -run build_rabcdasm
|
||||
|
||||
[d1]: http://www.digitalmars.com/d/1.0/
|
||||
[std2]: http://dsource.org/projects/std2
|
||||
Substitute `dmd` with `gdmd` if you're using gdc. You can use the `DC` and
|
||||
`DCFLAGS` environment variables to override the detected compiler and default
|
||||
compilation flags (`-O -inline`).
|
||||
|
||||
[d2]: http://www.digitalmars.com/d/2.0/
|
||||
[dmd]: http://www.digitalmars.com/d/download.html
|
||||
[gdc]: http://bitbucket.org/goshawk/gdc/
|
||||
[git]: http://git-scm.com/
|
||||
[svn]: http://subversion.apache.org/
|
||||
|
||||
Pre-compiled binaries
|
||||
---------------------
|
||||
@@ -86,25 +87,29 @@ To begin hacking on a SWF file:
|
||||
|
||||
abcexport file.swf
|
||||
|
||||
This will create `file0.abc` ... `fileN.abc` (often just `file0.abc`). Each
|
||||
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 file0.abc
|
||||
rabcdasm file-0.abc
|
||||
|
||||
This will create a `file0` directory, which will contain `file0.main.asasm`
|
||||
(the main program file), `file0.privatens.asasm` (private namespace alias
|
||||
definitions) and a file per ActionScript class.
|
||||
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 file0/file0.main.asasm
|
||||
abcreplace file0.swf 0 file0/file0.main.abc
|
||||
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
|
||||
======
|
||||
|
||||
@@ -246,7 +251,7 @@ 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 (`file0.privatens.asasm`).
|
||||
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`
|
||||
@@ -300,9 +305,9 @@ be instantiated in two ways:
|
||||
although variables are defined using a string syntax, they are not
|
||||
inserted as a string using this syntax. Thus, the code:
|
||||
|
||||
#set str "Hello, world!"
|
||||
...
|
||||
pushstring $str
|
||||
#set str "Hello, world!"
|
||||
...
|
||||
pushstring $str
|
||||
|
||||
will expand to `pushstring Hello, world!`, which will result in an error.
|
||||
To correct the problem, add escaped quotes around the variable contents
|
||||
@@ -388,34 +393,38 @@ RABCDAsm users.
|
||||
|
||||
2. If you plan on making non-trivial changes to SWF files, you should install
|
||||
the [debug Flash Player][]. This will allow you to see validation and
|
||||
run-time error messages, instead of simply getting a blank rectangle.
|
||||
run-time error messages, instead of simply getting an empty window.
|
||||
|
||||
[debug Flash Player]: http://www.adobe.com/support/flashplayer/downloads.html
|
||||
|
||||
3. The [Fiddler][] Web Debugging Proxy can be very useful for analysing
|
||||
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);
|
||||
}
|
||||
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
|
||||
===========
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -20,7 +20,7 @@ module abcexport;
|
||||
|
||||
import std.file;
|
||||
import std.path;
|
||||
import std.string;
|
||||
import std.conv;
|
||||
import std.stdio;
|
||||
import swffile;
|
||||
|
||||
@@ -45,11 +45,11 @@ void main(string[] args)
|
||||
while (*p++) {} // skip name
|
||||
abc = tag.data[p-tag.data.ptr..$];
|
||||
}
|
||||
write(getName(arg) ~ .toString(count++) ~ ".abc", abc);
|
||||
std.file.write(getName(arg) ~ "-" ~ to!string(count++) ~ ".abc", abc);
|
||||
}
|
||||
if (count == 0)
|
||||
throw new Exception("No DoABC tags found");
|
||||
}
|
||||
catch (Object o)
|
||||
writefln("Error while processing %s: %s", arg, o);
|
||||
catch (Exception e)
|
||||
writefln("Error while processing %s: %s", arg, e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -19,9 +19,11 @@
|
||||
module abcfile;
|
||||
|
||||
import std.string : format; // exception formatting
|
||||
import std.conv;
|
||||
import std.exception;
|
||||
|
||||
/**
|
||||
* Implements a shallow representation of an .abc file.
|
||||
/**
|
||||
* Implements a shallow representation of an .abc file.
|
||||
* Loading and saving an .abc file using this class should produce
|
||||
* output identical to the input.
|
||||
*/
|
||||
@@ -181,7 +183,7 @@ class ABCFile
|
||||
|
||||
// TraitAttributes bitmask
|
||||
ubyte attr() { return cast(ubyte)(kindAttr >> 4); }
|
||||
void attr(ubyte value) { kindAttr = (kindAttr&0xF) | (value<<4); }
|
||||
void attr(ubyte value) { kindAttr = cast(ubyte)((kindAttr&0xF) | (value<<4)); }
|
||||
}
|
||||
|
||||
struct Class
|
||||
@@ -206,6 +208,9 @@ class ABCFile
|
||||
Instruction[] instructions;
|
||||
ExceptionInfo[] exceptions;
|
||||
TraitsInfo[] traits;
|
||||
|
||||
string error;
|
||||
ubyte[] rawBytes;
|
||||
}
|
||||
|
||||
/// Destination for a jump or exception block boundary
|
||||
@@ -218,7 +223,7 @@ class ABCFile
|
||||
uint index; /// instruction index
|
||||
int offset; /// signed offset relative to said instruction
|
||||
}
|
||||
private int absoluteOffset; /// internal temporary value used during reading and writing
|
||||
private ptrdiff_t absoluteOffset; /// internal temporary value used during reading and writing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +392,7 @@ enum MethodFlags : ubyte
|
||||
NEED_REST = 0x04, // This flag creates an ActionScript 3.0 rest arguments array. Must not be used with NEED_ARGUMENTS. See Chapter 3.
|
||||
HAS_OPTIONAL = 0x08, // Must be set if this method has optional parameters and the options field is present in this method_info structure.
|
||||
SET_DXNS = 0x40, // Must be set if this method uses the dxns or dxnslate opcodes.
|
||||
HAS_PARAM_NAMES = 0x80, // Must be set when the param_names field is present in this method_info structure.
|
||||
HAS_PARAM_NAMES = 0x80, // Must be set when the param_names field is present in this method_info structure.
|
||||
}
|
||||
|
||||
const string[8] MethodFlagNames = ["NEED_ARGUMENTS", "NEED_ACTIVATION", "NEED_REST", "HAS_OPTIONAL", "0x10", "0x20", "SET_DXNS", "HAS_PARAM_NAMES"];
|
||||
@@ -708,16 +713,16 @@ const OpcodeInfo[256] opcodeInfo = [
|
||||
/* 0x32 */ {"hasnext2", [OpcodeArgumentType.UIntLiteral, OpcodeArgumentType.UIntLiteral]},
|
||||
/* 0x33 */ {"pushdecimal", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x34 */ {"pushdnan", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x35 */ {"li8", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x36 */ {"li16", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x37 */ {"li32", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x38 */ {"lf32", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x39 */ {"lf64", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x3A */ {"si8", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x3B */ {"si16", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x3C */ {"si32", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x3D */ {"sf32", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x3E */ {"sf64", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x35 */ {"li8", []},
|
||||
/* 0x36 */ {"li16", []},
|
||||
/* 0x37 */ {"li32", []},
|
||||
/* 0x38 */ {"lf32", []},
|
||||
/* 0x39 */ {"lf64", []},
|
||||
/* 0x3A */ {"si8", []},
|
||||
/* 0x3B */ {"si16", []},
|
||||
/* 0x3C */ {"si32", []},
|
||||
/* 0x3D */ {"sf32", []},
|
||||
/* 0x3E */ {"sf64", []},
|
||||
/* 0x3F */ {"0x3F", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x40 */ {"newfunction", [OpcodeArgumentType.Method]},
|
||||
/* 0x41 */ {"call", [OpcodeArgumentType.UIntLiteral]},
|
||||
@@ -735,9 +740,9 @@ const OpcodeInfo[256] opcodeInfo = [
|
||||
/* 0x4D */ {"callinterface", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x4E */ {"callsupervoid", [OpcodeArgumentType.Multiname, OpcodeArgumentType.UIntLiteral]},
|
||||
/* 0x4F */ {"callpropvoid", [OpcodeArgumentType.Multiname, OpcodeArgumentType.UIntLiteral]},
|
||||
/* 0x50 */ {"sxi1", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x51 */ {"sxi8", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x52 */ {"sxi16", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x50 */ {"sxi1", []},
|
||||
/* 0x51 */ {"sxi8", []},
|
||||
/* 0x52 */ {"sxi16", []},
|
||||
/* 0x53 */ {"applytype", [OpcodeArgumentType.UIntLiteral]},
|
||||
/* 0x54 */ {"0x54", [OpcodeArgumentType.Unknown]},
|
||||
/* 0x55 */ {"newobject", [OpcodeArgumentType.UIntLiteral]},
|
||||
@@ -992,13 +997,13 @@ private final class ABCReader
|
||||
foreach (ref value; abc.bodies)
|
||||
value = readMethodBody();
|
||||
}
|
||||
catch (Object o)
|
||||
throw new Exception(format("Error at %d (0x%X): %s", pos, pos, o));
|
||||
catch (Exception e)
|
||||
throw new Exception(format("Error at %d (0x%X):", pos, pos), e);
|
||||
}
|
||||
|
||||
ubyte readU8()
|
||||
{
|
||||
assert(pos < buf.length);
|
||||
enforce(pos < buf.length, "End of file reached");
|
||||
return buf[pos++];
|
||||
}
|
||||
|
||||
@@ -1046,7 +1051,7 @@ private final class ABCReader
|
||||
|
||||
void readExact(void* ptr, size_t len)
|
||||
{
|
||||
assert(pos+len <= buf.length);
|
||||
enforce(pos+len <= buf.length, "End of file reached");
|
||||
(cast(ubyte*)ptr)[0..len] = buf[pos..pos+len];
|
||||
pos += len;
|
||||
}
|
||||
@@ -1061,8 +1066,9 @@ private final class ABCReader
|
||||
|
||||
string readString()
|
||||
{
|
||||
string s = new char[readU30()];
|
||||
readExact(s.ptr, s.length);
|
||||
char[] buf = new char[readU30()];
|
||||
readExact(buf.ptr, buf.length);
|
||||
string s = assumeUnique(buf);
|
||||
if (s.length == 0)
|
||||
s = ""; // not null!
|
||||
return s;
|
||||
@@ -1275,17 +1281,18 @@ private final class ABCReader
|
||||
r.instructions = null;
|
||||
|
||||
size_t len = readU30();
|
||||
uint[] instructionAtOffset = new uint[len];
|
||||
auto instructionAtOffset = new uint[len];
|
||||
r.rawBytes = buf[pos..pos+len];
|
||||
|
||||
void translateLabel(ref ABCFile.Label label)
|
||||
{
|
||||
int absoluteOffset = label.absoluteOffset;
|
||||
int instructionOffset = absoluteOffset;
|
||||
auto absoluteOffset = label.absoluteOffset;
|
||||
auto instructionOffset = absoluteOffset;
|
||||
while (true)
|
||||
{
|
||||
if (instructionOffset >= cast(int)len)
|
||||
if (instructionOffset >= len)
|
||||
{
|
||||
label.index = r.instructions.length;
|
||||
label.index = to!uint(r.instructions.length);
|
||||
instructionOffset = len;
|
||||
break;
|
||||
}
|
||||
@@ -1302,26 +1309,28 @@ private final class ABCReader
|
||||
}
|
||||
instructionOffset--;
|
||||
}
|
||||
label.offset = absoluteOffset-instructionOffset;
|
||||
label.offset = to!int(absoluteOffset-instructionOffset);
|
||||
}
|
||||
|
||||
size_t start = pos;
|
||||
size_t end = pos + len;
|
||||
|
||||
size_t offset() { return pos - start; }
|
||||
|
||||
try
|
||||
{
|
||||
size_t start = pos;
|
||||
size_t end = pos + len;
|
||||
|
||||
uint offset() { return pos - start; }
|
||||
|
||||
instructionAtOffset[] = uint.max;
|
||||
uint[] instructionOffsets;
|
||||
size_t[] instructionOffsets;
|
||||
while (pos < end)
|
||||
{
|
||||
uint instructionOffset = offset;
|
||||
instructionAtOffset[instructionOffset] = r.instructions.length;
|
||||
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)
|
||||
switch (type)
|
||||
final switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to decode OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
@@ -1361,9 +1370,6 @@ private final class ABCReader
|
||||
foreach (ref label; instruction.arguments[i].switchTargets)
|
||||
label.absoluteOffset = instructionOffset + readS24();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
r.instructions ~= instruction;
|
||||
instructionOffsets ~= instructionOffset;
|
||||
@@ -1388,8 +1394,14 @@ private final class ABCReader
|
||||
default:
|
||||
break;
|
||||
}
|
||||
pos = end;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
r.instructions = null;
|
||||
r.error = e.msg;
|
||||
instructionAtOffset[] = 0;
|
||||
}
|
||||
pos = end;
|
||||
|
||||
r.exceptions.length = readU30();
|
||||
foreach (ref value; r.exceptions)
|
||||
@@ -1471,7 +1483,7 @@ private final class ABCWriter
|
||||
foreach (ref value; abc.instances)
|
||||
writeInstance(value);
|
||||
|
||||
assert(abc.classes.length == abc.instances.length);
|
||||
assert(abc.classes.length == abc.instances.length, "Number of classes and instances differs");
|
||||
foreach (ref value; abc.classes)
|
||||
writeClass(value);
|
||||
|
||||
@@ -1506,7 +1518,6 @@ private final class ABCWriter
|
||||
writeU8(cast(ubyte)(v>>16));
|
||||
}
|
||||
|
||||
/// Note: may return values larger than 0xFFFFFFFF.
|
||||
void writeU32(ulong v)
|
||||
{
|
||||
if ( v < 128)
|
||||
@@ -1546,12 +1557,13 @@ private final class ABCWriter
|
||||
writeU32(cast(ulong)v);
|
||||
}
|
||||
|
||||
void writeU30(uint v)
|
||||
void writeU30(ulong v)
|
||||
{
|
||||
enforce(v < (1<<30));
|
||||
writeU32(v);
|
||||
}
|
||||
|
||||
void writeExact(void* ptr, size_t len)
|
||||
void writeExact(const(void)* ptr, size_t len)
|
||||
{
|
||||
while (pos+len > buf.length)
|
||||
buf.length = buf.length * 2;
|
||||
@@ -1643,7 +1655,7 @@ private final class ABCWriter
|
||||
}
|
||||
if (v.flags & MethodFlags.HAS_PARAM_NAMES)
|
||||
{
|
||||
assert(v.paramNames.length == v.paramTypes.length);
|
||||
assert(v.paramNames.length == v.paramTypes.length, "Mismatching number of parameter names and types");
|
||||
foreach (value; v.paramNames)
|
||||
writeU30(value);
|
||||
}
|
||||
@@ -1745,23 +1757,24 @@ private final class ABCWriter
|
||||
writeU30(v.initScopeDepth);
|
||||
writeU30(v.maxScopeDepth);
|
||||
|
||||
uint[] instructionOffsets = new uint[v.instructions.length+1];
|
||||
auto instructionOffsets = new size_t[v.instructions.length+1];
|
||||
|
||||
uint resolveLabel(ref ABCFile.Label label) { return instructionOffsets[label.index]+label.offset; }
|
||||
ptrdiff_t resolveLabel(ref ABCFile.Label label) { return instructionOffsets[label.index]+label.offset; }
|
||||
|
||||
{
|
||||
// we don't know the length before writing all the instructions - swap buffer with a temporary one
|
||||
auto globalBuf = buf;
|
||||
auto globalPos = pos;
|
||||
buf = new ubyte[1024];
|
||||
static ubyte[1024*16] methodBuf;
|
||||
buf = methodBuf[];
|
||||
pos = 0;
|
||||
|
||||
struct Fixup { ABCFile.Label target; uint pos, base; }
|
||||
struct Fixup { ABCFile.Label target; size_t pos, base; }
|
||||
Fixup[] fixups;
|
||||
|
||||
foreach (ii, ref instruction; v.instructions)
|
||||
{
|
||||
uint instructionOffset = pos;
|
||||
auto instructionOffset = pos;
|
||||
instructionOffsets[ii] = instructionOffset;
|
||||
|
||||
writeU8(instruction.opcode);
|
||||
@@ -1770,7 +1783,7 @@ private final class ABCWriter
|
||||
throw new Exception("Mismatching number of arguments");
|
||||
|
||||
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
|
||||
switch (type)
|
||||
final switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to encode OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
@@ -1816,9 +1829,6 @@ private final class ABCWriter
|
||||
writeS24(0);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1828,7 +1838,7 @@ private final class ABCWriter
|
||||
foreach (ref fixup; fixups)
|
||||
{
|
||||
pos = fixup.pos;
|
||||
writeS24(resolveLabel(fixup.target)-fixup.base);
|
||||
writeS24(to!int(cast(ptrdiff_t)(resolveLabel(fixup.target)-fixup.base)));
|
||||
}
|
||||
|
||||
auto code = buf;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -27,7 +27,7 @@ void main(string[] args)
|
||||
if (args.length != 4)
|
||||
throw new Exception("Bad arguments. Usage: abcreplace file.swf index code.abc");
|
||||
auto swf = SWFFile.read(cast(ubyte[])read(args[1]));
|
||||
auto index = toUint(args[2]);
|
||||
auto index = to!uint(args[2]);
|
||||
uint count;
|
||||
foreach (ref tag; swf.tags)
|
||||
if ((tag.type == TagType.DoABC || tag.type == TagType.DoABC2) && count++ == index)
|
||||
|
||||
+87
-106
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -18,10 +18,12 @@
|
||||
|
||||
module asprogram;
|
||||
|
||||
import std.algorithm;
|
||||
import core.stdc.string;
|
||||
import abcfile;
|
||||
import autodata;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Represents a hierarchically-organized ActionScript program,
|
||||
* with all constants expanded and bytecode disassembled to separate instructions.
|
||||
*/
|
||||
@@ -41,16 +43,8 @@ final class ASProgram
|
||||
uint privateIndex; // unique index for private namespaces
|
||||
|
||||
mixin AutoCompare;
|
||||
//mixin ProcessAllData;
|
||||
|
||||
R processData(R, string prolog, string epilog, H)(ref H handler)
|
||||
{
|
||||
mixin(prolog);
|
||||
mixin(addAutoField("kind"));
|
||||
mixin(addAutoField("name"));
|
||||
mixin(addAutoField("privateIndex"));
|
||||
mixin(epilog);
|
||||
}
|
||||
mixin AutoToString;
|
||||
mixin ProcessAllData;
|
||||
}
|
||||
|
||||
static class Multiname
|
||||
@@ -89,7 +83,7 @@ final class ASProgram
|
||||
mixin AutoCompare;
|
||||
mixin AutoToString;
|
||||
|
||||
R processData(R, string prolog, string epilog, H)(ref H handler)
|
||||
R processData(R, string prolog, string epilog, H)(ref H handler) const
|
||||
{
|
||||
mixin(prolog);
|
||||
mixin(addAutoField("kind"));
|
||||
@@ -192,7 +186,7 @@ final class ASProgram
|
||||
}
|
||||
|
||||
struct Value
|
||||
{
|
||||
{
|
||||
ASType vkind;
|
||||
union
|
||||
{
|
||||
@@ -244,7 +238,7 @@ final class ASProgram
|
||||
|
||||
Instance instance;
|
||||
|
||||
string toString()
|
||||
override string toString()
|
||||
{
|
||||
return instance.name.toString();
|
||||
}
|
||||
@@ -266,6 +260,9 @@ final class ASProgram
|
||||
Instruction[] instructions;
|
||||
Exception[] exceptions;
|
||||
Trait[] traits;
|
||||
|
||||
string error;
|
||||
ubyte[] rawBytes;
|
||||
}
|
||||
|
||||
struct Instruction
|
||||
@@ -452,6 +449,7 @@ private final class ABCtoAS
|
||||
n.vTypeName.params.length = multiname.TypeName.params.length;
|
||||
foreach (j, param; multiname.TypeName.params)
|
||||
n.vTypeName.params[j] = multinames[param];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -584,6 +582,8 @@ private final class ABCtoAS
|
||||
e.varName = multinames[exc.varName];
|
||||
}
|
||||
n.traits = convertTraits(vbody.traits);
|
||||
n.error = vbody.error;
|
||||
n.rawBytes = vbody.rawBytes;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -594,7 +594,7 @@ private final class ABCtoAS
|
||||
r.arguments.length = instruction.arguments.length;
|
||||
|
||||
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
|
||||
switch (type)
|
||||
final switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to convert OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
@@ -622,16 +622,16 @@ private final class ABCtoAS
|
||||
r.arguments[i].stringv = abc.strings[instruction.arguments[i].index];
|
||||
break;
|
||||
case OpcodeArgumentType.Namespace:
|
||||
r.arguments[i].namespacev = namespaces[instruction.arguments[i].index];
|
||||
r.arguments[i].namespacev = namespaces.checkedGet(instruction.arguments[i].index);
|
||||
break;
|
||||
case OpcodeArgumentType.Multiname:
|
||||
r.arguments[i].multinamev = multinames[instruction.arguments[i].index];
|
||||
r.arguments[i].multinamev = multinames.checkedGet(instruction.arguments[i].index);
|
||||
break;
|
||||
case OpcodeArgumentType.Class:
|
||||
r.arguments[i].classv = classes[instruction.arguments[i].index];
|
||||
r.arguments[i].classv = classes.checkedGet(instruction.arguments[i].index);
|
||||
break;
|
||||
case OpcodeArgumentType.Method:
|
||||
r.arguments[i].methodv = methods[instruction.arguments[i].index];
|
||||
r.arguments[i].methodv = methods.checkedGet(instruction.arguments[i].index);
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
@@ -642,9 +642,6 @@ private final class ABCtoAS
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
r.arguments[i].switchTargets = instruction.arguments[i].switchTargets;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -658,7 +655,7 @@ private final class ABCtoAS
|
||||
as.majorVersion = abc.majorVersion;
|
||||
|
||||
namespaces.length = abc.namespaces.length;
|
||||
foreach (i, ref namespace; abc.namespaces)
|
||||
foreach (uint i, ref namespace; abc.namespaces)
|
||||
if (i)
|
||||
namespaces[i] = convertNamespace(namespace, i);
|
||||
|
||||
@@ -688,7 +685,7 @@ private final class ABCtoAS
|
||||
instances[i] = convertInstance(instance);
|
||||
|
||||
classes.length = abc.classes.length;
|
||||
foreach (i, ref vclass; abc.classes)
|
||||
foreach (uint i, ref vclass; abc.classes)
|
||||
classes[i] = convertClass(vclass, i);
|
||||
|
||||
as.scripts.length = abc.scripts.length;
|
||||
@@ -732,9 +729,24 @@ private final class AStoABC
|
||||
return value == T.init;
|
||||
}
|
||||
|
||||
static void move(T)(T[] array, size_t from, size_t to)
|
||||
{
|
||||
assert(from<array.length && to<array.length);
|
||||
if (from == to)
|
||||
return;
|
||||
T t = array[from];
|
||||
if (from < to)
|
||||
memmove(array.ptr+from, array.ptr+from+1, (to-from)*T.sizeof);
|
||||
else
|
||||
memmove(array.ptr+to+1, array.ptr+to, (from-to)*T.sizeof);
|
||||
array[to] = t;
|
||||
}
|
||||
|
||||
/// Maintain an unordered set of values; sort/index by usage count
|
||||
struct ConstantPool(T, bool haveNull = true)
|
||||
{
|
||||
alias immutable(T) I;
|
||||
|
||||
struct Entry
|
||||
{
|
||||
uint hits;
|
||||
@@ -743,7 +755,7 @@ private final class AStoABC
|
||||
|
||||
mixin AutoCompare;
|
||||
|
||||
R processData(R, string prolog, string epilog, H)(ref H handler)
|
||||
R processData(R, string prolog, string epilog, H)(ref H handler) const
|
||||
{
|
||||
mixin(prolog);
|
||||
mixin(addAutoField("hits", true));
|
||||
@@ -752,17 +764,17 @@ private final class AStoABC
|
||||
}
|
||||
}
|
||||
|
||||
Entry[T] pool;
|
||||
Entry[immutable(T)] pool;
|
||||
T[] values;
|
||||
|
||||
bool add(T value) // return true if added
|
||||
{
|
||||
if (haveNull && isNull(value))
|
||||
return false;
|
||||
auto cp = value in pool;
|
||||
auto cp = cast(I)value in pool;
|
||||
if (cp is null)
|
||||
{
|
||||
pool[value] = Entry(1, value);
|
||||
pool[cast(I)value] = Entry(1, value);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -774,7 +786,7 @@ private final class AStoABC
|
||||
|
||||
bool notAdded(T value)
|
||||
{
|
||||
auto ep = value in pool;
|
||||
auto ep = cast(I)value in pool;
|
||||
if (ep)
|
||||
ep.hits++;
|
||||
return !((haveNull && isNull(value)) || ep);
|
||||
@@ -786,9 +798,9 @@ private final class AStoABC
|
||||
all.sort;
|
||||
enum { NullOffset = haveNull ? 1 : 0 }
|
||||
values.length = all.length + NullOffset;
|
||||
foreach (i, ref c; all)
|
||||
foreach (uint i, ref c; all)
|
||||
{
|
||||
pool[c.value].index = i + NullOffset;
|
||||
pool[cast(I)c.value].index = i + NullOffset;
|
||||
values[i + NullOffset] = c.value;
|
||||
}
|
||||
}
|
||||
@@ -797,7 +809,7 @@ private final class AStoABC
|
||||
{
|
||||
if (haveNull && isNull(value))
|
||||
return 0;
|
||||
return pool[value].index;
|
||||
return pool[cast(I)value].index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -811,17 +823,8 @@ private final class AStoABC
|
||||
uint addIndex, index;
|
||||
void*[] parents;
|
||||
|
||||
int opCmp(Entry* o)
|
||||
{
|
||||
if (this.parents.contains(o.object))
|
||||
return 1;
|
||||
if (o.parents.contains(this.object))
|
||||
return -1;
|
||||
if (o.hits==hits)
|
||||
return addIndex-o.addIndex;
|
||||
else
|
||||
return o.hits-hits;
|
||||
}
|
||||
mixin AutoToString;
|
||||
mixin ProcessAllData;
|
||||
}
|
||||
|
||||
Entry[void*] pool;
|
||||
@@ -835,7 +838,7 @@ private final class AStoABC
|
||||
auto rp = p in pool;
|
||||
if (rp is null)
|
||||
{
|
||||
pool[p] = Entry(1, p, pool.length);
|
||||
pool[p] = Entry(1, p, to!uint(pool.length));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -871,61 +874,37 @@ private final class AStoABC
|
||||
|
||||
void finalize()
|
||||
{
|
||||
// assign unique index
|
||||
// create array
|
||||
auto all = new Entry*[pool.length];
|
||||
int i=0;
|
||||
foreach (ref e; pool)
|
||||
e.index = i++;
|
||||
|
||||
// topographical sort
|
||||
bool done;
|
||||
while (!done)
|
||||
{
|
||||
done = true;
|
||||
|
||||
foreach (ref a; pool)
|
||||
foreach (parent; a.parents)
|
||||
{
|
||||
auto pb = parent in pool;
|
||||
assert(pb !is null, "Can't find referenced object");
|
||||
if (pb.index > a.index)
|
||||
{
|
||||
auto t = pb.index;
|
||||
pb.index = a.index;
|
||||
a.index = t;
|
||||
done = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy to array
|
||||
auto all = new Entry[i];
|
||||
foreach (ref e; pool)
|
||||
all[e.index] = e;
|
||||
all[i++] = &e;
|
||||
|
||||
// sort
|
||||
//all.sort; // sort preserving topological integrity demands compared items to always be adjacent
|
||||
done = false;
|
||||
while (!done)
|
||||
{
|
||||
done = true;
|
||||
sort!q{a.hits > b.hits || (a.hits == b.hits && a.addIndex < b.addIndex)}(all);
|
||||
|
||||
for (int j=0; j<all.length-1; j++)
|
||||
if (all[j] > &all[j+1])
|
||||
// topographical sort
|
||||
topSort:
|
||||
|
||||
// update indices
|
||||
foreach (uint j, e; all)
|
||||
e.index = j;
|
||||
|
||||
foreach (ref a; pool)
|
||||
foreach (parent; a.parents)
|
||||
{
|
||||
auto pb = parent in pool;
|
||||
assert(pb !is null, "Can't find referenced object");
|
||||
if (pb.index > a.index)
|
||||
{
|
||||
auto t = all[j];
|
||||
all[j] = all[j+1];
|
||||
all[j+1] = t;
|
||||
done = false;
|
||||
move(all, pb.index, a.index);
|
||||
goto topSort;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objects.length = i;
|
||||
foreach (j, ref e; all)
|
||||
{
|
||||
pool[e.object].index = j;
|
||||
foreach (j, e; all)
|
||||
objects[j] = cast(T)e.object;
|
||||
}
|
||||
}
|
||||
|
||||
uint get(T obj)
|
||||
@@ -996,7 +975,7 @@ private final class AStoABC
|
||||
throw new .Exception("Unknown Multiname kind");
|
||||
}
|
||||
bool r = multinames.add(multiname);
|
||||
assert(r);
|
||||
assert(r, "Recursive multiname reference");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1087,15 +1066,12 @@ private final class AStoABC
|
||||
|
||||
void visitClass(ASProgram.Class vclass)
|
||||
{
|
||||
if (classes.notAdded(vclass))
|
||||
if (classes.add(vclass))
|
||||
{
|
||||
visitMethod(vclass.cinit);
|
||||
visitTraits(vclass.traits);
|
||||
|
||||
visitInstance(vclass.instance);
|
||||
|
||||
bool r = classes.add(vclass);
|
||||
assert(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1110,7 +1086,7 @@ private final class AStoABC
|
||||
strings.add(name);
|
||||
foreach (ref value; options)
|
||||
visitValue(value);
|
||||
foreach (name; paramNames)
|
||||
foreach (name; paramNames)
|
||||
strings.add(name);
|
||||
|
||||
if (vbody)
|
||||
@@ -1122,7 +1098,7 @@ private final class AStoABC
|
||||
{
|
||||
foreach (ref instruction; vbody.instructions)
|
||||
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
|
||||
switch (type)
|
||||
final switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to visit OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
@@ -1161,9 +1137,6 @@ private final class AStoABC
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
foreach (ref exception; vbody.exceptions)
|
||||
@@ -1474,7 +1447,7 @@ private final class AStoABC
|
||||
r.arguments.length = instruction.arguments.length;
|
||||
|
||||
foreach (i, type; opcodeInfo[instruction.opcode].argumentTypes)
|
||||
switch (type)
|
||||
final switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to convert OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
@@ -1508,10 +1481,16 @@ private final class AStoABC
|
||||
r.arguments[i].index = multinames.get(instruction.arguments[i].multinamev);
|
||||
break;
|
||||
case OpcodeArgumentType.Class:
|
||||
r.arguments[i].index = classes.get(instruction.arguments[i].classv);
|
||||
if (instruction.arguments[i].classv is null)
|
||||
r.arguments[i].index = to!uint(abc.classes.length);
|
||||
else
|
||||
r.arguments[i].index = classes.get(instruction.arguments[i].classv);
|
||||
break;
|
||||
case OpcodeArgumentType.Method:
|
||||
r.arguments[i].index = methods.get(instruction.arguments[i].methodv);
|
||||
if (instruction.arguments[i].methodv is null)
|
||||
r.arguments[i].index = to!uint(abc.methods.length);
|
||||
else
|
||||
r.arguments[i].index = methods.get(instruction.arguments[i].methodv);
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
@@ -1522,9 +1501,6 @@ private final class AStoABC
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
r.arguments[i].switchTargets = instruction.arguments[i].switchTargets;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -1585,3 +1561,8 @@ private bool contains(T)(T[] arr, T val)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private T checkedGet(T)(T[] array, uint index, T def = T.init)
|
||||
{
|
||||
return index < array.length ? array[index] : def;
|
||||
}
|
||||
|
||||
+65
-76
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -22,6 +22,7 @@ import std.file;
|
||||
import std.string;
|
||||
import std.conv;
|
||||
import std.path;
|
||||
import std.exception;
|
||||
import abcfile;
|
||||
import asprogram;
|
||||
|
||||
@@ -44,8 +45,8 @@ final class Assembler
|
||||
{
|
||||
string filename;
|
||||
string buf;
|
||||
char* pos;
|
||||
char* end;
|
||||
immutable(char)* pos;
|
||||
immutable(char)* end;
|
||||
string[] arguments;
|
||||
string basePath;
|
||||
|
||||
@@ -53,7 +54,7 @@ final class Assembler
|
||||
{
|
||||
return fromFile(filename, cast(string)read(filename), arguments);
|
||||
}
|
||||
|
||||
|
||||
static File fromFile(string filename, string data, string[] arguments = null)
|
||||
{
|
||||
return fromData(filename, data, arguments, getDirName(filename));
|
||||
@@ -61,7 +62,7 @@ final class Assembler
|
||||
|
||||
static File fromData(string name, string data, string[] arguments = null, string basePath = null)
|
||||
{
|
||||
data ~= \0; data = data[0..$-1]; // hack to prevent readWord etc. from checking for end-of-file on every character
|
||||
data ~= "\0"; data = data[0..$-1]; // hack to prevent readWord etc. from checking for end-of-file on every character
|
||||
return File(name, data, data.ptr, data.ptr + data.length, arguments, basePath);
|
||||
}
|
||||
|
||||
@@ -74,8 +75,8 @@ final class Assembler
|
||||
}
|
||||
|
||||
string positionStr()
|
||||
{
|
||||
auto lines = splitlines(buf);
|
||||
{
|
||||
auto lines = splitLines(buf);
|
||||
foreach (i, line; lines)
|
||||
if (pos <= line.ptr + line.length)
|
||||
return format("%s(%d,%d)", filename, i+1, pos-line.ptr+1);
|
||||
@@ -93,16 +94,16 @@ final class Assembler
|
||||
return file.basePath;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
string convertFilename(string filename)
|
||||
{
|
||||
if (filename.length == 0)
|
||||
throw new Exception("Empty filename");
|
||||
filename = filename.dup;
|
||||
foreach (ref c; filename)
|
||||
auto buf = filename.dup;
|
||||
foreach (ref c; buf)
|
||||
if (c == '\\')
|
||||
c = '/';
|
||||
return std.path.join(getBasePath, filename);
|
||||
return std.path.join(getBasePath, assumeUnique(buf));
|
||||
}
|
||||
|
||||
void skipWhitespace()
|
||||
@@ -126,7 +127,7 @@ final class Assembler
|
||||
do {} while (*++files[0].pos != '\n');
|
||||
}
|
||||
else
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +191,7 @@ final class Assembler
|
||||
foreach (ref file; files[0..fileCount])
|
||||
if (file.arguments.length)
|
||||
{
|
||||
uint index = .toUint(name)-1;
|
||||
uint index = .to!uint(name)-1;
|
||||
if (index >= file.arguments.length)
|
||||
throw new Exception("Argument index out-of-bounds");
|
||||
string value = file.arguments[index];
|
||||
@@ -211,7 +212,7 @@ final class Assembler
|
||||
|
||||
static bool isWordChar(char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.'; // TODO: use lookup table?
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.';
|
||||
}
|
||||
|
||||
string readWord()
|
||||
@@ -249,12 +250,12 @@ final class Assembler
|
||||
case 'd': case 'D': return 13;
|
||||
case 'e': case 'E': return 14;
|
||||
case 'f': case 'F': return 15;
|
||||
default:
|
||||
default:
|
||||
throw new Exception("Malformed hex digit " ~ x);
|
||||
}
|
||||
}
|
||||
|
||||
void pushFile(ref File file)
|
||||
void pushFile(File file)
|
||||
{
|
||||
if (fileCount == files.length)
|
||||
throw new Exception("Recursion limit exceeded");
|
||||
@@ -264,7 +265,7 @@ final class Assembler
|
||||
}
|
||||
|
||||
/// For restoring the position of an error
|
||||
void setFile(ref File file)
|
||||
void setFile(File file)
|
||||
{
|
||||
files[0] = file;
|
||||
fileCount = -1;
|
||||
@@ -357,7 +358,7 @@ final class Assembler
|
||||
static const char[16] hexDigits = "0123456789ABCDEF";
|
||||
|
||||
// TODO: optimize
|
||||
string s = \";
|
||||
string s = "\"";
|
||||
foreach (c; str)
|
||||
if (c == 0x0A)
|
||||
s ~= `\n`;
|
||||
@@ -422,7 +423,7 @@ final class Assembler
|
||||
return v;
|
||||
}
|
||||
|
||||
ubyte readFlag(string[] names)
|
||||
ubyte readFlag(const string[] names)
|
||||
{
|
||||
auto word = readWord();
|
||||
ubyte f = 1;
|
||||
@@ -490,7 +491,7 @@ final class Assembler
|
||||
string w = readWord();
|
||||
if (w == "null")
|
||||
return ABCFile.NULL_INT;
|
||||
return toInt(w);
|
||||
return to!int(w);
|
||||
}
|
||||
|
||||
ulong readUInt()
|
||||
@@ -498,7 +499,7 @@ final class Assembler
|
||||
string w = readWord();
|
||||
if (w == "null")
|
||||
return ABCFile.NULL_UINT;
|
||||
return toUint(w);
|
||||
return to!uint(w);
|
||||
}
|
||||
|
||||
double readDouble()
|
||||
@@ -506,7 +507,7 @@ final class Assembler
|
||||
string w = readWord();
|
||||
if (w == "null")
|
||||
return ABCFile.NULL_DOUBLE;
|
||||
return toDouble(w);
|
||||
return to!double(w);
|
||||
}
|
||||
|
||||
string readString()
|
||||
@@ -539,8 +540,8 @@ final class Assembler
|
||||
default: s ~= *files[0].pos;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
s ~= *files[0].pos;
|
||||
default:
|
||||
s ~= *files[0].pos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,16 +968,16 @@ final class Assembler
|
||||
if (c=='-' || c=='+')
|
||||
{
|
||||
name = label[0..i];
|
||||
offset = .toInt(label[i..$]);
|
||||
offset = to!int(label[i..$]);
|
||||
break;
|
||||
}
|
||||
auto lp = name in labels;
|
||||
if (lp is null)
|
||||
throw new Exception("Unknown label " ~ name);
|
||||
|
||||
|
||||
return ABCFile.Label(*lp, offset);
|
||||
}
|
||||
|
||||
|
||||
ASProgram.Instruction[] readInstructions(ref uint[string] _labels)
|
||||
{
|
||||
ASProgram.Instruction[] instructions;
|
||||
@@ -991,7 +992,7 @@ final class Assembler
|
||||
break;
|
||||
if (peekChar() == ':')
|
||||
{
|
||||
addUnique!("label")(labels, word, instructions.length);
|
||||
addUnique!("label")(labels, word, to!uint(instructions.length));
|
||||
skipChar(); // :
|
||||
continue;
|
||||
}
|
||||
@@ -1007,9 +1008,9 @@ final class Assembler
|
||||
instruction.opcode = *popcode;
|
||||
auto argTypes = opcodeInfo[instruction.opcode].argumentTypes;
|
||||
instruction.arguments.length = argTypes.length;
|
||||
foreach (i, type; argTypes)
|
||||
foreach (uint i, type; argTypes)
|
||||
{
|
||||
switch (type)
|
||||
final switch (type)
|
||||
{
|
||||
case OpcodeArgumentType.Unknown:
|
||||
throw new Exception("Don't know how to assemble OP_" ~ opcodeInfo[instruction.opcode].name);
|
||||
@@ -1043,26 +1044,23 @@ final class Assembler
|
||||
instruction.arguments[i].multinamev = readMultiname();
|
||||
break;
|
||||
case OpcodeArgumentType.Class:
|
||||
localClassFixups ~= LocalFixup(files[0].position, instructions.length, i, readString());
|
||||
localClassFixups ~= LocalFixup(files[0].position, to!uint(instructions.length), i, readString());
|
||||
break;
|
||||
case OpcodeArgumentType.Method:
|
||||
localMethodFixups ~= LocalFixup(files[0].position, instructions.length, i, readString());
|
||||
localMethodFixups ~= LocalFixup(files[0].position, to!uint(instructions.length), i, readString());
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.JumpTarget:
|
||||
case OpcodeArgumentType.SwitchDefaultTarget:
|
||||
jumpFixups ~= LocalFixup(files[0].position, instructions.length, i, readWord());
|
||||
jumpFixups ~= LocalFixup(files[0].position, to!uint(instructions.length), i, readWord());
|
||||
break;
|
||||
|
||||
case OpcodeArgumentType.SwitchTargets:
|
||||
string[] switchTargetLabels = readList!('[', ']', readWord, false)();
|
||||
instruction.arguments[i].switchTargets.length = switchTargetLabels.length;
|
||||
foreach (li, s; switchTargetLabels)
|
||||
switchFixups ~= LocalFixup(files[0].position, instructions.length, i, s, li);
|
||||
foreach (uint li, s; switchTargetLabels)
|
||||
switchFixups ~= LocalFixup(files[0].position, to!uint(instructions.length), i, s, li);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
if (i < argTypes.length-1)
|
||||
expectChar(',');
|
||||
@@ -1073,24 +1071,14 @@ final class Assembler
|
||||
|
||||
foreach (ref f; jumpFixups)
|
||||
{
|
||||
try
|
||||
instructions[f.ii].arguments[f.ai].jumpTarget = parseLabel(f.name, labels);
|
||||
catch (Object o)
|
||||
{
|
||||
setFile(f.where.load);
|
||||
throw o;
|
||||
}
|
||||
scope(failure) setFile(f.where.load);
|
||||
instructions[f.ii].arguments[f.ai].jumpTarget = parseLabel(f.name, labels);
|
||||
}
|
||||
|
||||
foreach (ref f; switchFixups)
|
||||
{
|
||||
try
|
||||
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = parseLabel(f.name, labels);
|
||||
catch (Object o)
|
||||
{
|
||||
setFile(f.where.load);
|
||||
throw o;
|
||||
}
|
||||
scope(failure) setFile(f.where.load);
|
||||
instructions[f.ii].arguments[f.ai].switchTargets[f.si] = parseLabel(f.name, labels);
|
||||
}
|
||||
|
||||
foreach (ref f; localClassFixups)
|
||||
@@ -1107,13 +1095,8 @@ final class Assembler
|
||||
ABCFile.Label readLabel()
|
||||
{
|
||||
auto word = readWord();
|
||||
try
|
||||
return parseLabel(word, labels);
|
||||
catch (Object o)
|
||||
{
|
||||
backpedal(word.length);
|
||||
throw o;
|
||||
}
|
||||
scope(failure) backpedal(word.length);
|
||||
return parseLabel(word, labels);
|
||||
}
|
||||
|
||||
ASProgram.Exception e;
|
||||
@@ -1192,36 +1175,42 @@ final class Assembler
|
||||
readProgram();
|
||||
|
||||
foreach (ref f; classFixups)
|
||||
{
|
||||
auto cp = f.name in classesByID;
|
||||
if (cp is null)
|
||||
if (f.name is null)
|
||||
*f.ptr = null;
|
||||
else
|
||||
{
|
||||
setFile(f.where.load);
|
||||
throw new Exception("Unknown class refid: " ~ f.name);
|
||||
auto cp = f.name in classesByID;
|
||||
if (cp is null)
|
||||
{
|
||||
setFile(f.where.load);
|
||||
throw new Exception("Unknown class refid: " ~ f.name);
|
||||
}
|
||||
*f.ptr = *cp;
|
||||
}
|
||||
*f.ptr = *cp;
|
||||
}
|
||||
|
||||
foreach (ref f; methodFixups)
|
||||
{
|
||||
auto mp = f.name in methodsByID;
|
||||
if (mp is null)
|
||||
if (f.name is null)
|
||||
*f.ptr = null;
|
||||
else
|
||||
{
|
||||
setFile(f.where.load);
|
||||
throw new Exception("Unknown method refid: " ~ f.name);
|
||||
auto mp = f.name in methodsByID;
|
||||
if (mp is null)
|
||||
{
|
||||
setFile(f.where.load);
|
||||
throw new Exception("Unknown method refid: " ~ f.name);
|
||||
}
|
||||
*f.ptr = *mp;
|
||||
}
|
||||
*f.ptr = *mp;
|
||||
}
|
||||
}
|
||||
catch (Object o)
|
||||
catch (Exception e)
|
||||
{
|
||||
string s = files[0].positionStr ~ ": " ~ o.toString();
|
||||
string s = files[0].positionStr ~ ": ";
|
||||
if (fileCount == -1)
|
||||
s ~= "\n\t(inclusion context unavailable)";
|
||||
else
|
||||
foreach (ref f; files[1..fileCount])
|
||||
s ~= "\n\t(included from " ~ f.positionStr ~ ")";
|
||||
throw new Exception(s);
|
||||
throw new Exception(s, e);
|
||||
}
|
||||
|
||||
classFixups = null;
|
||||
|
||||
+109
-82
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -19,61 +19,68 @@
|
||||
module autodata;
|
||||
|
||||
import murmurhash2a;
|
||||
import std2.traits;
|
||||
import std.traits;
|
||||
public import std.conv;
|
||||
|
||||
string addAutoField(string name, bool reverseSort = false)
|
||||
{
|
||||
//return `mixin(handler.process!(typeof(` ~ name ~ `), "` ~ name ~ `")());`; // doesn't work due to DMD bug 3959
|
||||
return `{ static const _AutoDataStr = handler.process!(typeof(this.` ~ name ~ `), "` ~ name ~ `", ` ~ (reverseSort ? "true" : "false") ~`)(); mixin(_AutoDataStr); }`;
|
||||
return `mixin(typeof(handler).getMixin!(typeof(` ~ name ~ `), "` ~ name ~ `", ` ~ (reverseSort ? "true" : "false") ~`));`;
|
||||
}
|
||||
|
||||
template AutoCompare()
|
||||
{
|
||||
hash_t toHash()
|
||||
static if (is(typeof(this)==class))
|
||||
{
|
||||
alias typeof(this) _AutoDataTypeReference;
|
||||
alias Object _AutoDataOtherTypeReference;
|
||||
|
||||
override hash_t toHash() const { return _AutoDataHash(); }
|
||||
override bool opEquals(Object o) const { return _AutoDataEquals(o); }
|
||||
override int opCmp(Object o) const { return _AutoDataCmp(o); }
|
||||
}
|
||||
else // struct
|
||||
{
|
||||
alias const(typeof(this)*) _AutoDataTypeReference;
|
||||
alias const(typeof(this)*) _AutoDataOtherTypeReference;
|
||||
|
||||
hash_t toHash() const { return _AutoDataHash(); }
|
||||
bool opEquals(ref const typeof(this) s) const { return _AutoDataEquals(&s); }
|
||||
int opCmp(ref const typeof(this) s) const { return _AutoDataCmp(&s); }
|
||||
}
|
||||
|
||||
private hash_t _AutoDataHash() const
|
||||
{
|
||||
HashDataHandler handler;
|
||||
handler.hasher.Begin();
|
||||
processData!(void, "", "")(handler);
|
||||
processData!(void, q{}, q{})(handler);
|
||||
return handler.hasher.End();
|
||||
}
|
||||
|
||||
static if (is(typeof(this)==class))
|
||||
alias Object _AutoDataOtherType;
|
||||
else
|
||||
alias typeof(this) _AutoDataOtherType;
|
||||
|
||||
int opEquals(_AutoDataOtherType other)
|
||||
private bool _AutoDataEquals(_AutoDataOtherTypeReference other) const
|
||||
{
|
||||
EqualsDataHandler!(typeof(this)) handler;
|
||||
static if (is(typeof(this)==class))
|
||||
{
|
||||
handler.other = cast(typeof(this)) other;
|
||||
if (handler.other is null)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
handler.other = other;
|
||||
return processData!(bool, "auto _AutoDataOther = handler.other;", "return true;")(handler);
|
||||
auto handler = EqualsDataHandler!_AutoDataTypeReference(cast(_AutoDataTypeReference) other);
|
||||
if (handler.other is null)
|
||||
return false;
|
||||
return processData!(bool, q{auto _AutoDataOther = handler.other;}, q{return true;})(handler);
|
||||
}
|
||||
|
||||
int opCmp(_AutoDataOtherType other)
|
||||
private int _AutoDataCmp(_AutoDataOtherTypeReference other) const
|
||||
{
|
||||
CmpDataHandler!(typeof(this)) handler;
|
||||
static if (is(typeof(this)==class))
|
||||
{
|
||||
handler.other = cast(typeof(this)) other;
|
||||
if (handler.other is null)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
handler.other = other;
|
||||
return processData!(int, "auto _AutoDataOther = handler.other;", "return 0;")(handler);
|
||||
auto handler = CmpDataHandler!_AutoDataTypeReference(cast(_AutoDataTypeReference) other);
|
||||
if (handler.other is null)
|
||||
return false;
|
||||
return processData!(int, q{auto _AutoDataOther = handler.other;}, "return 0;")(handler);
|
||||
}
|
||||
}
|
||||
|
||||
template AutoToString()
|
||||
{
|
||||
string toString()
|
||||
static if (is(typeof(this)==class))
|
||||
override string toString() const { return _AutoDataToString(); }
|
||||
else // struct
|
||||
string toString() { return _AutoDataToString(); }
|
||||
|
||||
string _AutoDataToString() const
|
||||
{
|
||||
ToStringDataHandler handler;
|
||||
return processData!(string, "string _AutoDataResult;", "return _AutoDataResult;")(handler);
|
||||
@@ -82,20 +89,11 @@ template AutoToString()
|
||||
|
||||
template ProcessAllData()
|
||||
{
|
||||
R processData(R, string prolog, string epilog, H)(ref H handler)
|
||||
R processData(R, string prolog, string epilog, H)(ref H handler) const
|
||||
{
|
||||
mixin(prolog);
|
||||
foreach (i, T; this.tupleof)
|
||||
{
|
||||
//mixin(addAutoField(T.stringof)); // doesn't work
|
||||
static if (this.tupleof[i].stringof == typeof(this.tupleof[i]).stringof)
|
||||
static assert(0, "DMD bug 2881 detected - can't use enums with ProcessAllData");
|
||||
else
|
||||
static if (is (typeof(this) == class))
|
||||
mixin(addAutoField(this.tupleof[i].stringof[5..$])); // remove "this."
|
||||
else
|
||||
mixin(addAutoField(this.tupleof[i].stringof[8..$])); // remove "(*this)."
|
||||
}
|
||||
mixin(addAutoField(this.tupleof[i].stringof[5..$])); // remove "this."
|
||||
mixin(epilog);
|
||||
}
|
||||
}
|
||||
@@ -103,29 +101,30 @@ template ProcessAllData()
|
||||
/// For data handlers that only need to look at the raw data (currently only HashDataHandler)
|
||||
template RawDataHandlerWrapper()
|
||||
{
|
||||
static string process(T, string name, bool reverseSort)()
|
||||
template getMixin(T, string name, bool reverseSort)
|
||||
{
|
||||
return processRecursive!(T, "this." ~ name, "");
|
||||
enum getMixin = getMixinRecursive!(T, "this." ~ name, "");
|
||||
}
|
||||
|
||||
static string processRecursive(T, string name, string loopDepth)()
|
||||
template getMixinRecursive(T, string name, string loopDepth)
|
||||
{
|
||||
static if (!hasAliasing!(T))
|
||||
return processRaw("&" ~ name, name ~ ".sizeof");
|
||||
else
|
||||
static if (is(T U : U[]))
|
||||
{
|
||||
string s = "{ bool _AutoDataNullTest = " ~ name ~ " is null; " ~ processRaw("&_AutoDataNullTest", "bool.sizeof") ~ "}";
|
||||
static if (!hasAliasing!(U))
|
||||
s ~= processRaw(name ~ ".ptr", name ~ ".length");
|
||||
else
|
||||
s ~= "foreach (ref _AutoDataArrayItem" ~ loopDepth ~ "; " ~ name ~ ") {" ~ processRecursive!(U, "_AutoDataArrayItem" ~ loopDepth, loopDepth~"Item")() ~ "}";
|
||||
return s;
|
||||
}
|
||||
enum getMixinRecursive =
|
||||
"{ bool _AutoDataNullTest = " ~ name ~ " is null; " ~ getRawMixin!("&_AutoDataNullTest", "bool.sizeof") ~ "}" ~
|
||||
(!hasAliasing!(U) ?
|
||||
getRawMixin!(name ~ ".ptr", name ~ ".length")
|
||||
:
|
||||
"foreach (ref _AutoDataArrayItem" ~ loopDepth ~ "; " ~ name ~ ") {" ~ getMixinRecursive!(U, "_AutoDataArrayItem" ~ loopDepth, loopDepth~"Item") ~ "}"
|
||||
);
|
||||
else
|
||||
static if (is(typeof((new T).toHash())))
|
||||
//static assert(0, "aoeu: " ~ T.stringof);
|
||||
return name ~ ".processData!(void, ``, ``)(handler);";
|
||||
static if (!hasAliasing!(T))
|
||||
enum getMixinRecursive = getRawMixin!("&" ~ name, name ~ ".sizeof");
|
||||
else
|
||||
static if (is(T==struct))
|
||||
enum getMixinRecursive = name ~ ".processData!(void, ``, ``)(handler);";
|
||||
else
|
||||
static if (is(T==class))
|
||||
enum getMixinRecursive = "if ("~name~" !is null) " ~ name ~ ".processData!(void, ``, ``)(handler);";
|
||||
else
|
||||
static assert(0, "Don't know how to process type: " ~ T.stringof);
|
||||
}
|
||||
@@ -137,9 +136,9 @@ struct HashDataHandler
|
||||
|
||||
MurmurHash2A hasher;
|
||||
|
||||
static string processRaw(string ptr, string len)
|
||||
template getRawMixin(string ptr, string len)
|
||||
{
|
||||
return "handler.hasher.Add(" ~ ptr ~ ", " ~ len ~ ");";
|
||||
enum getRawMixin = "handler.hasher.Add(" ~ ptr ~ ", to!int(" ~ len ~ "));";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,13 +146,17 @@ struct EqualsDataHandler(O)
|
||||
{
|
||||
O other;
|
||||
|
||||
static string process(T, string name, bool reverseSort)()
|
||||
template nullCheck(T, string name)
|
||||
{
|
||||
string s;
|
||||
static if (is(T U : U[]))
|
||||
s ~= " if ((this." ~ name ~ " is null) != (_AutoDataOther." ~ name ~ " is null)) return false;";
|
||||
s ~= "if (this." ~ name ~ " != _AutoDataOther." ~ name ~ ") return false;";
|
||||
return s;
|
||||
static if (is(typeof(T.init is null)))
|
||||
enum nullCheck = "if ((this." ~ name ~ " is null) != (_AutoDataOther." ~ name ~ " is null)) return false;";
|
||||
else
|
||||
enum nullCheck = "";
|
||||
}
|
||||
|
||||
template getMixin(T, string name, bool reverseSort)
|
||||
{
|
||||
enum getMixin = nullCheck!(T, name) ~ "if (this." ~ name ~ " != _AutoDataOther." ~ name ~ ") return false;";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,32 +164,56 @@ struct CmpDataHandler(O)
|
||||
{
|
||||
O other;
|
||||
|
||||
static string process(T, string name, bool reverseSort)()
|
||||
template getMixin(T, string name, bool reverseSort)
|
||||
{
|
||||
string reverseStr = reverseSort ? "-" : "";
|
||||
string s;
|
||||
enum getMixin = getMixinComposite!(T, name, reverseSort).code;
|
||||
}
|
||||
|
||||
template nullCheck(T, string name, string reverseStr)
|
||||
{
|
||||
static if (is(typeof(T.init is null)))
|
||||
enum nullCheck = "
|
||||
if (this."~name~" is null && _AutoDataOther."~name~" is null)
|
||||
{ /* skip */ }
|
||||
else
|
||||
if (this."~name~" is null && _AutoDataOther."~name~" !is null)
|
||||
return " ~ reverseStr ~ "(-1);
|
||||
else
|
||||
if (this."~name~" !is null && _AutoDataOther."~name~" is null)
|
||||
return " ~ reverseStr ~ "( 1);
|
||||
else";
|
||||
else
|
||||
enum nullCheck = "";
|
||||
}
|
||||
|
||||
template getMixinComposite(T, string name, bool reverseSort)
|
||||
{
|
||||
enum reverseStr = reverseSort ? "-" : "";
|
||||
static if (is(T U : U[]))
|
||||
s ~= "{ int _AutoDataCmp = cast(int)(this." ~ name ~ " !is null) - cast(int)(_AutoDataOther." ~ name ~ " !is null); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
|
||||
enum arrCode = "{ int _AutoDataCmp = cast(int)(this." ~ name ~ " !is null) - cast(int)(_AutoDataOther." ~ name ~ " !is null); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
|
||||
else
|
||||
enum arrCode = "";
|
||||
|
||||
static if (is(T == string) && is(std.string.cmp))
|
||||
s ~= "{ int _AutoDataCmp = std.string.cmp(this." ~ name ~ ", _AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
|
||||
enum dataCode = "{ int _AutoDataCmp = std.string.cmp(this." ~ name ~ ", _AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
|
||||
else
|
||||
static if (is(T == int))
|
||||
s ~= "{ int _AutoDataCmp = this." ~ name ~ " - _AutoDataOther." ~ name ~ "; if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }"; // TODO: use long?
|
||||
enum dataCode = "{ int _AutoDataCmp = this." ~ name ~ " - _AutoDataOther." ~ name ~ "; if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }"; // TODO: use long?
|
||||
else
|
||||
static if (is(typeof(T.opCmp)))
|
||||
s ~= "{ int _AutoDataCmp = this." ~ name ~ ".opCmp(_AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
|
||||
enum dataCode = nullCheck!(T, name, reverseStr)
|
||||
~ "{ int _AutoDataCmp = this." ~ name ~ ".opCmp(_AutoDataOther." ~ name ~ "); if (_AutoDataCmp != 0) return " ~ reverseStr ~ "_AutoDataCmp; }";
|
||||
else
|
||||
s ~= "if (this." ~ name ~ " < _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "(-1);" ~
|
||||
"if (this." ~ name ~ " > _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "( 1);";
|
||||
return s;
|
||||
enum dataCode = "if (this." ~ name ~ " < _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "(-1);" ~
|
||||
"if (this." ~ name ~ " > _AutoDataOther." ~ name ~ ") return " ~ reverseStr ~ "( 1);";
|
||||
enum code = arrCode ~ dataCode;
|
||||
}
|
||||
}
|
||||
|
||||
struct ToStringDataHandler
|
||||
{
|
||||
static string process(T, string name, bool reverseSort)()
|
||||
template getMixin(T, string name, bool reverseSort)
|
||||
{
|
||||
return "_AutoDataResult ~= format(`%s = %s `, `" ~ name ~ "`, this." ~ name ~ ");";
|
||||
enum getMixin = "_AutoDataResult ~= `" ~ name ~ " = ` ~ to!string(this." ~ name ~ ") ~ ` `;";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* RABCDAsm is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with RABCDAsm. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// A simple tool to build RABCDAsm in one command.
|
||||
/// You can use the DC and DCFLAGS environment variables to override the detected compiler and compilation flags.
|
||||
|
||||
module build_rabcdasm;
|
||||
|
||||
version(D_Version2)
|
||||
{ /* All OK */ }
|
||||
else
|
||||
static assert(false, "Unsupported D version.\nThis software requires a D2 ( http://www.digitalmars.com/d/2.0/ ) compiler to build.");
|
||||
|
||||
version(DigitalMars)
|
||||
const DEFAULT_COMPILER = "dmd";
|
||||
else
|
||||
const DEFAULT_COMPILER = "gdmd";
|
||||
|
||||
const DEFAULT_FLAGS = "-w -O -inline";
|
||||
|
||||
import std.process;
|
||||
import std.string;
|
||||
|
||||
string[][string] programs;
|
||||
|
||||
static this()
|
||||
{
|
||||
programs["rabcasm" ] = ["abcfile", "asprogram", "assembler", "autodata", "murmurhash2a"];
|
||||
programs["rabcdasm" ] = ["abcfile", "asprogram", "disassembler", "autodata", "murmurhash2a"];
|
||||
programs["abcexport" ] = ["swffile", "zlibx"];
|
||||
programs["abcreplace" ] = ["swffile", "zlibx"];
|
||||
programs["swfbinexport" ] = ["swffile", "zlibx"];
|
||||
programs["swfbinreplace"] = ["swffile", "zlibx"];
|
||||
programs["swfdecompress"] = ["swffile", "zlibx"];
|
||||
programs["swf7zcompress"] = ["swffile", "zlibx"];
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
string compiler = getenv("DC");
|
||||
if (compiler is null)
|
||||
compiler = DEFAULT_COMPILER;
|
||||
|
||||
string flags = getenv("DCFLAGS");
|
||||
if (flags is null)
|
||||
flags = DEFAULT_FLAGS;
|
||||
|
||||
foreach (program, modules; programs)
|
||||
{
|
||||
int ret = system(format("%s %s %s %s", compiler, flags, program, join(modules, " ")));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+592
-215
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -16,6 +16,8 @@
|
||||
|
||||
module murmurhash2a;
|
||||
|
||||
import std.conv;
|
||||
|
||||
struct MurmurHash2A
|
||||
{
|
||||
private static string mmix(string h, string k) { return "{ "~k~" *= m; "~k~" ^= "~k~" >> r; "~k~" *= m; "~h~" *= m; "~h~" ^= "~k~"; }"; }
|
||||
@@ -31,7 +33,7 @@ public:
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
void Add ( void * vdata, int len )
|
||||
void Add ( const(void) * vdata, int len )
|
||||
{
|
||||
ubyte * data = cast(ubyte*)vdata;
|
||||
m_size += len;
|
||||
@@ -67,8 +69,8 @@ public:
|
||||
void Add(ref ubyte v) { Add(&v, v.sizeof); }
|
||||
void Add(ref int v) { Add(&v, v.sizeof); }
|
||||
void Add(ref uint v) { Add(&v, v.sizeof); }
|
||||
void Add(string s) { Add(s.ptr, s.length); }
|
||||
void Add(ubyte[] s) { Add(s.ptr, s.length); }
|
||||
void Add(string s) { Add(s.ptr, to!uint(s.length)); }
|
||||
void Add(ubyte[] s) { Add(s.ptr, to!uint(s.length)); }
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -32,7 +32,7 @@ void main(string[] args)
|
||||
{
|
||||
scope abc = ABCFile.read(cast(ubyte[])read(arg));
|
||||
scope as = ASProgram.fromABC(abc);
|
||||
scope disassembler = new Disassembler(as, getName(getBaseName(arg)));
|
||||
scope disassembler = new Disassembler(as, getName(arg), getName(getBaseName(arg)));
|
||||
disassembler.disassemble();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* RABCDAsm is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with RABCDAsm. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module swfbinexport;
|
||||
|
||||
import std.file;
|
||||
import std.path;
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import std.exception;
|
||||
import swffile;
|
||||
|
||||
void main(string[] args)
|
||||
{
|
||||
if (args.length == 1)
|
||||
throw new Exception("No file specified");
|
||||
foreach (arg; args[1..$])
|
||||
try
|
||||
{
|
||||
scope swf = SWFFile.read(cast(ubyte[])read(arg));
|
||||
bool found;
|
||||
foreach (ref tag; swf.tags)
|
||||
if (tag.type == TagType.DefineBinaryData)
|
||||
{
|
||||
found = true;
|
||||
enforce(tag.data.length >= 6);
|
||||
ushort id = *cast(short*)tag.data.ptr;
|
||||
ubyte[] bin = tag.data[6..$];
|
||||
std.file.write(format("%s-%d.bin", getName(arg), id), bin);
|
||||
}
|
||||
enforce(found, "No DefineBinaryData tags found");
|
||||
}
|
||||
catch (Exception e)
|
||||
writefln("Error while processing %s: %s", arg, e);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* RABCDAsm is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with RABCDAsm. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module swfbinreplace;
|
||||
|
||||
import std.file;
|
||||
import std.conv;
|
||||
import swffile;
|
||||
|
||||
void main(string[] args)
|
||||
{
|
||||
if (args.length != 4)
|
||||
throw new Exception("Bad arguments. Usage: swfbinreplace file.swf id data.bin");
|
||||
auto swf = SWFFile.read(cast(ubyte[])read(args[1]));
|
||||
auto id = to!ushort(args[2]);
|
||||
foreach (ref tag; swf.tags)
|
||||
if (tag.type == TagType.DefineBinaryData && tag.data.length >= 6 && *cast(short*)tag.data.ptr == id)
|
||||
{
|
||||
auto bin = cast(ubyte[])read(args[3]);
|
||||
tag.data = tag.data[0..6] ~ bin;
|
||||
write(args[1], swf.write());
|
||||
return;
|
||||
}
|
||||
throw new Exception("DefineBinaryData tag with specified ID not found in file");
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* Copyright 2010, 2011 Vladimir Panteleev <vladimir@thecybershadow.net>
|
||||
* This file is part of RABCDAsm.
|
||||
*
|
||||
* RABCDAsm is free software: you can redistribute it and/or modify
|
||||
@@ -19,10 +19,11 @@
|
||||
module swffile;
|
||||
|
||||
import std.zlib;
|
||||
import std.conv;
|
||||
import zlibx;
|
||||
|
||||
/**
|
||||
* Implements a shallow representation of a .swf file.
|
||||
/**
|
||||
* Implements a shallow representation of a .swf file.
|
||||
* Loading and saving a .swf file using this class should produce
|
||||
* output identical to the input (aside zlib compression differences).
|
||||
*/
|
||||
@@ -230,7 +231,7 @@ private final class SWFWriter
|
||||
buf ~= swf.frameSize.bytes;
|
||||
buf ~= toArray(swf.frameRate);
|
||||
buf ~= toArray(swf.frameCount);
|
||||
|
||||
|
||||
foreach (ref tag; swf.tags)
|
||||
{
|
||||
ushort u = cast(ushort)(tag.type << 6);
|
||||
@@ -243,13 +244,13 @@ private final class SWFWriter
|
||||
{
|
||||
u |= 0x3F;
|
||||
buf ~= toArray(u);
|
||||
uint l = tag.data.length;
|
||||
uint l = to!uint(tag.data.length);
|
||||
buf ~= toArray(l);
|
||||
}
|
||||
buf ~= tag.data;
|
||||
}
|
||||
|
||||
swf.header.fileLength = 8 + buf.length;
|
||||
swf.header.fileLength = to!uint(8 + buf.length);
|
||||
if (swf.header.signature[0] == 'C')
|
||||
buf = cast(ubyte[])compress(buf, 9);
|
||||
buf = toArray(swf.header) ~ buf;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module zlibx;
|
||||
|
||||
import std.zlib, etc.c.zlib;
|
||||
import std.zlib, etc.c.zlib, std.conv;
|
||||
static import etc.c.zlib;
|
||||
alias std.zlib.Z_SYNC_FLUSH Z_SYNC_FLUSH;
|
||||
|
||||
@@ -13,12 +13,12 @@ ubyte[] exactUncompress(ubyte[] srcbuf, size_t destlen)
|
||||
|
||||
auto destbuf = new ubyte[destlen];
|
||||
uint err;
|
||||
|
||||
|
||||
zs.next_in = srcbuf.ptr;
|
||||
zs.avail_in = srcbuf.length;
|
||||
zs.avail_in = to!uint(srcbuf.length);
|
||||
|
||||
zs.next_out = destbuf.ptr;
|
||||
zs.avail_out = destbuf.length;
|
||||
zs.avail_out = to!uint(destbuf.length);
|
||||
|
||||
err = etc.c.zlib.inflateInit2(&zs, 15);
|
||||
if (err)
|
||||
@@ -37,7 +37,7 @@ ubyte[] exactUncompress(ubyte[] srcbuf, size_t destlen)
|
||||
}
|
||||
if (zs.avail_out != 0)
|
||||
throw new Exception("Too little data in stream");
|
||||
|
||||
|
||||
err = etc.c.zlib.inflateEnd(&zs);
|
||||
if (err != Z_OK)
|
||||
goto Lerr;
|
||||
|
||||
Reference in New Issue
Block a user