├── .gitignore ├── Emakefile ├── Makefile ├── README ├── README.windows ├── doc └── priv │ └── overview.edoc ├── docgen.erl ├── include ├── ngram.hrl ├── swf.hrl ├── swfabc.hrl └── swfdt.hrl ├── lib └── Makefile ├── scripts ├── apitest.py ├── sort_by_version.sh └── version.erl ├── src ├── ngram.erl ├── ssamod.erl ├── ssamod_abcdump.erl ├── ssamod_api.erl ├── ssamod_check.erl ├── ssamod_ngram.erl ├── ssamod_swfdump.erl ├── ssamod_version.erl ├── swf.erl ├── swfabc.erl ├── swfabcformat.erl ├── swfaction.erl ├── swfdt.erl ├── swfformat.erl ├── swfjson.erl ├── swfjsonabc.erl ├── swfmime.erl └── swfutils.erl ├── ssacli.erl ├── test ├── Makefile ├── ngram_SUITE.erl └── swfabc_SUITE.erl └── url_rewrite.erl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin 2 | lib/erlang-rfc4627 3 | -------------------------------------------------------------------------------- /Emakefile: -------------------------------------------------------------------------------- 1 | {'src/*', [{outdir, "ebin"},{i, "include"}]}. 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all compile clean doc docs lib test 2 | 3 | all: compile 4 | 5 | compile: 6 | mkdir -p ebin 7 | @erl -make 8 | 9 | clean: 10 | rm -f *.beam ebin/*.beam 11 | rm -f erl_crash.dump 12 | rm -f `find doc/* -prune -type f` 13 | make -C test clean 14 | 15 | distclean: clean 16 | make -C lib distclean 17 | make -C test clean 18 | 19 | doc: 20 | cp doc/priv/* doc 21 | ./docgen.erl doc src/*.erl 22 | 23 | docs: doc 24 | 25 | install: soft_install 26 | 27 | soft_install: 28 | ln -sf `pwd`/ssacli.erl /usr/local/bin/ssacli 29 | 30 | lib: 31 | make -C lib 32 | 33 | test: 34 | make -C test 35 | 36 | stat: 37 | @all_erls="src/*erl ssacli.erl test/*.erl include/*.hrl" ;\ 38 | echo "lines of code (w/o comments):" ;\ 39 | cat `echo $$all_erls` |grep -v '^[[:space:]]*$$' |grep -v "^%" |wc -l ;\ 40 | echo "functions:" ;\ 41 | cat `echo $$all_erls` |grep '^[a-z].*(.*) ->' |wc -l ;\ 42 | echo "inline functions (fun()s)" ;\ 43 | cat `echo $$all_erls` |grep 'fun(' |wc -l ;\ 44 | echo "total comments:" ;\ 45 | cat `echo $$all_erls` |grep '%% ' |wc -l ;\ 46 | echo "pure comments:" ;\ 47 | cat `echo $$all_erls` |grep '^[[:space:]]*%% ' |wc -l ;\ 48 | echo "@doc comments:" ;\ 49 | cat `echo $$all_erls` |grep '^%% @doc' |wc -l ;\ 50 | echo "lines including binaries (<<>>):" ;\ 51 | cat `echo $$all_erls` |grep '<<' |wc -l ;\ 52 | echo 53 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | _ __ 2 | ___ _ __| |_____ __/ _| 3 | / _ \ '__| / __\ \ /\ / / |_ 4 | | __/ | | \__ \\ V V /| _| 5 | \___|_| |_|___/ \_/\_/ |_| 6 | 7 | 8 | about: 9 | This project provides erlang modules for SWF disassembly and analysis with particular emphasis on security issues. Features include SWF decomposition, actionscript 2 bytecode disassembly, actionscript 3 bytecode (ABC) disassembly and filtering for predefined conditions such as tag occurrence. 10 | 11 | source download: 12 | https://github.com/bef/erlswf 13 | 14 | requirements: 15 | Erlang >= OTP R12B-0 16 | 17 | license: 18 | GNU General Public License version 3 19 | see http://www.gnu.org/licenses/gpl.html 20 | 21 | compile: 22 | $ make lib 23 | $ make 24 | 25 | install: 26 | $ make install 27 | This creates a symlink in /usr/local/bin. Type 'ssacli' for a quick start. 28 | 29 | API documentation: 30 | $ make doc 31 | 32 | related projects: 33 | - eswf: https://github.com/mochi/eswf 34 | 35 | 36 | mini-FAQ: 37 | Q: I can see error messages like "src/swfdt.erl:26: bit type bitstring undefined" 38 | "src/swfdt.erl:27: function bit_size/1 undefined". 39 | A: Please note the required erlang version. 40 | 41 | Q: error: undef: [{rfc4627,encode,... 42 | A: Type "make lib" to install the missing json library. 43 | 44 | -------------------------------------------------------------------------------- /README.windows: -------------------------------------------------------------------------------- 1 | Notes on running erlswf on Windows: 2 | 1. Install Erlang (http://erlang.org) 5.6.x / OTP R12 3 | 2. Install Cygwin (http://cygwin.org) with at least the following packages: 4 | - bash 5 | - gnu make 6 | - svn 7 | - mercurial 8 | 2b. (alternative to 2) Install equivalent components from MinGW or other sources. 9 | 3. Start bash and add erlang to PATH: 10 | $ export PATH="$PATH:/cygdrive/e/Programme/erl5.6.5/bin" 11 | The path (/cygdrive/e/...) should reflect your erlang installation's bin directory. 12 | 3b. You may want to add this to your ~/.bash_profile or ~/.bashrc 13 | 3c. Check the added path, e.g. "type erl" 14 | 4. Download/Build/Install/Run erlswf as described in README 15 | 4b. The symlinked ssacli may not work properly in cygwin. Just run ./ssacli.erl from the erlswf directory. 16 | 4c. A non-cygwin Erlang installation as described above cannot handle Unix-style path names in command line arguments, e.g. /cygdrive/c/Downloads/test.swf. Use escaped Windows-style instead: 17 | $ ./ssacli.erl dump C:\\Downloads\\test.swf 18 | 19 | 20 | -------------------------------------------------------------------------------- /doc/priv/overview.edoc: -------------------------------------------------------------------------------- 1 | @author: Ben Fuhrmannek 2 | @version 2008-01-22/v0.1a6 3 | @copyright 2008 - Ben Fuhrmannek 4 | 5 | @doc 6 | 7 | erlswf is a sophisticated framework for the decomposition and static analysis of SWF bytecode, ActionScript2 bytecode and ActionScript3 bytecode (ABC). 8 | 9 | This auto generated documentation may serve as a starting point for extending these modules and their usage. 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /docgen.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | main([TargetDir|Files]) -> 4 | edoc:files(Files, [{dir, TargetDir}]). 5 | 6 | -------------------------------------------------------------------------------- /include/ngram.hrl: -------------------------------------------------------------------------------- 1 | -record(ngramprofile, {name, type, n, l, data}). 2 | -------------------------------------------------------------------------------- /include/swf.hrl: -------------------------------------------------------------------------------- 1 | -record(swf, {header, tags}). 2 | -record(rawswf, {header, tags}). 3 | -record(swfheader, {type, version, filelength, framesize, framerate, framecount, headersize, rawheader}). 4 | -record(tag, {code, name, pos, raw, contents}). 5 | -------------------------------------------------------------------------------- /include/swfabc.hrl: -------------------------------------------------------------------------------- 1 | -record(abcfile, {version, cpool, method, metadata, instance, class, script, method_body}). 2 | -record(cpool, {integer, uinteger, double, string, namespace, ns_set, multiname}). 3 | -record(method, {param_count, return_type, param_type, name, flags, flags_arr, options, param_names}). 4 | -record(instance, {name, super_name, flags, flags_arr, protected_ns, interface, iinit, trait}). 5 | 6 | -record(trait, {name, type, data, metadata}). 7 | -record(trait_slot, {slot_id, type_name, value}). %% trait slot and const 8 | -record(trait_class, {slot_id, classi}). 9 | -record(trait_function, {slot_id, functioni}). 10 | -record(trait_method, {disp_id, methodi}). %% trait method, getter, setter 11 | 12 | -record(class, {cinit, trait}). 13 | -record(script, {init, trait}). 14 | -record(exception, {from, to, target, exc_type, var_name}). 15 | 16 | -record(instr, {addr, name, args}). 17 | -record(method_body, {methodi, max_stack, local_count, init_scope_depth, max_scope_depth, code, exception, trait}). 18 | -------------------------------------------------------------------------------- /include/swfdt.hrl: -------------------------------------------------------------------------------- 1 | -record(rect, {xmin, xmax, ymin, ymax}). 2 | 3 | -record(rgb, {red, green, blue}). 4 | -record(rgba, {red, green, blue, alpha}). 5 | -record(argb, {alpha, red, green, blue}). 6 | 7 | -record(cxform, {redmult, greenmult, bluemult, redadd, greenadd, blueadd}). 8 | -record(cxformwa, {redmult, greenmult, bluemult, alphamult, redadd, greenadd, blueadd, alphaadd}). %% with alpha 9 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | all: getjsonlib compilejsonlib 2 | 3 | getjsonlib: 4 | if [ -d "erlang-rfc4627" ]; then \ 5 | cd erlang-rfc4627 ; git pull ;\ 6 | else \ 7 | git clone https://github.com/tonyg/erlang-rfc4627.git ;\ 8 | fi 9 | 10 | compilejsonlib: 11 | mkdir -p erlang-rfc4627/ebin 12 | make -C erlang-rfc4627 13 | 14 | clean: 15 | make -C erlang-rfc4627 clean 16 | 17 | distclean: 18 | rm -rf erlang-rfc4627 19 | -------------------------------------------------------------------------------- /scripts/apitest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pexpect 4 | import sys 5 | 6 | if len(sys.argv) == 1: 7 | sys.exit(1) 8 | 9 | filenames = sys.argv[1:] 10 | 11 | p = pexpect.spawn("ssacli api") 12 | # p.logfile = sys.stdout 13 | 14 | for fn in filenames: 15 | p.expect("100 .*?\n") 16 | print "ready for " + fn 17 | 18 | p.sendline("version " + fn) 19 | p.expect(".*?\n") ## discard sendline echo 20 | 21 | p.expect("\n([2-9]\d\d) (.*?)\r\n") 22 | ret_code, ret_msg = (p.match.group(1), p.match.group(2)) 23 | print "return:", ret_code, ret_msg 24 | print "output:", 25 | print p.before 26 | 27 | 28 | p.close() 29 | -------------------------------------------------------------------------------- /scripts/sort_by_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "x$1" == "x" ]; then 4 | echo "$0 dirname" 5 | exit 1 6 | fi 7 | 8 | VERSION=`dirname $0`/version.erl 9 | for i in `find $1 -name \*.swf`; do 10 | echo -n "$i: " 11 | V=`$VERSION "$i"` 12 | if [ "$?" != "0" ]; then 13 | echo "error" 14 | continue 15 | fi 16 | echo "$V" 17 | [ ! -d "$V" ] && mkdir "$V" 18 | cp "$i" "$V" 19 | done 20 | -------------------------------------------------------------------------------- /scripts/version.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | main([Filename|_]) -> 4 | {ok, Io} = file:open(Filename, [read]), 5 | {ok, Start} = file:read(Io, 4), 6 | file:close(Io), 7 | print_version(list_to_binary(Start)); 8 | main(_) -> 9 | io:format("version.erl Filename~n",[]). 10 | 11 | print_version(<<_, "WS", Version>>) -> 12 | io:format("~p~n", [Version]); 13 | print_version(_) -> 14 | io:format("undef~n",[]). 15 | 16 | -------------------------------------------------------------------------------- /src/ngram.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc functions for n-gram analysis 3 | %% 4 | -module(ngram). 5 | -export([ 6 | ngram/2, ngram/3, 7 | ngramfold/2, ngramfold/3, 8 | cut_profile/2, 9 | intersect_profiles/2, 10 | expand_profiles/2, 11 | distance/2, simplified_distance/2, common_distance/2, 12 | save_profile/2, 13 | load_profile/1, 14 | merge_profiles/2, merge_profiles/1, 15 | incr_pl/3 16 | ]). 17 | -include("ngram.hrl"). 18 | 19 | 20 | %% @doc generate n-gram profile 21 | %% From a list L of elements count each N successive elements. PL is the accumulator / starting point. L with length(L) < N are being silently ignored. 22 | %% @spec ngram(integer(), profile(), list()) -> profile() 23 | ngram(N, PL, L) when length(L) < N -> 24 | PL; 25 | ngram(N, PL, [_|R]=L) -> 26 | {Key,_} = lists:split(N, L), 27 | PL2 = incr_pl(Key, PL, 1), 28 | % io:format("~p ~p ~p~n", [length(PL2), Key, length(L)]), 29 | ngram(N, PL2, R). 30 | 31 | %% @doc same as ngram(N, [], L) 32 | ngram(N, L) -> ngram(N, [], L). 33 | 34 | %% @doc increase integer value of property list entry by I 35 | incr_pl(Key, PL, I) -> 36 | Val = proplists:get_value(Key, PL, 0), 37 | PL2 = proplists:delete(Key, PL), 38 | P = proplists:property(Key, Val+I), 39 | [P|PL2]. 40 | 41 | 42 | %% @doc generate n-gram from a list of lists 43 | ngramfold(N, LL) -> ngramfold(N, [], LL). 44 | ngramfold(N, PL0, LL) -> 45 | % io:format("L=~p~n", [length(LL)]), 46 | lists:foldl(fun(L, PL) -> 47 | ngram(N, PL, L) 48 | end, PL0, LL). 49 | 50 | %% @doc limit n-gram profile to the Length 51 | cut_profile(Length, P) when length(P) < Length -> 52 | P; 53 | cut_profile(Length, P) -> 54 | {SP, _} = lists:split(Length, lists:reverse(lists:keysort(2, P))), 55 | SP. 56 | 57 | %% @doc calculate P1 cap P2 / all common keys of two profiles 58 | %% the resulting profiles are of equal length 59 | %% @spec intersect_profiles(profile(), profile()) -> {profile(), profile()} 60 | intersect_profiles(P1, P2) -> 61 | P1out = lists:filter(fun({K,_}) -> lists:keymember(K, 1, P2) end, P1), 62 | P2out = lists:filter(fun({K,_}) -> lists:keymember(K, 1, P1out) end, P2), 63 | {P1out, P2out}. 64 | 65 | %% @doc calculate P1 cup P2 / add missing keys with value 0 66 | %% the resulting profiles are of equal length 67 | %% @spec expand_profiles(profile(), profile()) -> {profile(), profile()} 68 | expand_profiles(P1, P2) -> 69 | P1a = [{K,0} || {K,_} <- lists:filter(fun({K,_}) -> not lists:keymember(K, 1, P2) end, P1)], 70 | P2a = [{K,0} || {K,_} <- lists:filter(fun({K,_}) -> not lists:keymember(K, 1, P1) end, P2)], 71 | {lists:append(P1, P2a), lists:append(P2, P1a)}. 72 | 73 | %% @doc calculate distance between profiles. 74 | %% profiles must be of equal length and contain the same keys 75 | %% @spec distance(profile(), profile()) -> Distance::float() 76 | distance(P1, P2) when length(P1) =:= length(P2) -> 77 | Ndist = lists:map(fun({K, V1}) -> 78 | {value, {K, V2}} = lists:keysearch(K, 1, P2), 79 | X = (2 * (V1-V2)) / (V1 + V2), 80 | X*X 81 | end, P1), 82 | lists:sum(Ndist). 83 | 84 | %% @doc simplified profile intersection (SPI) distance. 85 | %% intersect, then calculate distance 86 | %% @spec simplified_distance(profile(), profile()) -> {IntersectionLength::integer(), Distance::float()} 87 | simplified_distance(P1, P2) -> 88 | {P1a, P2a} = intersect_profiles(P1, P2), 89 | Ndist = distance(P1a, P2a), 90 | {length(P1a), Ndist}. 91 | 92 | %% @doc common distance 93 | %% expand profiles, then calculate distance 94 | %% @spec common_distance(profile(), profile()) -> {IntersectionLength::integer(), Distance::float()} 95 | common_distance(P1, P2) -> 96 | {P1a, P2a} = expand_profiles(P1, P2), 97 | Ndist = distance(P1a, P2a), 98 | {length(P1a), Ndist}. 99 | 100 | %% @doc merge two profiles 101 | %% @spec merge_profiles(profile(), profile()) -> profile() 102 | merge_profiles(P1, P2) -> 103 | lists:foldl(fun({K, V}, Acc) -> 104 | incr_pl(K, Acc, V) 105 | end, P1, P2). 106 | 107 | %% @doc merge many profiles 108 | %% @spec merge_profiles([profile()]) -> profile() 109 | merge_profiles([]) -> []; 110 | merge_profiles([P1|L]) -> 111 | lists:foldl(fun(P, Acc) -> merge_profiles(Acc, P) end, P1, L). 112 | 113 | 114 | save_profile(Filename, #ngramprofile{}=P) -> 115 | file:write_file(Filename, term_to_binary(P)). 116 | load_profile(Filename) -> 117 | {ok, B} = file:read_file(Filename), 118 | binary_to_term(B). 119 | 120 | -------------------------------------------------------------------------------- /src/ssamod.erl: -------------------------------------------------------------------------------- 1 | -module(ssamod). 2 | -compile(export_all). 3 | 4 | modlist() -> 5 | Dir = filename:dirname(code:which(swf)), 6 | {ok, DirList} = file:list_dir(Dir), 7 | ModList = lists:filter(fun("ssamod_"++_) -> true; (_) -> false end, [filename:basename(N, code:objfile_extension()) || N <- DirList]), 8 | [list_to_atom(X) || X <- ModList]. 9 | %lists:keysort(2, lists:map(fun("ssamod_"++ShortName = Mod) -> {list_to_atom(Mod), ShortName} end, ModList)). 10 | 11 | commands(Mods) -> 12 | Cmds = lists:map(fun(Mod) -> 13 | Attrs = Mod:module_info(attributes), 14 | C1 = proplists:get_value(commands, Attrs, []), 15 | lists:map(fun({CmdName, CmdDetails}) -> 16 | {CmdName, Mod, CmdDetails} 17 | end, C1) 18 | end, Mods), 19 | lists:append(Cmds). 20 | 21 | commands() -> 22 | commands(modlist()). 23 | 24 | 25 | traverse_files(Fun, Filenames) -> 26 | lists:foreach(fun(F) -> 27 | io:format("processing file ~p~n", [F]), 28 | try Fun(F) of 29 | _ -> ok 30 | catch 31 | throw:no_swf -> 32 | io:format(" no swf~n", []); 33 | error:data_error -> 34 | io:format(" data error. unable to uncompress file~n", []) 35 | end 36 | end, Filenames). 37 | -------------------------------------------------------------------------------- /src/ssamod_abcdump.erl: -------------------------------------------------------------------------------- 1 | -module(ssamod_abcdump). 2 | -export([run/1]). 3 | 4 | -commands([ 5 | {"abcdump", [ 6 | {usage, "abcdump [version|cpool|metadata|scripts|methods|instances|classes]..."}, 7 | {shorthelp, "dump SWF's ABC-parts in human readable format"}]}, 8 | {"abcdump-raw", [ 9 | {usage, "abcdump-raw "}, 10 | {shorthelp, "raw ABC dump"}]}, 11 | {"jsonabc", [ 12 | {usage, "jsonabc "}, 13 | {shorthelp, "dump ABC as JSON (incomplete implementation / work in progress)"}, 14 | {longhelp, " (1) when supplied with an ABC file -> dump JSON object {...} 15 | (2) otherwise dump Array of JSON objects [{...},...], since one SWF may contain more than one doABC tag"}]} 16 | ]). 17 | 18 | 19 | run(["abcdump", Filename|Parts]) -> 20 | Bin = swf:readfile(Filename), 21 | case swfmime:getmime(Bin) of 22 | swf -> 23 | RawSwf = swf:parsetorawtags(Bin), 24 | AbcList = swfutils:abcdata(RawSwf), 25 | lists:foreach(fun(AbcBinary) -> abcdump(AbcBinary, Parts) end, AbcList); 26 | abc -> 27 | abcdump(Bin, Parts); 28 | _ -> 29 | io:format("unknown fileformat~n") 30 | end; 31 | 32 | run(["abcdump-raw", Filename]) -> 33 | {X,_} = swfabc:abc(swf:readfile(Filename)), 34 | io:format("~p~n", [X]); 35 | 36 | run(["jsonabc", Filename]) -> 37 | Bin = swf:readfile(Filename), 38 | Obj = case swfmime:getmime(Bin) of 39 | swf -> 40 | RawSwf = swf:parsetorawtags(Bin), 41 | AbcList = swfutils:abcdata(RawSwf), 42 | lists:map(fun(AbcBinary) -> 43 | {Abc,_} = swfabc:abc(AbcBinary), 44 | swfjsonabc:obj(Abc) 45 | end, AbcList); 46 | abc -> 47 | {Abc,_} = swfabc:abc(Bin), 48 | swfjsonabc:obj(Abc); 49 | _ -> 50 | io:format("unknown fileformat~n") 51 | end, 52 | JsonStr = rfc4627:encode(Obj), 53 | io:format("~s~n", [JsonStr]); 54 | 55 | run(_) -> invalid_usage. 56 | 57 | abcdump(AbcBinary, Parts) -> 58 | io:format("~n----- ABCDUMP -----~n", []), 59 | {ABC,_} = swfabc:abc(AbcBinary), 60 | P = case Parts of 61 | [] -> all; 62 | _ -> Parts 63 | end, 64 | swfabcformat:abc(standard_io, ABC, P). 65 | -------------------------------------------------------------------------------- /src/ssamod_api.erl: -------------------------------------------------------------------------------- 1 | -module(ssamod_api). 2 | -export([run/1]). 3 | -include("swf.hrl"). 4 | 5 | -commands([ 6 | {"api", [ 7 | {usage, "api"}, 8 | {shorthelp, "experimental api"} 9 | ]} 10 | ]). 11 | 12 | 13 | run(["api"]) -> 14 | Commands = ssamod:commands(), 15 | 16 | api_loop(Commands), 17 | ok; 18 | 19 | run(_) -> invalid_usage. 20 | 21 | api_loop(Commands) -> 22 | % io:format("~p~n", [Commands]), 23 | case io:get_line("100 ready.\n") of 24 | eof -> 25 | ok; 26 | {error, Err} -> 27 | io:format("500 api error: ~p~n", [Err]); 28 | Input -> 29 | case string:tokens(string:strip(Input, right, $\n), " \t") of 30 | [] -> io:format("501 empty command~n"); 31 | [Cmd|Args] -> 32 | case lists:keysearch(Cmd, 1, Commands) of 33 | {value, {_, Mod, _}} -> 34 | {Code, Msg} = run(Mod, [Cmd|Args]), 35 | io:format("~p ~s~n", [Code, Msg]); 36 | _ -> 37 | io:format("404 invalid command~n") 38 | end 39 | end, 40 | 41 | api_loop(Commands) 42 | end. 43 | 44 | run(Mod, Args) -> 45 | try Mod:run(Args) of 46 | ok -> {200, "ok"}; 47 | invalid_usage -> {405, "invalid usage"}; 48 | Err -> {201, lists:flatten(io_lib:format("~p", [Err]))} 49 | catch 50 | error:Err -> {502, lists:flatten(io_lib:format("~p, ~p", [Err, erlang:get_stacktrace()]))} 51 | end. 52 | -------------------------------------------------------------------------------- /src/ssamod_check.erl: -------------------------------------------------------------------------------- 1 | -module(ssamod_check). 2 | -export([run/1]). 3 | 4 | -commands([ 5 | {"check", [ 6 | {usage, "check ..."}, 7 | {shorthelp, "check file for security issues (experimental/incomplete)"}, 8 | {longhelp, "checks SWF for 9 | (1) unknown tags and 10 | (2) branch offset obfuscation (applicable for AS2/AVM1 only)"}]} 11 | ]). 12 | 13 | run(["check" | Filenames]) -> 14 | ssamod:traverse_files(fun(Filename) -> 15 | try swfutils:dumpsecuritycheck(swf:swffile(Filename)) of 16 | _ -> ok 17 | catch 18 | error:X -> 19 | io:format("ERROR: ~p stacktrace: ~p~n", [X, erlang:get_stacktrace()]) 20 | end 21 | end, Filenames); 22 | 23 | run(_) -> invalid_usage. 24 | -------------------------------------------------------------------------------- /src/ssamod_ngram.erl: -------------------------------------------------------------------------------- 1 | -module(ssamod_ngram). 2 | -export([run/1]). 3 | -include("ngram.hrl"). 4 | 5 | -commands([ 6 | {"ngram", [ 7 | {usage, "ngram "}, 8 | {shorthelp, "statistical classification"}, 9 | {longhelp, "commands: 10 | new 11 | create new profile with given parameters, e.g. ngram new foo.profile foo abc 3 200 12 | 13 | debug dump 14 | debug-dump profile 15 | 16 | debug actions 17 | debug list actions 18 | 19 | add ... 20 | populate profile -- training 21 | 22 | distance ... -- ..."}]} 23 | ]). 24 | 25 | 26 | run(["ngram", "new", Filename, Name, Type, N, L]) -> 27 | ngram:save_profile(Filename, 28 | #ngramprofile{ 29 | name=Name, 30 | type=list_to_atom(Type), 31 | n=list_to_integer(N), 32 | l=list_to_integer(L), 33 | data=[] 34 | }), 35 | ok; 36 | 37 | run(["ngram", "debug", "actions", Filename]) -> 38 | io:format("~p~n", [getactionoplist(Filename)]), ok; 39 | 40 | run(["ngram", "debug", "dump", Filename]) -> 41 | P = ngram:load_profile(Filename), 42 | io:format("~p~n", [P]), 43 | ok; 44 | 45 | run(["ngram", "add", ProfileFilename | Filenames]) -> 46 | %% load profile 47 | P1 = ngram:load_profile(ProfileFilename), 48 | N = P1#ngramprofile.n, 49 | L = P1#ngramprofile.l, 50 | io:format("profile ~s: n=~p l=~p~n", [P1#ngramprofile.name, N, L]), 51 | 52 | Profile = lists:foldl(fun(F, PL) -> 53 | calcprofile(F, PL, N, L, P1#ngramprofile.type) 54 | end, P1#ngramprofile.data, Filenames), 55 | 56 | %% save profile 57 | ngram:save_profile(ProfileFilename, P1#ngramprofile{data=Profile}), 58 | ok; 59 | 60 | run(["ngram", "distance" | FL]) -> 61 | {PLs, ["--"| FNs]} = lists:splitwith(fun(A) -> A =/= "--" end, FL), 62 | AProfiles = lists:map(fun(Fn) -> ngram:load_profile(Fn) end, PLs), 63 | 64 | [P1|_] = AProfiles, 65 | N = P1#ngramprofile.n, 66 | % L = P1#ngramprofile.l, 67 | 68 | PProfiles = lists:map(fun(Fn) -> 69 | {Fn, calcprofile(Fn, [], N, 20000, P1#ngramprofile.type)} 70 | end, FNs), 71 | 72 | lists:foreach(fun({Fn, PP}) -> 73 | Dists = lists:map(fun(#ngramprofile{data=AP, name=APname}) -> 74 | Dist = ngram:simplified_distance(AP, PP), 75 | {APname, Dist} 76 | end, AProfiles), 77 | 78 | io:format("dists for ~p~n", [Fn]), 79 | lists:foreach(fun({Name, {Intersect, Dist}}) -> io:format(" profile ~s: intersect=~p, distance=~p~n", [Name, Intersect, Dist]) end, Dists) 80 | end, PProfiles), 81 | ok; 82 | 83 | run(_) -> invalid_usage. 84 | 85 | 86 | calcprofile(F, P0, N, L, Type) -> 87 | io:format("processing file ~p~n", [F]), 88 | LL = case Type of 89 | abc -> getabcoplist(F); 90 | action -> getactionoplist(F); 91 | T -> throw({unknown_profile_type, T}) 92 | end, 93 | 94 | %% print stats 95 | NumFuncs = length(LL), 96 | NumOps = lists:sum([length(X) || X <- LL]), 97 | io:format(" ~p functions with ~p opcodes in total~n", [NumFuncs, NumOps]), 98 | 99 | %% calculate profile 100 | NewProfile = ngram:ngramfold(N, P0, LL), 101 | ngram:cut_profile(L, NewProfile). 102 | 103 | 104 | getabcoplist(Filename) -> 105 | Bin = swf:readfile(Filename), 106 | case swfmime:getmime(Bin) of 107 | swf -> 108 | RawSwf = swf:parsetorawtags(Bin), 109 | AbcList = swfutils:abcdata(RawSwf), 110 | LLs = lists:map(fun(B) -> 111 | {Abc,_} = swfabc:abc(B), 112 | swfutils:abc2oplist(Abc) 113 | end, AbcList), 114 | lists:append(LLs); 115 | abc -> 116 | {Abc,_} = swfabc:abc(Bin), 117 | Abc; 118 | _ -> 119 | io:format("unknown fileformat~n"), [] 120 | end. 121 | 122 | getactionoplist(Filename) -> 123 | Bin = swf:readfile(Filename), 124 | RawSwf = swf:parsetorawtags(Bin), 125 | ActionL = swfutils:actiondata(RawSwf), 126 | lists:map(fun(AL) -> 127 | swfutils:actions2oplist(AL) 128 | end, ActionL). 129 | 130 | -------------------------------------------------------------------------------- /src/ssamod_swfdump.erl: -------------------------------------------------------------------------------- 1 | -module(ssamod_swfdump). 2 | -export([run/1]). 3 | 4 | -commands([ 5 | {"dump", [ 6 | {usage, "dump "}, 7 | {shorthelp, "dump whole SWF"}, 8 | {longhelp, " 9 | example: 10 | $ ssacli dump foo.swf 11 | ** header: {swfheader,cws,10,352780288, 12 | {rect,0,10000,0,7500}, 13 | 24,1,13, 14 | <<120,0,4,226,0,0,14,166,0,0,24,1,0>>} 15 | ** <00000002> [069] fileAttributes : [{hasMetadata,1}, 16 | {actionScript3,1}, 17 | {useNetwork,1}] 18 | ... 19 | 20 | tag output syntax 21 | **
[tag-id] tag-name : tag-contents 22 | address points to the first byte of the tag's contents / skips the header 23 | "}]}, 24 | {"rawdump", [ 25 | {usage, "rawdump "}, 26 | {shorthelp, "debug dump"}]}, 27 | {"filedump", [ 28 | {usage, "filedump "}, 29 | {shorthelp, "dump whole SWF to file structure"}, 30 | {longhelp,"the following example creates various dump files dump/foo-* from foo.swf 31 | 32 | create dump directory 33 | $ mkdir dump 34 | 35 | dump: 36 | $ ssacli filedump foo.swf dump/foo"}]}, 37 | {"dumptags", [ 38 | {usage, "dumptags ..."}, 39 | {shorthelp, "dump specified tags only"}]}, 40 | {"test", [ 41 | {usage, "test ..."}, 42 | {shorthelp, "test library (read swf w/o dump)"}]}, 43 | {"jsondump", [ 44 | {usage, "jsondump "}]} 45 | ]). 46 | 47 | 48 | run(["dump", Filename]) -> 49 | swfutils:dumpswf(swf:swffile(Filename)), ok; 50 | 51 | run(["rawdump", Filename]) -> 52 | io:format("~p~n", [swf:swffile(Filename)]); 53 | 54 | run(["filedump", Filename, Prefix]) -> 55 | swfutils:filedumpswf(swf:swffile(Filename), Prefix), ok; 56 | 57 | run(["dumptags", Filename | Tagnames]) -> 58 | swfutils:dumptags(swf:parsetorawtags(swf:readfile(Filename)), [list_to_atom(Tagname) || Tagname <- Tagnames]), ok; 59 | 60 | run(["test" | Filenames]) -> 61 | ssamod:traverse_files(fun(Filename) -> 62 | statistics(runtime), statistics(wall_clock), %% start the clock 63 | 64 | %% decode swf 65 | {swf, _H, _Tags} = swf:swffile(Filename), 66 | 67 | %% stop the clock 68 | {_, Time1} = statistics(runtime), 69 | {_, Time2} = statistics(wall_clock), 70 | io:format(" runtime: ~p~n realtime:~p~n", [Time1, Time2]) 71 | end, Filenames); 72 | 73 | run(["jsondump", Filename]) -> 74 | Swf = swf:swffile(Filename), 75 | Obj = swfjson:obj(Swf), 76 | JsonStr = rfc4627:encode(Obj), 77 | io:format("~s~n", [JsonStr]); 78 | 79 | run(_) -> invalid_usage. -------------------------------------------------------------------------------- /src/ssamod_version.erl: -------------------------------------------------------------------------------- 1 | -module(ssamod_version). 2 | -export([run/1]). 3 | 4 | -commands([ 5 | {"version", [ 6 | {usage, "version "}, 7 | {shorthelp, "determine SWF version"}, 8 | {longhelp, "lirum larum"}]} 9 | ]). 10 | 11 | 12 | run(["version", Filename]) -> 13 | {ok, Io} = file:open(Filename, [read]), 14 | {ok, Start} = file:read(Io, 4), 15 | file:close(Io), 16 | PrintVersion = 17 | fun(<<_, "WS", Version>>) -> 18 | io:format("~p~n", [Version]); 19 | (_) -> 20 | io:format("undef~n",[]) 21 | end, 22 | PrintVersion(list_to_binary(Start)), 23 | ok; 24 | 25 | run(_) -> invalid_usage. -------------------------------------------------------------------------------- /src/swf.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% simple swf analyzer 3 | %% author: Ben Fuhrmannek 4 | %% license: GPLv3 - http://www.gnu.org/licenses/licenses.html#GPL 5 | %% 6 | 7 | -module(swf). 8 | -include("swf.hrl"). 9 | 10 | -export([ 11 | debug/2, 12 | readfile/1, 13 | uncompress/1, 14 | headerdecode/1, 15 | tagsplit/1, 16 | tagdecode/1, 17 | parsetorawtags/1, 18 | swf/1, 19 | swffile/1, 20 | tag/2 21 | ]). 22 | 23 | 24 | %debug(Fmt, Args) -> io:format("DEBUG: " ++ Fmt, Args). %% display message 25 | debug(_, _) -> foo. %% do nothing 26 | 27 | %% 28 | %% edoc type definitions 29 | %% 30 | %% @type paramlist() = [{key(), value()}] where key() = atom() 31 | %% value() = any() 32 | %% 33 | 34 | %% 35 | %% swf parser 36 | %% 37 | 38 | %% @doc read a whole file 39 | %% @spec readfile(string()) -> binary() 40 | readfile(Filename) -> 41 | {ok, B} = file:read_file(Filename), 42 | B. 43 | 44 | %% @doc read swf type, version, file length and uncompress if necessary 45 | %% @spec uncompress(binary()) -> binary() | atom() 46 | %% @throws no_swf 47 | uncompress(<<"FWS", Version, FileLength:32, B/binary>>) -> 48 | {rawswf1, 49 | #swfheader{type=fws, version=Version, filelength=FileLength}, 50 | B}; 51 | 52 | uncompress(<<"CWS", Version, FileLength:32, B/binary>>) -> 53 | {rawswf1, 54 | #swfheader{type=cws, version=Version, filelength=FileLength}, 55 | zlib:uncompress(B)}; 56 | 57 | uncompress(_) -> throw(no_swf). 58 | 59 | %% @doc decode swf header 60 | %% @spec headerdecode({rawswf(), header1(), binary()}) -> {rawswf, paramlist(), binary()} 61 | headerdecode({rawswf1, Header1, Raw}) -> 62 | RawSize = size(Raw), 63 | {FrameSize, <<_FrameRateIgn:8, 64 | FrameRate:8/unsigned-integer, 65 | FrameCount:16/unsigned-integer-little, 66 | R/binary>>} = swfdt:rect(Raw), 67 | HeaderSize = RawSize - size(R), 68 | <> = Raw, 69 | 70 | %% assemble swf structure of the form {rawswf, Header, RawTags} 71 | {rawswf, 72 | Header1#swfheader{ 73 | framesize=FrameSize, 74 | framerate=FrameRate, 75 | framecount=FrameCount, 76 | headersize=HeaderSize, 77 | rawheader=RawHeader}, 78 | R}. 79 | 80 | %% @doc split raw tag blob into tag list 81 | %% @spec tagsplit(binary()) -> [rawtag()] 82 | tagsplit(B) -> 83 | tagsplit(B, 0, []). 84 | 85 | tagsplit(<<>>, _, Acc) -> 86 | lists:reverse(Acc); 87 | 88 | tagsplit(<>, InPos, Acc) -> 89 | %% decode tag code and preliminary tag length 90 | <> = <>, 91 | 92 | %% decode possibly overflowing length 93 | {TagLength, B2} = case TagLength0 of 94 | 16#3f -> 95 | <> = B, 96 | {Length, R1}; 97 | _ -> 98 | {TagLength0, B} 99 | end, 100 | 101 | %% cut tag by length 102 | {{Code, Name, Raw}, Rest} = tagcut(TagCode, TagLength, B2), 103 | debug("found tag: ~p~n", [{Code, Name}]), 104 | 105 | %% recalculate position in binary 106 | Pos = InPos + 2 + (size(B)-size(B2)), %% add bytes (tag code and length) to current binary position in taglist 107 | NewPos = InPos + 2 + (size(B) - size(Rest)), 108 | 109 | tagsplit(Rest, NewPos, [#tag{code=Code, name=Name, pos=Pos, raw=Raw} | Acc]). 110 | 111 | tagcut(Code, Length, B) -> 112 | <> = B, 113 | Name = tag(name, Code), 114 | {{Code, Name, TB}, R}. 115 | 116 | 117 | %% @doc decode rawtag 118 | %% @spec tagdecode(rawtag()) -> tag() 119 | %% where 120 | %% tagcode() = integer() 121 | %% pos() = integer() 122 | %% raw() = binary() 123 | tagdecode(#tag{code=Code, name=Name, pos=Pos, raw=Raw, contents=undefined} = InTag) -> 124 | DecodedTag = try tag(Code, Raw) of 125 | [unknown] -> 126 | error_logger:warning_msg("<~8.10.0B> [~3.10.0B] ~p : unable to decode tag correctly~n", [Pos, Code, Name]), 127 | [unknown]; 128 | Val -> Val 129 | catch 130 | error:X -> 131 | error_logger:warning_msg("<~8.10.0B> [~3.10.0B] ~p : ~p~n ~p~n", [Pos, Code, Name, X, erlang:get_stacktrace()]), 132 | [{error, {Code, Name, X}}] 133 | end, 134 | debug("decoded tag: ~p~n", [InTag#tag{contents=DecodedTag}]), 135 | InTag#tag{contents=DecodedTag}; 136 | tagdecode(#tag{contents=C} = Tag) when C =/= undefined -> 137 | Tag. 138 | 139 | %% 140 | %% convenience interface 141 | %% 142 | 143 | %% @doc swf blob to swf structure with rawtags 144 | %% @spec parsetorawtags(binary()) -> {swf, swfheader(), [rawtag()]} 145 | %% where swfheader() = paramlist() 146 | parsetorawtags(B) -> 147 | {rawswf, Header, RawTags} = headerdecode(uncompress(B)), 148 | {swf, Header, swf:tagsplit(RawTags)}. 149 | 150 | %% @doc swf blob to swf structure with decoded tags 151 | %% @spec swf(binary()) -> {swf, swfheader(), [tag()]} 152 | swf(B) -> %% decode all tags 153 | {swf, Header, RawTags} = parsetorawtags(B), 154 | {swf, Header, lists:map(fun tagdecode/1, RawTags)}. 155 | 156 | %% @doc convert swf file to fully decoded swf structure 157 | swffile(Filename) -> 158 | swf(readfile(Filename)). 159 | 160 | 161 | 162 | 163 | %% 164 | %% tags 165 | %% 166 | 167 | %% @doc decode tag by code OR get name using tag(name, Code) 168 | %% @spec tag(tagcode(), binary()) -> paromlist() 169 | tag(name, 0) -> 'endTag'; 170 | tag(0, _) -> 171 | []; 172 | 173 | tag(name, 1) -> 'showFrame'; 174 | tag(1, _) -> 175 | []; 176 | 177 | tag(name, 2) -> 'defineShape'; 178 | tag(2, <>) -> 179 | {Bounds, ShapeWithStyle} = swfdt:rect(B), 180 | 181 | %% SHAPEWITHSTYLE 182 | {FSA, R1} = swfdt:fillstylearray(shape1, ShapeWithStyle), 183 | {LSA, _R2} = swfdt:linestylearray(shape1, R1), 184 | %{Shape, _} = style(R2), 185 | 186 | [{shapeID, ShapeID}, 187 | {shapeBounds, Bounds}, 188 | {fillStyles, FSA}, 189 | {lineStyles, LSA}, 190 | unimplemented]; 191 | 192 | %% tag 3 is unused 193 | 194 | tag(name, 4) -> 'placeObject'; 195 | tag(4, <>) -> 196 | {M, R1} = swfdt:matrix(B), 197 | {CX, _} = swfdt:cxform(R1), 198 | [{characterID, CharacterID}, 199 | {depth, Depth}, 200 | {matrix, M}, 201 | {colorTransform, CX}]; 202 | 203 | tag(name, 5) -> 'removeObject'; 204 | tag(5, <>) -> 205 | [{characterID, CharacterID}, {depth, Depth}]; 206 | 207 | tag(name, 6) -> 'defineBits'; 208 | tag(6, <>) -> 209 | [{characterID, CharacterID}, {jpegImage, Data}]; 210 | 211 | tag(name, 7) -> 'defineButton'; 212 | tag(7, <>) -> 213 | {BR, R1} = swfdt:buttonrecords(B), 214 | {actions, Actions, _} = swfaction:actionrecords(R1), 215 | [{buttonID, ButtonID}, {buttonrecords, BR}, {actions, Actions}]; 216 | 217 | tag(name, 8) -> 'jpegTables'; 218 | tag(8, Data) -> 219 | [{jpegEncodingTable, Data}]; 220 | 221 | tag(name, 9) -> 'setBackgroundColor'; 222 | tag(9, <>) -> 223 | [{rgb, R, G, B}]; 224 | 225 | tag(name, 10) -> 'defineFont'; 226 | tag(10, <>) -> 227 | [{fontID, FontID}, unimplemented]; 228 | 229 | tag(name, 11) -> 'defineText'; 230 | tag(11, <>) -> 231 | {TextBounds, R1} = swfdt:rect(B), 232 | {TextMatrix, <<_GlyphBits, _AdvanceBits, _R2/binary>>} = swfdt:matrix(R1), 233 | %{textrecords, TextRecords, _} = textrecords({GlyphBits, AdvanceBits, 0}, R2), 234 | [ 235 | {characterID, CharacterID}, 236 | {textBounds, TextBounds}, 237 | {textMatrix, TextMatrix}, 238 | unimplemented 239 | % {textrecords, TextRecords} 240 | ]; 241 | 242 | tag(name, 12) -> 'doAction'; 243 | tag(12, ActionRecord) -> 244 | {actions, Actions, _} = swfaction:actionrecords(ActionRecord), 245 | [{actions, Actions}]; 246 | 247 | tag(name, 13) -> 'defineFontInfo'; 248 | tag(13, <>) -> 259 | [ 260 | {fontID, FontID}, 261 | {fontName, binary_to_list(FontName)}, 262 | {smallText, SmallText}, 263 | {shiftJIS, ShiftJIS}, 264 | {ansi, ANSI}, 265 | {italic, Italic}, 266 | {bold, Bold}, 267 | {wideCodes, WideCodes}, 268 | {codeTable, CodeTable}]; 269 | 270 | tag(name, 14) -> 'defineSound'; 271 | tag(14, <>) -> 278 | %DataSize = (Size * 8 + 8) * SampleCount, 279 | %debug("data size ~p, bin size ~p~n", [DataSize, size(B)]), 280 | %<> = B, 281 | [ 282 | {id, ID}, 283 | {format, Format}, 284 | {rate, Rate}, 285 | {size, Size}, 286 | {type, Type}, 287 | {sampleCount, SampleCount}, 288 | {data, B}]; 289 | 290 | tag(name, 15) -> 'startSound'; 291 | tag(15, <>) -> 292 | {soundinfo, SI} = swfdt:soundinfo(B), 293 | [{id, ID}, {info, SI}]; 294 | 295 | %% tag 16: stopSound (undocumented) 296 | 297 | tag(name, 17) -> 'defineButtonSound'; 298 | tag(17, _) -> 299 | [unimplemented]; 300 | 301 | tag(name, 18) -> 'soundStreamHead'; 302 | tag(18, _) -> 303 | [unimplemented]; 304 | 305 | tag(name, 19) -> 'soundStreamBlock'; 306 | tag(19, _) -> 307 | [unimplemented]; 308 | 309 | tag(name, 20) -> 'defineBitsLossless'; 310 | tag(20, _) -> 311 | [unimplemented]; 312 | 313 | tag(name, 21) -> 'defineBitsJPEG2'; 314 | tag(21, <>) -> 315 | [{characterID, CharacterID}, {jpegData, Data}]; 316 | 317 | tag(name, 22) -> 'defineShape2'; 318 | tag(22, _) -> 319 | [unimplemented]; 320 | 321 | tag(name, 23) -> 'defineButtonCxform'; 322 | tag(23, _) -> 323 | [unimplemented]; 324 | 325 | tag(name, 24) -> 'protect'; 326 | tag(24, <<>>) -> 327 | [unprotected]; 328 | tag(24, S) -> 329 | {MD5, _} = swfdt:string(S), 330 | [{md5password, MD5}]; 331 | 332 | tag(name, 25) -> 'pathsArePostscript'; 333 | tag(25, _) -> %% lacks documentation 334 | [unimplemented]; 335 | 336 | tag(name, 26) -> 'placeObject2'; 337 | tag(26, <>) -> 347 | {CharacterID, R1} = swfdt:condfun(FlagHasCharacter, fun swfdt:ui16/1, B), 348 | {Matrix, R2} = swfdt:condfun(FlagHasMatrix, fun swfdt:matrix/1, R1), 349 | {ColorTransform, R3} = swfdt:condfun(FlagHasColorTransform, fun swfdt:cxformwithalpha/1, R2), 350 | {Ratio, R4} = swfdt:condfun(FlagHasRatio, fun swfdt:ui16/1, R3), 351 | {Name, R5} = swfdt:condfun(FlagHasName, fun(B1) -> {S, Rf} = swfdt:string(B1), {S, Rf} end, R4), 352 | {ClipDepth, R6} = swfdt:condfun(FlagHasClipDepth, fun swfdt:ui16/1, R5), 353 | {ClipActions, _} = swfdt:condfun(FlagHasClipActions, fun swfdt:clipactions/1, R6), 354 | [{depth, Depth}, 355 | {characterID, CharacterID}, 356 | {matrix, Matrix}, 357 | {colorTransform, ColorTransform}, 358 | {ratio, Ratio}, 359 | {name, Name}, 360 | {clipDepth, ClipDepth}, 361 | {flagMove, FlagMove}, 362 | {clipActions, ClipActions}, 363 | unimplemented]; 364 | 365 | %% tag 27 is unknown 366 | 367 | tag(name, 28) -> 'removeObject2'; 368 | tag(28, <>) -> 369 | [{depth, Depth}]; 370 | 371 | %% tag 29: syncFrame (undocumented) 372 | 373 | %% tag 30 is unknown 374 | 375 | %% tag 31: freeAll (undocumented) 376 | 377 | tag(name, 32) -> 'defineShape3'; 378 | tag(32, _) -> 379 | [unimplemented]; 380 | 381 | tag(name, 33) -> 'defineText2'; 382 | tag(33, _) -> 383 | [unimplemented]; 384 | 385 | tag(name, 34) -> 'defineButton2'; 386 | tag(34, <>) -> 390 | <> = B, 391 | <<_CharB:ActionOffset/binary, ActionB/binary>> = B, 392 | 393 | Characters = unimplemented, 394 | % {Characters, _} = swfdt:buttonrecords2(CharB), 395 | 396 | Actions = case ActionOffset of 397 | 0 -> []; 398 | _ -> swfdt:buttoncondactions(ActionB) 399 | end, 400 | 401 | [ 402 | {buttonID, ButtonID}, 403 | {trackAsMenu, TrackAsMenu}, 404 | {characters, Characters} 405 | ] ++ Actions; 406 | 407 | tag(name, 35) -> 'defineBitsJPEG3'; 408 | tag(35, << CharacterID:16/unsigned-integer-little, 409 | AlphaDataOffset:32/unsigned-integer-little, 410 | JPEGData:AlphaDataOffset/binary, 411 | BitmapAlphaData/binary>>) -> 412 | [ 413 | {characterID, CharacterID}, 414 | {jpegData, JPEGData}, 415 | {bitmapAlphaData, zlib:uncompress(BitmapAlphaData)}]; 416 | 417 | tag(name, 36) -> 'defineBitsLossless2'; 418 | tag(36, _) -> 419 | [unimplemented]; 420 | 421 | tag(name, 37) -> 'defineEditText'; 422 | tag(37, << CharacterID:16/unsigned-integer-little, R/binary>>) -> 423 | {Bounds, R1} = swfdt:rect(R), 424 | <> = R1, 425 | Flags = lists:foldl(fun(I, FAcc) -> 426 | case Flagbits band (1 bsl (15-I)) of 427 | 0 -> FAcc; 428 | _ -> 429 | case I of 430 | 0 -> [hasText|FAcc]; 431 | 1 -> [wordWrap|FAcc]; 432 | 2 -> [multiline|FAcc]; 433 | 3 -> [password|FAcc]; 434 | 4 -> [readOnly|FAcc]; 435 | 5 -> [hasTextColor|FAcc]; 436 | 6 -> [hasMaxLength|FAcc]; 437 | 7 -> [hasFont|FAcc]; 438 | 8 -> [hasFontClass|FAcc]; 439 | 9 -> [autoSize|FAcc]; 440 | 10 -> [hasLayout|FAcc]; 441 | 11 -> [noSelect|FAcc]; 442 | 12 -> [border|FAcc]; 443 | 13 -> [wasStatic|FAcc]; 444 | 14 -> [html|FAcc]; 445 | 15 -> [useOutlines|FAcc] 446 | end 447 | end 448 | end, [], lists:seq(0,15)), 449 | {Opt, _Rest} = lists:foldl(fun(El, {Acc, B}) -> 450 | case El of 451 | a -> 452 | case lists:member(hasFont, Flags) of 453 | true -> {FontID, B1} = swfdt:ui16(B), {[{fontID, FontID}|Acc], B1}; 454 | false -> {Acc, B} 455 | end; 456 | b -> 457 | case lists:member(hasFontClass, Flags) of 458 | true -> {FontClass, B1} = swfdt:string(B), {[{fontClass, FontClass}|Acc], B1}; 459 | false -> {Acc, B} 460 | end; 461 | c -> 462 | case lists:member(hasFont, Flags) of 463 | true -> {FontHeight, B1} = swfdt:ui16(B), {[{fontHeight, FontHeight}|Acc], B1}; 464 | false -> {Acc, B} 465 | end; 466 | d -> 467 | case lists:member(hasTextColor, Flags) of 468 | true -> {TextColor, B1} = swfdt:rgba(B), {[{textColor, TextColor}|Acc], B1}; 469 | false -> {Acc, B} 470 | end; 471 | e -> 472 | case lists:member(hasMaxLength, Flags) of 473 | true -> {MaxLength, B1} = swfdt:ui16(B), {[{maxLength, MaxLength}|Acc], B1}; 474 | false -> {Acc, B} 475 | end; 476 | f -> 477 | case lists:member(hasLayout, Flags) of 478 | true -> 479 | <> = B, 485 | AlignName = case Align of 486 | 0 -> left; 487 | 1 -> right; 488 | 2 -> center; 489 | 3 -> justify; 490 | _ -> unknown 491 | end, 492 | {[{align, AlignName}, {leftMargin, LeftMargin}, {rightMargin, RightMargin}, {indent, Indent}, {leading, Leading}|Acc], B1}; 493 | false -> {Acc, B} 494 | end; 495 | g -> 496 | {VarName, B1} = swfdt:string(B), {[{variableName, VarName}|Acc], B1}; 497 | h -> 498 | case lists:member(hasText, Flags) of 499 | true -> {InitialText, B1} = swfdt:string(B), {[{initialText, InitialText}|Acc], B1}; 500 | false -> {Acc, B} 501 | end 502 | end 503 | end, {[], R2}, [a,b,c,d,e,f,g,h]), 504 | 505 | [ 506 | {characterID, CharacterID}, 507 | {bounds, Bounds}, 508 | {flags, Flagbits}, 509 | {flagsArr, Flags}] ++ Opt; 510 | 511 | %% tag 38: defineVideo (undocumented) 512 | 513 | tag(name, 39) -> 'defineSprite'; 514 | tag(39, _) -> 515 | [unimplemented]; 516 | 517 | %% tag 40: nameCharacter (undocumented) 518 | 519 | tag(name, 41) -> 'productInfo'; 520 | tag(41, <>) -> %% ?? 526 | [ 527 | {productID, ProductID}, 528 | {edition, Edition}, 529 | {majorVersion, MajorVersion}, 530 | {minorVersion, MinorVersion}, 531 | {buildNumber, BuildNumber}, 532 | {compilationDate, CompilationDate}]; 533 | 534 | %% tag 42: defineTextFormat (undocumented) 535 | 536 | tag(name, 43) -> 'frameLabel'; 537 | tag(43, S) -> 538 | {Label, NA} = swfdt:string(S), 539 | [{namedAnchorFlag, NA}, {label, Label}]; 540 | 541 | %% tag 44 is unknown 542 | 543 | tag(name, 45) -> 'soundStreamHead2'; 544 | tag(45, _) -> 545 | [unimplemented]; 546 | 547 | tag(name, 46) -> 'defineMorphShape'; 548 | tag(46, _) -> 549 | [unimplemented]; 550 | 551 | %% tag 47: generateFrame (undocumented) 552 | 553 | tag(name, 48) -> 'defineFont2'; 554 | tag(48, _) -> 555 | [unimplemented]; 556 | 557 | %% tag 49: generatorCommand (undocumented) 558 | %% tag 50: defineCommandObject (undocumented) 559 | %% tag 51: characterSet (undocumented) 560 | %% tag 52: externalFont (undocumented) 561 | %% tags 53 to 55 are unknown 562 | 563 | tag(name, 56) -> 'exportAssets'; 564 | tag(56, <>) -> 565 | [{count, Count}, {assets, swfdt:assets(Assets)}]; 566 | 567 | tag(name, 57) -> 'importAssets'; 568 | tag(57, <>) -> 569 | {URL, <>} = swfdt:string(B), 570 | [{count, Count}, {url, URL}, {assets, swfdt:assets(Assets)}]; 571 | 572 | tag(name, 58) -> 'enableDebugger'; 573 | tag(58, S) -> 574 | {MD5, _} = swfdt:string(S), 575 | [{md5password, MD5}]; 576 | 577 | tag(name, 59) -> 'doInitAction'; 578 | tag(59, <>) -> 579 | {actions, Actions, _} = swfaction:actionrecords(ActionRecord), 580 | [{spriteID, SpriteID}, {actions, Actions}]; 581 | 582 | tag(name, 60) -> 'defineVideoStream'; 583 | tag(60, <>) -> 589 | [ 590 | {characterID, CharacterId}, 591 | {numFrames, NumFrames}, 592 | {width, Width}, {height, Height}, 593 | {vfDeblocking, VFDeblocking}, 594 | {vfSmoothing, VFSmoothing}, 595 | {codecId, CodecId} 596 | ]; 597 | 598 | tag(name, 61) -> 'videoFrame'; 599 | tag(61, _) -> 600 | [unimplemented]; 601 | 602 | tag(name, 62) -> 'defineFontInfo2'; 603 | tag(62, <>) -> 615 | [ 616 | {fontID, FontID}, 617 | {fontName, binary_to_list(FontName)}, 618 | {smallText, SmallText}, 619 | {shiftJIS, ShiftJIS}, 620 | {ansi, ANSI}, 621 | {italic, Italic}, 622 | {bold, Bold}, 623 | {wideCodes, WideCodes}, 624 | {languageCode, LanguageCode}, 625 | {codeTable, CodeTable}]; 626 | 627 | %% tag 63: debugID (undocumented) 628 | 629 | tag(name, 64) -> 'enableDebugger2'; 630 | tag(64, <<_Reserved:16, S/binary>>) -> 631 | {MD5, _} = swfdt:string(S), 632 | [{md5password, MD5}]; 633 | 634 | tag(name, 65) -> 'scriptLimits'; 635 | tag(65, <>) -> 636 | [{maxRecursionDepth, MaxRecursionDepth}, {scriptTimeoutSeconds, ScriptTimeoutSeconds}]; 637 | 638 | tag(name, 66) -> 'setTabIndex'; 639 | tag(66, <>) -> 640 | [{depth, Depth}, {tabIndex, TabIndex}]; 641 | 642 | %% tags 67 to 68 are unknown 643 | 644 | tag(name, 69) -> 'fileAttributes'; 645 | tag(69, <<_Reserved0:3, HasMetadata:1, ActionScript3:1, _Reserved0:2, UseNetwork:1, _Reserved0:24>>) -> 646 | [{hasMetadata, HasMetadata}, {actionScript3, ActionScript3}, {useNetwork, UseNetwork}]; 647 | 648 | tag(name, 70) -> 'placeObject3'; 649 | tag(70, << 650 | %FlagHasClipActions:1, 651 | %FlagHasClipDepth:1, 652 | %FlagHasName:1, 653 | %FlagHasRatio:1, 654 | %FlagHasColorTransform:1, 655 | %FlagHasMatrix:1, 656 | %FlagHasCharacter:1, 657 | %FlagMove:1, 658 | %_Reserved:3, 659 | %FlagHasImage:1, 660 | %FlagHasClassName:1, 661 | %FlagHasCacheAsBitmap:1, 662 | %FlagHasBlendMode:1, 663 | %FlagHasFilterList:1, 664 | %Depth:16/unsigned-integer-little, 665 | _B/binary>>) -> 666 | [unimplemented]; 667 | 668 | tag(name, 71) -> 'importAssets2'; 669 | tag(71, <>) -> 670 | {URL, <<_Reserved1, _Reserved0, Count:16/unsigned-integer-little, Assets/binary>>} = swfdt:string(B), 671 | [{count, Count}, {url, URL}, {assets, swfdt:assets(Assets)}]; 672 | 673 | %% tag 72: doABC (undocumented) 674 | tag(name, 72) -> doABC1; 675 | tag(72, <>) -> 676 | [{data, Data}]; 677 | 678 | tag(name, 73) -> 'defineFontAlignZones'; 679 | tag(73, <>) -> 682 | Thickness = case CSMTableHint of 683 | 0 -> thin; 684 | 1 -> medium; 685 | 2 -> thick; 686 | _ -> unknown 687 | end, 688 | [{fontID, FontID}, {csmTableHint, CSMTableHint}, {thickness, Thickness}, {zoneTable, unimplemented}]; 689 | 690 | tag(name, 74) -> 'csmTextSettings'; 691 | tag(74, <>) -> 696 | [ 697 | {textID, TextID}, 698 | {useFlashType, case UseFlashType of 0 -> normal; 1 -> advanced; _ -> unknown end}, 699 | {gridFit, case GridFit of 0 -> none; 1 -> pixel; 2 -> subpixel; _ -> unknown end}, 700 | {thickness, Thickness}, 701 | {sharpness, Sharpness} 702 | ]; 703 | 704 | tag(name, 75) -> 'defineFont2'; 705 | tag(75, _) -> 706 | [unimplemented]; 707 | 708 | tag(name, 76) -> 'symbolClass'; 709 | tag(76, <>) -> 710 | [{numSymbols, NumSymbols}, {assets, swfdt:assets(Assets)}]; 711 | 712 | tag(name, 77) -> 'metadata'; 713 | tag(77, S) -> 714 | {Metadata, _} = swfdt:string(S), 715 | [{xml, Metadata}]; 716 | 717 | tag(name, 78) -> 'defineScalingGrid'; 718 | tag(78, <>) -> 719 | {Splitter, _} = swfdt:rect(R), 720 | [{characterID, CharacterID}, {splitter, Splitter}]; 721 | 722 | %% tags 79 to 81 are unknown 723 | 724 | tag(name, 82) -> 'doABC'; %% sometimes referred to as 'doABC2' 725 | tag(82, <>) -> 726 | {Name, B} = swfdt:string(Data), 727 | [{flags, Flags}, {name, Name}, {data, B}]; 728 | 729 | tag(name, 83) -> 'defineShape4'; 730 | tag(83, _) -> 731 | [unimplemented]; 732 | 733 | tag(name, 84) -> 'defineMorphShape2'; 734 | tag(84, _) -> 735 | [unimplemented]; 736 | 737 | %% tag 85 is unknown 738 | 739 | tag(name, 86) -> 'defineSceneAndFrameLabelData'; 740 | tag(86, B) -> 741 | {SceneCount, R1} = swfdt:encodedu32(B), 742 | {Scenes, R2} = swfdt:metaarray(R1, SceneCount, fun(B1) -> 743 | {Offset, RS1} = swfdt:encodedu32(B1), 744 | {Name, RS2} = swfdt:string(RS1), 745 | {{Offset, Name}, RS2} 746 | end), 747 | {FrameLabelCount, R3} = swfdt:encodedu32(R2), 748 | {Labels, _} = swfdt:metaarray(R3, FrameLabelCount, fun(B2) -> 749 | {Num, RL1} = swfdt:encodedu32(B2), 750 | {Label, RL2} = swfdt:string(RL1), 751 | {{Num, Label}, RL2} 752 | end), 753 | [{scenes, Scenes}, {labels, Labels}]; 754 | 755 | tag(name, 87) -> 'binaryData'; 756 | tag(87, <>) -> 757 | [{tag, Tag}, {data, Data}]; 758 | 759 | tag(name, 88) -> 'defineFontName'; 760 | tag(88, <>) -> 761 | {Name, R} = swfdt:string(S), 762 | {Copyright, _} = swfdt:string(R), 763 | [{fontID, FontID}, {fontName, Name}, {fontCopyright, Copyright}]; 764 | 765 | tag(name, 89) -> 'startSound2'; 766 | tag(89, _) -> 767 | [unimplemented]; 768 | 769 | tag(name, 90) -> 'defineBitsJPEG4'; 770 | tag(90, <>) -> 775 | [{characterID, CharacterID}, {deblockParam, DeblockParam}, {imageData, ImageData}, {bitmapAlphaData, BitmapAlphaData}]; 776 | 777 | tag(name, 91) -> 'defineFont4'; 778 | tag(91, <>) -> 779 | {Name, R1} = swfdt:string(S), 780 | [{fontId, FontID}, {flags, Flags}, {name, Name}, {data, R1}]; 781 | 782 | tag(name, _) -> 'unknownTag'; 783 | tag(_Code, _B) -> 784 | [unknown]. 785 | 786 | -------------------------------------------------------------------------------- /src/swfabc.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc parse abc (avm2 bytecode) 3 | %% 4 | -module(swfabc). 5 | -export([abc/1, method/2, method_body/2, 6 | cpinteger/2, cpuinteger/2, cpdouble/2, cpstring/2, cpnamespace/2, cpnsset/2, cpmultiname/2, 7 | u8/1, u16/1, s24/1, u32/1, u30/1, s32/1, d64/1, string/1]). 8 | % -compile(export_all). 9 | -include("swfabc.hrl"). 10 | 11 | 12 | 13 | %% 14 | %% arrays 15 | %% 16 | 17 | array0(Fun, B) -> 18 | array0(Fun, B, []). 19 | array0(Fun, B, Args) -> 20 | {Count, B2} = u30(B), 21 | array(Fun, B2, Args, Count-1). 22 | 23 | array(Fun, B) -> 24 | array(Fun, B, []). 25 | array(Fun, B, Args) -> 26 | {Count, B2} = u30(B), 27 | array(Fun, B2, Args, Count). 28 | 29 | array(_, B, _, Count) when Count < 1 -> 30 | {[], B}; 31 | array(Fun, B, Args, Count) -> 32 | %io:format("array count: ~p~n", [Count]), 33 | {L, Bout} = lists:foldl(fun(_, {Acc, Bx}) -> 34 | {P, By} = apply(Fun, [Bx|Args]), 35 | {[P|Acc], By} 36 | end, {[], B}, lists:seq(1, Count)), 37 | {lists:reverse(L), Bout}. 38 | 39 | 40 | %% 41 | %% simple data types 42 | %% 43 | 44 | u8(<>) -> 45 | {X, R}. 46 | u16(<>) -> 47 | {X, Rest}. 48 | 49 | s24(<>) -> 50 | {X, Rest}. 51 | 52 | u32(<<0:1, A:7, R/binary>>) -> 53 | {A, R}; 54 | u32(<<1:1, A:7/bitstring, 0:1, B:7/bitstring, R/binary>>) -> 55 | <> = <<0:4, 0:7, 0:7, B/bitstring, A/bitstring>>, {X, R}; 56 | u32(<<1:1, A:7/bitstring, 1:1, B:7/bitstring, 0:1, C:7/bitstring, R/binary>>) -> 57 | <> = <<0:4, 0:7, C/bitstring, B/bitstring, A/bitstring>>, {X, R}; 58 | u32(<<1:1, A:7/bitstring, 1:1, B:7/bitstring, 1:1, C:7/bitstring, 0:1, D:7/bitstring, R/binary>>) -> 59 | <> = <<0:4, D/bitstring, C/bitstring, B/bitstring, A/bitstring>>, {X, R}; 60 | u32(<<1:1, A:7/bitstring, 1:1, B:7/bitstring, 1:1, C:7/bitstring, 1:1, D:7/bitstring, _:4/bitstring, E:4/bitstring, R/binary>>) -> 61 | <> = <>, {X, R}. 62 | 63 | u30(B) -> 64 | {X, R} = u32(B), 65 | <<_:2, Y:30/unsigned-integer-big>> = <>, 66 | {Y, R}. 67 | 68 | s32(B) -> 69 | {X, R} = u32(B), 70 | % Y = case X > (1 bsl 31) of 71 | % true -> X - (1 bsl 32); 72 | % false -> X 73 | % end, 74 | <> = <>, 75 | {Y, R}. 76 | 77 | d64(<>) -> 78 | {X, R}; 79 | d64(<<0,0,0,0,0,0,240,127, R/binary>>) -> 80 | {inf, R}; 81 | d64(<<0,0,0,0,0,0,248,127, R/binary>>) -> 82 | {nan, R}; 83 | d64(<<_:64, R/binary>>) -> 84 | {inv, R}. 85 | 86 | string(B) -> 87 | {Count, B2} = u30(B), 88 | <> = B2, 89 | {X, R}. 90 | 91 | %% 92 | %% structs 93 | %% 94 | 95 | namespace(<>, CP) -> 96 | KindName = case Kind of 97 | 16#08 -> namespace; 98 | 16#16 -> package_namespace; 99 | 16#17 -> package_internal_ns; 100 | 16#18 -> protected_namespace; 101 | 16#19 -> explicit_namespace; 102 | 16#1A -> static_protected_ns; 103 | 16#05 -> private_ns; 104 | X -> {unknown, X} 105 | end, 106 | {Index, R} = u30(B), 107 | 108 | {{KindName, cpstring(Index, CP)}, R}. 109 | 110 | ns_set(B, CP) -> 111 | array(fun(Bx) -> 112 | {Index, R} = u30(Bx), 113 | {cpnamespace(Index, CP), R} 114 | end, B). 115 | 116 | multiname(<>, CP) -> 117 | case Type of 118 | 16#07 -> 119 | {NsIndex, R1} = u30(B), 120 | {NameIndex, R2} = u30(R1), 121 | {{'QName', cpnamespace(NsIndex, CP), cpstring(NameIndex, CP)}, R2}; 122 | 16#0D -> 123 | {NsIndex, R1} = u30(B), 124 | {NameIndex, R2} = u30(R1), 125 | {{'QNameA', cpnamespace(NsIndex, CP), cpstring(NameIndex, CP)}, R2}; 126 | 16#0F -> 127 | {NameIndex, R1} = u30(B), 128 | {{'RTQName', cpstring(NameIndex, CP)}, R1}; 129 | 16#10 -> 130 | {NameIndex, R1} = u30(B), 131 | {{'RTQNameA', cpstring(NameIndex, CP)}, R1}; 132 | 16#11 -> 133 | {{'RTQNameL'}, B}; 134 | 16#12 -> 135 | {{'RTQNameLA'}, B}; 136 | 16#13 -> %% not in docu 137 | {{'NameL'}, B}; 138 | 16#14 -> %% not in docu 139 | {{'NameLA'}, B}; 140 | 16#09 -> 141 | {NameIndex, R1} = u30(B), 142 | {NsSetIndex, R2} = u30(R1), 143 | {{'Multiname', cpstring(NameIndex, CP), {nsseti, NsSetIndex}}, R2}; 144 | 16#0E -> 145 | {NameIndex, R1} = u30(B), 146 | {NsSetIndex, R2} = u30(R1), 147 | {{'MultinameA', cpstring(NameIndex, CP), {nsseti, NsSetIndex}}, R2}; 148 | 16#1B -> 149 | {NsSetIndex, R1} = u30(B), 150 | {{'MultinameL', {nsseti, NsSetIndex}}, R1}; 151 | 16#1C -> 152 | {NsSetIndex, R1} = u30(B), 153 | {{'MultinameLA', {nsseti, NsSetIndex}}, R1}; 154 | 16#1D -> %% not in docu 155 | {NameIndex, R1} = u30(B), 156 | {TypeNameIndexArray, R2} = array(fun u30/1, R1), 157 | {{'TypeName', NameIndex, TypeNameIndexArray}, R2}; 158 | _ -> 159 | throw({invalid_multiname, Type}) %{{unknown, Type}, B} 160 | end. 161 | 162 | option_detail(B, CP) -> 163 | {VIndex, <>} = u30(B), 164 | Val = cpconstant(Kind, VIndex, CP), 165 | {Val, R}. 166 | 167 | 168 | % traits_info 169 | % { 170 | % u30 name 171 | % u8 kind 172 | % u8 data[] 173 | % u30 metadata_count 174 | % u30 metadata[metadata_count] 175 | % } 176 | 177 | traits_info(B, CP) -> 178 | {Name, R1} = u30(B), 179 | <> = R1, 180 | AttrArr = flags(Attr, [{16#01, final}, {16#02, override}, {16#04, metadata}]), 181 | KindName = case Kind of 182 | 0 -> slot; 183 | 6 -> const; 184 | 4 -> class; 185 | 5 -> function; 186 | 1 -> method; 187 | 2 -> getter; 188 | 3 -> setter; 189 | _ -> invalid 190 | end, 191 | % io:format("trait kind ~p~n", [Kind]), 192 | {Data, R3} = case Kind of 193 | X when X =:= 0; X =:= 6 -> %% trait slot or const 194 | {SlotId, Q1} = u30(R2), 195 | {TypeNameIndex, Q2} = u30(Q1), 196 | TypeName = cpmultiname(TypeNameIndex, CP), 197 | {Vindex, Q3} = u30(Q2), 198 | {Vkind, Q4} = case Vindex =:= 0 of 199 | true -> {none, Q3}; 200 | false -> u8(Q3) 201 | end, 202 | Val = cpconstant(Vkind, Vindex, CP), 203 | {#trait_slot{slot_id=SlotId, type_name=TypeName, value=Val}, Q4}; 204 | 4 -> %% trait class 205 | {SlotId, Q1} = u30(R2), 206 | {Classi, Q2} = u30(Q1), 207 | {#trait_class{slot_id=SlotId, classi=Classi}, Q2}; 208 | 5 -> %% trait function 209 | {SlotId, Q1} = u30(R2), 210 | {Functioni, Q2} = u30(Q1), 211 | {#trait_function{slot_id=SlotId, functioni=Functioni}, Q2}; 212 | X when X =:= 1; X =:= 2; X =:= 3 -> %% trait method, getter, setter 213 | {DispId, Q1} = u30(R2), 214 | {Methodi, Q2} = u30(Q1), 215 | {#trait_method{disp_id=DispId, methodi=Methodi}, Q2}; 216 | _ -> throw({invalid_trait_kind, Kind}) 217 | end, 218 | 219 | {Metadata, R4} = case lists:member(metadata, AttrArr) of 220 | true -> 221 | array(fun u30/1, R3); 222 | false -> 223 | {[], R3} 224 | end, 225 | 226 | {#trait{name=Name, type=KindName, data=Data, metadata=Metadata}, R4}. 227 | 228 | 229 | % exception_info 230 | % { 231 | % u30 from 232 | % u30 to 233 | % u30 target 234 | % u30 exc_type 235 | % u30 var_name 236 | % } 237 | 238 | exception_info(B, CP) -> 239 | {From, R1} = u30(B), 240 | {To, R2} = u30(R1), 241 | {Target, R3} = u30(R2), 242 | {ExcTypeI, R4} = u30(R3), 243 | ExcType = cpstring(ExcTypeI, CP), 244 | {VarNameI, R5} = u30(R4), 245 | VarName = cpstring(VarNameI, CP), 246 | {#exception{from=From, to=To, target=Target, exc_type=ExcType, var_name=VarName}, R5}. 247 | 248 | 249 | %% parse instructions 250 | code(B, CP) -> 251 | code(B, CP, [], 0). 252 | code(<<>>, _CP, Acc, _Addr) -> 253 | lists:reverse(Acc); 254 | code(<>, CP, Acc, Addr) -> 255 | Fun_Multiname_Arg = fun(IName) -> 256 | {Idx, R1} = u30(B), 257 | {ArgCount, R} = u30(R1), 258 | {IName, [cpmultiname(Idx, CP), ArgCount], R} 259 | end, 260 | Fun_Arg = fun(IName) -> 261 | {ArgCount, R} = u30(B), 262 | {IName, [ArgCount], R} 263 | end, 264 | Fun_Multiname = fun(IName) -> 265 | {Idx, R} = u30(B), 266 | {IName, [cpmultiname(Idx, CP)], R} 267 | end, 268 | Fun_Idx = fun(IName) -> 269 | {Idx, R} = u30(B), 270 | {IName, [Idx], R} 271 | end, 272 | Fun_Offset = fun(IName) -> 273 | {Offset, R} = s24(B), 274 | {IName, [{offset, Offset}], R} 275 | end, 276 | 277 | Op = case OpCode of 278 | % 16#00 -> unknown_0x00; %% not is spec 279 | 16#01 -> bkpt; %% not in docu 280 | 16#02 -> nop; 281 | 16#03 -> throw; 282 | 16#04 -> Fun_Multiname(getsuper); 283 | 16#05 -> Fun_Multiname(setsuper); 284 | 16#06 -> 285 | {Idx, R} = u30(B), 286 | {dxns, [cpstring(Idx, CP)], R}; 287 | 16#07 -> dxnslate; 288 | 16#08 -> Fun_Idx(kill); 289 | 16#09 -> label; 290 | % 16#0a ? 291 | % 16#0b ? 292 | 16#0c -> Fun_Offset(ifnlt); 293 | 16#0d -> Fun_Offset(ifnle); 294 | 16#0e -> Fun_Offset(ifngt); 295 | 16#0f -> Fun_Offset(ifnge); 296 | 16#10 -> Fun_Offset(jump); 297 | 16#11 -> Fun_Offset(iftrue); 298 | 16#12 -> Fun_Offset(iffalse); 299 | 16#13 -> Fun_Offset(ifeq); 300 | 16#14 -> Fun_Offset(ifne); 301 | 16#15 -> Fun_Offset(iflt); 302 | 16#16 -> Fun_Offset(ifle); 303 | 16#17 -> Fun_Offset(ifgt); 304 | 16#18 -> Fun_Offset(ifeq); 305 | 16#19 -> Fun_Offset(ifstricteq); 306 | 16#1a -> Fun_Offset(ifstrictne); 307 | 16#1b -> 308 | {DefaultOffset, R1} = s24(B), 309 | {Count, R2} = u30(R1), 310 | {CaseOffsets, R3} = array(fun(Bx) -> s24(Bx) end, R2, [], Count+1), 311 | {lookupswitch, [DefaultOffset, CaseOffsets], R3}; 312 | 16#1c -> pushwith; 313 | 16#1d -> popscope; 314 | 16#1e -> nextname; 315 | 16#1f -> hasnext; 316 | 16#20 -> pushnull; 317 | 16#21 -> pushundefined; 318 | % 16#22 ? 319 | 16#23 -> nextvalue; 320 | 16#24 -> 321 | {Byte, R} = u8(B), 322 | {pushbyte, [Byte], R}; 323 | 16#25 -> Fun_Idx(pushshort); 324 | 16#26 -> pushtrue; 325 | 16#27 -> pushfalse; 326 | 16#28 -> pushnan; 327 | 16#29 -> pop; 328 | 16#2a -> dup; 329 | 16#2b -> swap; 330 | 16#2c -> 331 | {Idx, R} = u30(B), 332 | {pushstring, [cpstring(Idx, CP)], R}; 333 | 16#2d -> 334 | {Idx, R} = u30(B), 335 | {pushint, [cpinteger(Idx, CP)], R}; 336 | 16#2e -> Fun_Idx(pushuint); 337 | 16#2f -> 338 | {Idx, R} = u30(B), 339 | {pushdouble, [cpdouble(Idx, CP)], R}; 340 | 16#30 -> pushscope; 341 | 16#31 -> 342 | {Idx, R} = u30(B), 343 | {pushnamespace, [cpnamespace(Idx, CP)], R}; 344 | 16#32 -> 345 | {I1, R1} = u30(B), 346 | {I2, R2} = u30(R1), 347 | {hasnext2, [I1, I2], R2}; 348 | % 16#33 ? 349 | % 16#34 ? 350 | 16#35 -> fmo_si8; %% fast memory op 351 | 16#36 -> fmo_si16; %% fast memory op 352 | 16#37 -> fmo_si32; %% fast memory op 353 | 16#38 -> fmo_sf32; %% fast memory op 354 | 16#39 -> fmo_sf64; %% fast memory op 355 | 16#3a -> fmo_li8; %% fast memory op 356 | 16#3b -> fmo_li16; %% fast memory op 357 | 16#3c -> fmo_li32; %% fast memory op 358 | 16#3d -> fmo_lf32; %% fast memory op 359 | 16#3e -> fmo_lf64; %% fast memory op 360 | % 16#3f ? 361 | 16#40 -> 362 | {Idx, R} = u30(B), 363 | {newfunction, [{method, Idx}], R}; 364 | 16#41 -> Fun_Arg(call); 365 | 16#42 -> Fun_Arg(construct); 366 | 16#43 -> 367 | {Idx, R1} = u30(B), 368 | {ArgCount, R} = u30(R1), 369 | {callmethod, [{method, Idx}, ArgCount], R}; 370 | 16#44 -> 371 | {Idx, R1} = u30(B), 372 | {ArgCount, R} = u30(R1), 373 | {callstatic, [{method, Idx}, ArgCount], R}; 374 | 16#45 -> Fun_Multiname_Arg(callsuper); 375 | 16#46 -> Fun_Multiname_Arg(callproperty); 376 | 16#47 -> returnvoid; 377 | 16#48 -> returnvalue; 378 | 16#49 -> Fun_Arg(constructsuper); 379 | 16#4a -> Fun_Multiname_Arg(constructprop); 380 | % 16#4b ? 381 | 16#4c -> Fun_Multiname_Arg(callproplex); 382 | % 16#4d ? 383 | 16#4e -> Fun_Multiname_Arg(callsupervoid); 384 | 16#4f -> Fun_Multiname_Arg(callpropvoid); 385 | 16#50 -> fmo_sxi1; %% fast memory op 386 | 16#51 -> fmo_sxi8; %% fast memory op 387 | 16#52 -> fmo_sxi16; %% fast memory op 388 | 16#53 -> applytype; %% not in docu 389 | % 16#54 ? 390 | 16#55 -> Fun_Arg(newobject); 391 | 16#56 -> Fun_Arg(newarray); 392 | 16#57 -> newactivation; 393 | 16#58 -> Fun_Idx(newclass); 394 | 16#59 -> Fun_Multiname(getdescendants); 395 | 16#5a -> Fun_Idx(newcatch); 396 | % 16#5b ? 397 | % 16#5c ? 398 | 16#5d -> Fun_Multiname(findpropstrict); 399 | 16#5e -> Fun_Multiname(findproperty); 400 | % 16#5f ? 401 | 16#60 -> Fun_Multiname(getlex); 402 | 16#61 -> Fun_Multiname(setproperty); 403 | 16#62 -> Fun_Idx(getlocal); 404 | 16#63 -> Fun_Idx(setlocal); 405 | 16#64 -> getglobalscope; 406 | 16#65 -> 407 | {Idx, R} = u8(B), 408 | {getscopeobject, [Idx], R}; 409 | 16#66 -> Fun_Multiname(getproperty); 410 | % 16#67 ? 411 | 16#68 -> Fun_Multiname(initproperty); 412 | % 16#69 ? 413 | 16#6a -> Fun_Multiname(deleteproperty); 414 | % 16#6b ? 415 | 16#6c -> Fun_Idx(getslot); 416 | 16#6d -> Fun_Idx(setslot); 417 | 16#6e -> Fun_Idx(getglobalslot); 418 | 16#6f -> Fun_Idx(setglobalslot); 419 | 16#70 -> convert_s; 420 | 16#71 -> esc_xelem; 421 | 16#72 -> esc_xattr; 422 | 16#73 -> convert_i; 423 | 16#74 -> convert_u; 424 | 16#75 -> convert_d; 425 | 16#76 -> convert_b; 426 | 16#77 -> convert_o; 427 | 16#78 -> checkfilter; 428 | % 16#79 ? 429 | % 16#7a ? 430 | % 16#7b ? 431 | % 16#7c ? 432 | % 16#7d ? 433 | % 16#7e ? 434 | % 16#7f ? 435 | 16#80 -> Fun_Multiname(coerce); 436 | % 16#81 ? 437 | 16#82 -> coerce_a; 438 | % 16#83 ? 439 | % 16#84 ? 440 | 16#85 -> coerce_s; 441 | 16#86 -> Fun_Multiname(astype); 442 | 16#87 -> astypelate; 443 | % 16#88 ? 444 | % 16#89 ? 445 | % 16#8a ? 446 | % 16#8b ? 447 | % 16#8c ? 448 | % 16#8d ? 449 | % 16#8e ? 450 | % 16#8f ? 451 | 16#90 -> negate; 452 | 16#91 -> increment; 453 | 16#92 -> Fun_Idx(inclocal); 454 | 16#93 -> decrement; 455 | 16#94 -> Fun_Idx(declocal); 456 | 16#95 -> typeof; 457 | 16#96 -> 'not'; 458 | 16#97 -> bitnot; 459 | % 16#98 ? 460 | % 16#99 ? 461 | % 16#9a ? 462 | % 16#9b ? 463 | % 16#9c ? 464 | % 16#9d ? 465 | % 16#9e ? 466 | % 16#9f ? 467 | 16#a0 -> add; 468 | 16#a1 -> subtract; 469 | 16#a2 -> multiply; 470 | 16#a3 -> divide; 471 | 16#a4 -> modulo; 472 | 16#a5 -> lshift; 473 | 16#a6 -> rshift; 474 | 16#a7 -> urshift; 475 | 16#a8 -> bitand; 476 | 16#a9 -> bitor; 477 | 16#aa -> bitxor; 478 | 16#ab -> equals; 479 | 16#ac -> strictequals; 480 | 16#ad -> lessthan; 481 | 16#ae -> lessequals; 482 | 16#af -> greaterthan; 483 | 16#b0 -> greaterequals; 484 | 16#b1 -> instanceof; 485 | 16#b2 -> Fun_Multiname(istype); 486 | 16#b3 -> istypelate; 487 | 16#b4 -> in; 488 | % 16#b5 ? 489 | % 16#b6 ? 490 | % 16#b7 ? 491 | % 16#b8 ? 492 | % 16#b9 ? 493 | % 16#ba ? 494 | % 16#bb ? 495 | % 16#bc ? 496 | % 16#bd ? 497 | % 16#be ? 498 | % 16#bf ? 499 | 16#c0 -> increment_i; 500 | 16#c1 -> decrement_i; 501 | 16#c2 -> Fun_Idx(inclocal_i); 502 | 16#c3 -> Fun_Idx(declocal_i); 503 | 16#c4 -> negate_i; 504 | 16#c5 -> add_i; 505 | 16#c6 -> subtract_i; 506 | 16#c7 -> multiply_i; 507 | % 16#c8 ? 508 | % 16#c9 ? 509 | % 16#ca ? 510 | % 16#cb ? 511 | % 16#cc ? 512 | % 16#cd ? 513 | % 16#ce ? 514 | % 16#cf ? 515 | 16#d0 -> getlocal_0; 516 | 16#d1 -> getlocal_1; 517 | 16#d2 -> getlocal_2; 518 | 16#d3 -> getlocal_3; 519 | 16#d4 -> setlocal_0; 520 | 16#d5 -> setlocal_1; 521 | 16#d6 -> setlocal_2; 522 | 16#d7 -> setlocal_3; 523 | % 16#d8 ? 524 | % 16#d9 ? 525 | % 16#da ? 526 | % 16#db ? 527 | % 16#dc ? 528 | % 16#dd ? 529 | % 16#de ? 530 | % 16#df ? 531 | % 16#e0 ? 532 | % 16#e1 ? 533 | % 16#e2 ? 534 | % 16#e3 ? 535 | % 16#e4 ? 536 | % 16#e5 ? 537 | % 16#e6 ? 538 | % 16#e7 ? 539 | % 16#e8 ? 540 | % 16#e9 ? 541 | % 16#ea ? 542 | % 16#eb ? 543 | % 16#ec ? 544 | % 16#ed ? 545 | % 16#ee ? 546 | 16#ef -> 547 | {DebugType, R1} = u8(B), 548 | {RegNameIdx, R2} = u30(R1), 549 | RegName = cpstring(RegNameIdx, CP), 550 | {Reg, R3} = u8(R2), 551 | {_Extra, R} = u30(R3), 552 | {debug, [DebugType, RegName, Reg], R}; 553 | 16#f0 -> Fun_Idx(debugline); 554 | 16#f1 -> 555 | {Idx, R} = u30(B), 556 | {debugfile, [cpstring(Idx, CP)], R}; 557 | % 16#f2 ? 558 | % 16#f3 ? 559 | % 16#f4 ? 560 | % 16#f5 ? 561 | % 16#f6 ? 562 | % 16#f7 ? 563 | % 16#f8 ? 564 | % 16#f9 ? 565 | % 16#fa ? 566 | % 16#fb ? 567 | % 16#fc ? 568 | % 16#fd ? 569 | % 16#fe ? 570 | % 16#ff ? 571 | X -> throw({invalid_opcode, X}) %{{invalid, X}, B} 572 | end, 573 | 574 | {Name, Args, Rest} = case Op of 575 | {_Name, _Args, _Rest} -> Op; 576 | {Nm, Rx} -> {Nm, [], Rx}; 577 | Nm when is_atom(Nm) -> {Nm, [], B} 578 | end, 579 | % io:format("~p~n", [{Name, Args}]), 580 | NewAddr = Addr + (1 + byte_size(B) - byte_size(Rest)), 581 | code(Rest, CP, [#instr{addr=Addr, name=Name, args=Args}|Acc], NewAddr). 582 | 583 | 584 | %% 585 | %% toplevel structs 586 | %% 587 | 588 | % abcFile 589 | % { 590 | % u16 minor_version 591 | % u16 major_version 592 | % cpool_info constant_pool 593 | % u30 method_count 594 | % method_info method[method_count] 595 | % u30 metadata_count 596 | % metadata_info metadata[metadata_count] 597 | % u30 class_count 598 | % instance_info instance[class_count] 599 | % class_info class[class_count] 600 | % u30 script_count 601 | % script_info script[script_count] 602 | % u30 method_body_count 603 | % method_body_info method_body[method_body_count] 604 | % } 605 | 606 | %% @doc parse binary abc bytecode 607 | %% @spec abc(binary()) -> abcfile() 608 | abc(B) when is_binary(B) -> 609 | {MinorVersion, R1} = u16(B), 610 | {MajorVersion, R2} = u16(R1), 611 | {ConstantPool, R3} = cpool_info(R2), 612 | {Method, R4} = array(fun method_info/2, R3, [ConstantPool]), 613 | {Metadata, R5} = array(fun metadata_info/2, R4, [ConstantPool]), 614 | {ClassCount, R6} = u30(R5), 615 | {Instance, R7} = array(fun instance_info/2, R6, [ConstantPool], ClassCount), 616 | {Class, R8} = array(fun class_info/2, R7, [ConstantPool], ClassCount), 617 | {Script, R9} = array(fun script_info/2, R8, [ConstantPool]), 618 | {MethodBody, R10} = array(fun method_body_info/2, R9, [ConstantPool]), 619 | 620 | {#abcfile{ 621 | version={MinorVersion, MajorVersion}, 622 | cpool=ConstantPool, 623 | method=Method, 624 | metadata=Metadata, 625 | instance=Instance, 626 | class=Class, 627 | script=Script, 628 | method_body=MethodBody 629 | }, R10}. 630 | 631 | 632 | 633 | % cpool_info 634 | % { 635 | % u30 int_count 636 | % s32 integer[int_count] 637 | % u30 uint_count 638 | % u32 uinteger[uint_count] 639 | % u30 double_count 640 | % d64 double[double_count] 641 | % u30 string_count 642 | % string_info string[string_count] 643 | % u30 namespace_count 644 | % namespace_info namespace[namespace_count] 645 | % u30 ns_set_count 646 | % ns_set_info ns_set[ns_set_count] 647 | % u30 multiname_count 648 | % multiname_info multiname[multiname_count] 649 | % } 650 | 651 | cpool_info(B) -> 652 | {Integer, R1} = array0(fun s32/1, B), 653 | {Uinteger, R2} = array0(fun u32/1, R1), 654 | {Double, R3} = array0(fun d64/1, R2), 655 | {String, R4} = array0(fun string/1, R3), 656 | {Namespace, R5} = array0(fun namespace/2, R4, [#cpool{string=String}]), 657 | {NsSet, R6} = array0(fun ns_set/2, R5, [#cpool{namespace=Namespace}]), 658 | {Multiname, R7} = array0(fun multiname/2, R6, [#cpool{namespace=Namespace, string=String, ns_set=NsSet}]), 659 | 660 | {#cpool{ 661 | integer=Integer, 662 | uinteger=Uinteger, 663 | double=Double, 664 | string=String, 665 | namespace=Namespace, 666 | ns_set=NsSet, 667 | multiname=Multiname 668 | },R7}. 669 | 670 | 671 | 672 | % method_info 673 | % { 674 | % u30 param_count 675 | % u30 return_type 676 | % u30 param_type[param_count] 677 | % u30 name 678 | % u8 flags 679 | % option_info options 680 | % param_info param_names 681 | % } 682 | 683 | method_info(B, CP) -> 684 | {ParamCount, R1} = u30(B), 685 | 686 | {ReturnTypeIndex, R2} = u30(R1), 687 | ReturnType = cpmultiname(ReturnTypeIndex, CP), 688 | 689 | {ParamType, R3} = array(fun(Bx, C) -> 690 | {I, Q} = u30(Bx), 691 | {cpmultiname(I, C), Q} 692 | end, R2, [CP], ParamCount), 693 | 694 | {NameIndex, R4} = u30(R3), 695 | Name = cpstring(NameIndex, CP), 696 | 697 | <> = R4, 698 | FlagsArr = flags(Flags, [ 699 | {16#01, need_arguments}, 700 | {16#02, need_activation}, 701 | {16#04, need_rest}, 702 | {16#08, has_optional}, 703 | {16#40, set_dxns}, 704 | {16#80, has_param_names} 705 | ]), 706 | 707 | {Options, R6} = case lists:member(has_optional, FlagsArr) of 708 | true -> 709 | {ODetail, Q2} = array(fun option_detail/2, R5, [CP]), 710 | {ODetail, Q2}; 711 | false -> 712 | {[], R5} 713 | end, 714 | 715 | {ParamNames, R7} = case lists:member(has_param_names, FlagsArr) of 716 | true -> 717 | array(fun(Bx, C) -> 718 | {I, R} = u30(Bx), 719 | {cpstring(I, C), R} 720 | end, R6, [CP], ParamCount); 721 | false -> 722 | {[], R6} 723 | end, 724 | 725 | {#method{ 726 | param_count=ParamCount, 727 | return_type=ReturnType, 728 | param_type=ParamType, 729 | name=Name, 730 | flags=Flags, 731 | flags_arr=FlagsArr, 732 | options=Options, 733 | param_names=ParamNames}, R7}. 734 | 735 | 736 | 737 | % metadata_info 738 | % { 739 | % u30 name 740 | % u30 item_count 741 | % item_info items[item_count] 742 | % } 743 | 744 | metadata_info(B, CP) -> 745 | {NameIndex, R1} = u30(B), 746 | Name = cpstring(NameIndex, CP), 747 | {Items, R2} = array(fun(Bx, C) -> 748 | {KeyIndex, Q1} = u30(Bx), 749 | {ValueIndex, Q2} = u30(Q1), 750 | Value = cpstring(ValueIndex, C), 751 | case KeyIndex of 752 | 0 -> {Value, Q2}; 753 | _ -> 754 | Key = cpstring(KeyIndex, C), 755 | {{Key, Value}, Q2} 756 | end 757 | end, R1, [CP]), 758 | {{Name, Items}, R2}. 759 | 760 | 761 | 762 | % instance_info 763 | % { 764 | % u30 name 765 | % u30 super_name 766 | % u8 flags 767 | % u30 protectedNs 768 | % u30 intrf_count 769 | % u30 interface[intrf_count] 770 | % u30 iinit 771 | % u30 trait_count 772 | % traits_info trait[trait_count] 773 | % } 774 | 775 | instance_info(B, CP) -> 776 | {NameIndex, R1} = u30(B), 777 | Name = cpmultiname(NameIndex, CP), 778 | 779 | {SuperNameIndex, R2} = u30(R1), 780 | SuperName = cpmultiname(SuperNameIndex, CP), 781 | 782 | {Flags, R3} = u8(R2), 783 | FlagsArr = flags(Flags, [ 784 | {16#01, class_sealed}, 785 | {16#02, class_final}, 786 | {16#04, class_interface}, 787 | {16#08, class_protected_ns} 788 | ]), 789 | 790 | {ProtectedNs, R4} = case lists:member(class_protected_ns, FlagsArr) of 791 | true -> 792 | {PNsIndex, Q1} = u30(R3), 793 | {cpnamespace(PNsIndex, CP), Q1}; 794 | false -> {undefined, R3} 795 | end, 796 | 797 | {Interface, R5} = array(fun(Bx, C) -> 798 | {I, Q} = u30(Bx), 799 | {cpmultiname(I, C), Q} 800 | end, R4, [CP]), 801 | 802 | {Iinit, R6} = u30(R5), 803 | 804 | {Trait, R7} = array(fun traits_info/2, R6, [CP]), 805 | 806 | {#instance{ 807 | name=Name, 808 | super_name=SuperName, 809 | flags=Flags, 810 | flags_arr=FlagsArr, 811 | protected_ns=ProtectedNs, 812 | interface=Interface, 813 | iinit=Iinit, 814 | trait=Trait}, R7}. 815 | 816 | 817 | % class_info 818 | % { 819 | % u30 cinit 820 | % u30 trait_count 821 | % traits_info traits[trait_count] 822 | % } 823 | 824 | class_info(B, CP) -> 825 | {Cinit, R1} = u30(B), 826 | {Trait, R2} = array(fun traits_info/2, R1, [CP]), 827 | {#class{cinit=Cinit, trait=Trait}, R2}. 828 | 829 | 830 | % script_info 831 | % { 832 | % u30 init 833 | % u30 trait_count 834 | % traits_info trait[trait_count] 835 | % } 836 | 837 | script_info(B, CP) -> 838 | {Init, R1} = u30(B), 839 | {Trait, R2} = array(fun traits_info/2, R1, [CP]), 840 | {#script{init=Init, trait=Trait}, R2}. 841 | 842 | 843 | % method_body_info 844 | % { 845 | % u30 method 846 | % u30 max_stack 847 | % u30 local_count 848 | % u30 init_scope_depth 849 | % u30 max_scope_depth 850 | % u30 code_length 851 | % u8 code[code_length] 852 | % u30 exception_count 853 | % exception_info exception[exception_count] 854 | % u30 trait_count 855 | % traits_info trait[trait_count] 856 | % } 857 | 858 | method_body_info(B, CP) -> 859 | {Method, R1} = u30(B), 860 | % io:format("method: ~p~n", [Method]), 861 | {MaxStack, R2} = u30(R1), 862 | {LocalCount, R3} = u30(R2), 863 | {InitScopeDepth, R4} = u30(R3), 864 | {MaxScopeDepth, R5} = u30(R4), 865 | {CodeLength, R6} = u30(R5), 866 | <> = R6, 867 | Code = code(CodeB, CP), 868 | {Exception, R8} = array(fun exception_info/2, R7, [CP]), 869 | {Trait, R9} = array(fun traits_info/2, R8, [CP]), 870 | {#method_body{ 871 | methodi=Method, 872 | max_stack=MaxStack, 873 | local_count=LocalCount, 874 | init_scope_depth=InitScopeDepth, 875 | max_scope_depth=MaxScopeDepth, 876 | code=Code, 877 | exception=Exception, 878 | trait=Trait 879 | }, R9}. 880 | 881 | 882 | %% 883 | %% helper functions 884 | %% 885 | 886 | 887 | %% parse flags 888 | flags(U8, FlagList) -> 889 | lists:foldl(fun({Code, FlagName}, Acc) -> 890 | case (U8 band Code) =/= 0 of 891 | true -> [FlagName|Acc]; 892 | _ -> Acc 893 | end 894 | end, [], FlagList). 895 | 896 | %% constant pool access helpers 897 | cpinteger(Index, #cpool{integer=X}) -> {integer, cpget(Index, X)}. 898 | cpuinteger(Index, #cpool{uinteger=X}) -> {uinteger, cpget(Index, X)}. 899 | cpdouble(Index, #cpool{double=X}) -> {double, cpget(Index, X)}. 900 | cpstring(Index, #cpool{string=X}) -> {string, cpget(Index, X)}. 901 | cpnamespace(Index, #cpool{namespace=X}) -> {namespace, cpget(Index, X)}. 902 | cpnsset(Index, #cpool{ns_set=X}) -> {ns_set, cpget(Index, X)}. 903 | cpmultiname(Index, #cpool{multiname=X}) -> {multiname, cpget(Index, X)}. 904 | 905 | cpget(_, Arr) when Arr =:= undefined -> undefined; 906 | cpget(0, _) -> '*'; 907 | cpget(Index, Arr) -> 908 | case Index =< length(Arr) of 909 | true -> lists:nth(Index, Arr); 910 | false -> undefined 911 | end. 912 | 913 | cpconstant(Kind, VIndex, CP) -> 914 | case Kind of 915 | 16#03 -> {integer, cpinteger(VIndex, CP)}; 916 | 16#04 -> {uinteger, cpuinteger(VIndex, CP)}; 917 | 16#06 -> {double, cpdouble(VIndex, CP)}; 918 | 16#01 -> {utf8, cpstring(VIndex, CP)}; 919 | 16#0B -> true; 920 | 16#0A -> false; 921 | 16#0C -> null; 922 | 16#00 -> undefined; 923 | none -> none; 924 | X when X =:= 16#08; X >= 16#16, X =< 16#1A; X =:= 16#05 -> 925 | {namespace, cpnamespace(VIndex, CP)}; 926 | X -> {unknown, X} 927 | end. 928 | 929 | %% method access helper 930 | % method_and_body(MethodIndex, #abcfile{method=Methods, method_body=MB}) -> 931 | % Method = lists:nth(MethodIndex+1, Methods), 932 | % {value, Methodbody} = lists:keysearch(MethodIndex, 2, MB), 933 | % {Method, Methodbody}. 934 | 935 | method(Index, Methods) -> 936 | lists:nth(Index+1, Methods). 937 | 938 | method_body(MethodIndex, #abcfile{method_body=MB}) -> 939 | case lists:keysearch(MethodIndex, 2, MB) of 940 | {value, Methodbody} -> Methodbody; 941 | false -> {unknown_method_index, MethodIndex} 942 | end. 943 | -------------------------------------------------------------------------------- /src/swfabcformat.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc format abc dumps in a logically structured and human readable manner 3 | %% 4 | 5 | -module(swfabcformat). 6 | -export([abc/1, abc/3, dtfmt/1]). 7 | -include("swfabc.hrl"). 8 | 9 | 10 | %% @doc same as abc(standard_io, Abc, all) 11 | %% @spec abc(abc()) -> ok 12 | abc(Abc) -> 13 | abc(standard_io, Abc, all). 14 | 15 | %% @doc stream fancy abc to given io device 16 | %% @spec abc(io_device(), abc(), [identifier()]) -> ok 17 | abc(Io, Abc, all) -> 18 | abc(Io, Abc, ["version", "cpool", "metadata", "scripts", "classes"]); 19 | abc(_, _, []) -> ok; 20 | abc(Io, Abc, [Part|Parts]) -> 21 | abcfmt(Io, Part, Abc), 22 | abc(Io, Abc, Parts). 23 | 24 | abcfmt(Io, "version", #abcfile{version={Minor, Major}}) -> 25 | io:format(Io, "abc version: ~p.~p~n", [Major, Minor]); 26 | abcfmt(Io, "cpool", #abcfile{cpool=Cpool}) -> 27 | cpool(Io, Cpool); 28 | abcfmt(Io, "metadata", #abcfile{metadata=Metadata}) -> 29 | metadata(Io, Metadata); 30 | abcfmt(Io, "methods", Abc) -> 31 | methods(Io, Abc); 32 | abcfmt(Io, "scripts", Abc) -> 33 | scripts(Io, Abc); 34 | abcfmt(Io, "classes", Abc) -> 35 | classes(Io, Abc); 36 | 37 | abcfmt(_Io, X, _Abc) -> 38 | throw({unknown_option, X}). 39 | 40 | 41 | cpool(Io, #cpool{integer=Integer, uinteger=UInteger, double=Double, string=String, namespace=Namespace, ns_set=NsSet, multiname=Multiname}) -> 42 | io:format(Io, "~nCONSTANT POOL~n", []), 43 | 44 | IFun = fun(X) -> lists:flatten(io_lib:format("~p", [X])) end, 45 | cpoolfmt(Io, "integer pool", IFun, Integer), 46 | cpoolfmt(Io, "unsigned integer pool", IFun, UInteger), 47 | cpoolfmt(Io, "double pool", IFun, Double), 48 | cpoolfmt(Io, "string pool", IFun, String), 49 | cpoolfmt(Io, "namespace pool", fun({Type, {string, S}}) -> lists:flatten(io_lib:format("[~p] \"~s\"", [Type, S])) end, Namespace), 50 | cpoolfmt(Io, "ns set pool", IFun, NsSet), 51 | cpoolfmt(Io, "multiname pool", fun cpmn/1, Multiname), 52 | 53 | ok. 54 | 55 | cpoolfmt(Io, Desc, Fun, L) -> 56 | io:format(Io, "~s:~n", [Desc]), 57 | case L of 58 | [] -> io:format(Io, " empty~n", []); 59 | X when is_list(X) -> 60 | lists:foldl(fun(El, Idx) -> 61 | io:format(Io, " ~p ~s~n", [Idx, Fun(El)]), 62 | Idx+1 63 | end, 1, X); 64 | _ -> io:format(Io, " ~p~n", [L]) 65 | end, 66 | io:nl(Io). 67 | 68 | %% constant pool multiname 69 | cpmn({Type}) -> 70 | lists:flatten(io_lib:format("[~p]", [Type])); 71 | cpmn({Type, P1}) -> 72 | lists:flatten(io_lib:format("[~p] ~s", [Type, dtfmt(P1)])); 73 | cpmn({Type, P1, P2}) -> 74 | lists:flatten(io_lib:format("[~p] ~s::~s", [Type, dtfmt(P1), dtfmt(P2)])). 75 | 76 | %% metadata 77 | metadata(Io, Metadata) -> 78 | io:format(Io, "~nMETADATA~n", []), 79 | case Metadata of 80 | [] -> io:format(Io, " empty~n", []); 81 | _ -> metadata(Io, Metadata, 1) 82 | end. 83 | metadata(_, [], _) -> ok; 84 | metadata(Io, [M|R], N) -> 85 | io:format(Io, " ~p ~p~n", [N, M]), 86 | metadata(Io, R, N+1). 87 | 88 | methods(Io, #abcfile{method_body=MBs}=Abc) -> 89 | methods(Io, MBs, Abc, 0). 90 | methods(_, [], _, _) -> ok; 91 | methods(Io, [MB|MBs], Abc, N) -> 92 | io:format(Io, "/* method no. ~p */~n", [N]), 93 | method(Io, MB, Abc), 94 | methods(Io, MBs, Abc, N+1). 95 | 96 | method(Io, #method_body{methodi=Methodi, max_stack=MaxStack, local_count=LocalCount, init_scope_depth=InitScopeDepth, max_scope_depth=MaxScopeDepth, code=Code, exception=Exceptions, trait=Traits}, #abcfile{method=Methods}) -> 97 | #method{param_count=ParamCount, return_type=ReturnType, param_type=ParamType, name=Name, flags=Flags, flags_arr=FlagsArr, options=Options, param_names=ParamNames} = swfabc:method(Methodi, Methods), 98 | ParamTypes = [dtfmt(PT) || PT <- ParamType], 99 | io:format(Io, " method ~s/~p(~s):~p returns ~s {~n", [dtfmt(Name), ParamCount, string:join(ParamTypes, ", "), ParamNames, dtfmt(ReturnType)]), 100 | io:format(Io, " // max stack: ~p~n", [MaxStack]), 101 | io:format(Io, " // local count: ~p~n", [LocalCount]), 102 | io:format(Io, " // init/max scope depth: ~p/~p~n", [InitScopeDepth, MaxScopeDepth]), 103 | io:format(Io, " // flags: ~p/~p~n", [Flags, FlagsArr]), 104 | io:format(Io, " // exceptions: ~p~n", [Exceptions]), 105 | io:format(Io, " // traits: ~p~n", [Traits]), 106 | io:format(Io, " // options: ~p~n", [Options]), 107 | code(Io, Code), 108 | io:format(Io, " }~n", []), 109 | ok; 110 | method(Io, Unknown, _Abc) -> 111 | io:format(Io, " unknown method: ~p~n", [Unknown]). 112 | 113 | code(_, []) -> ok; 114 | code(Io, [#instr{addr=Addr, name=Name, args=Args}|R]) -> 115 | io:format(Io, " /* ~5.16.0B */ ~p ~s~n", [Addr, Name, [dtfmt(A) || A <- Args]]), 116 | code(Io, R). 117 | 118 | 119 | scripts(Io, #abcfile{script=Scripts}=Abc) -> 120 | io:format(Io, "~nSCRIPTS~n", []), 121 | case Scripts of 122 | [] -> io:format(Io, " empty~n", []); 123 | _ -> scripts(Io, Scripts, Abc, 0) 124 | end. 125 | scripts(_Io, [], _Abc, _N) -> ok; 126 | scripts(Io, [#script{init=I, trait=Traits}|Scripts], Abc, N) -> 127 | io:format(Io, "/* script no. ~p */~n", [N]), 128 | io:format(Io, "~p, ~p~n", [I, Traits]), %% incomplete/todo 129 | scripts(Io, Scripts, Abc, N+1). 130 | 131 | 132 | classes(Io, #abcfile{class=Classes, instance=Instances}=Abc) -> 133 | io:format(Io, "~nCLASSES~n", []), 134 | case Classes of 135 | [] -> io:format(Io, " empty~n", []); 136 | _ -> classes(Io, Classes, Instances, Abc, 0) 137 | end. 138 | classes(_Io, [], [], _Abc, _N) -> ok; 139 | classes(Io, [#class{cinit=Cinit, trait=Ctraits}|Classes], 140 | [#instance{name=Name, super_name=SuperName, flags=Flags, flags_arr=FlagsArr, protected_ns=ProtectedNs, interface=Interface, iinit=Iinit, trait=Itraits}|Instances], 141 | Abc, N) -> 142 | io:format(Io, "/* class no. ~p */~n", [N]), 143 | io:format(Io, "class ~s extends ~s {~n", [dtfmt(Name), dtfmt(SuperName)]), 144 | io:format(Io, " // flags: ~p/~p~n", [Flags, FlagsArr]), 145 | io:format(Io, " // protected ns: ~s~n", [dtfmt(ProtectedNs)]), 146 | io:format(Io, " // interface: ~p~n", [Interface]), 147 | io:format(Io, " static class init (~p)~n", [Cinit]), 148 | Methodbody = swfabc:method_body(Cinit, Abc), 149 | method(Io, Methodbody, Abc), 150 | 151 | io:format(Io, " __new__ (~p)~n", [Iinit]), 152 | Methodbody2 = swfabc:method_body(Iinit, Abc), 153 | method(Io, Methodbody2, Abc), 154 | 155 | case Ctraits of 156 | [] -> ok; 157 | _ -> 158 | io:format(Io, " /* class traits */~n", []), 159 | traits(Io, Ctraits, Abc) 160 | end, 161 | 162 | case Itraits of 163 | [] -> ok; 164 | _ -> 165 | io:format(Io, " /* instance traits */~n", []), 166 | traits(Io, Itraits, Abc) 167 | end, 168 | 169 | io:format(Io, "}~n", []), 170 | classes(Io, Classes, Instances, Abc, N+1). 171 | 172 | 173 | 174 | dtfmt({string, X}) when is_binary(X) -> 175 | io_lib:format("~s", [X]); 176 | dtfmt({string, X}) when is_atom(X) -> 177 | io_lib:format("~s", [atom_to_list(X)]); 178 | 179 | dtfmt({namespace, {_Kind, {string, Str}}}) -> 180 | dtfmt({string, Str}); 181 | dtfmt({multiname, Type}) when is_atom(Type) -> 182 | io_lib:format("{~s}", [atom_to_list(Type)]); 183 | dtfmt({multiname, {_Type, P1}}) -> 184 | dtfmt(P1); 185 | dtfmt({multiname, {_Type, P1, P2}}) -> 186 | lists:flatten(io_lib:format("~s::~s", [dtfmt(P1), dtfmt(P2)])); 187 | 188 | dtfmt(X) -> 189 | io_lib:format("<<~p>>", [X]). 190 | 191 | 192 | traits(_, [], _) -> ok; 193 | traits(Io, [T|Traits], Abc) -> 194 | traitfmt(Io, T, Abc), 195 | traits(Io, Traits, Abc). 196 | 197 | traitfmt(Io, #trait{name=NameI, type=Type, data=Data, metadata=Metadata}, #abcfile{cpool=CP}=Abc) -> 198 | Name = swfabc:cpmultiname(NameI, CP), 199 | case Data of 200 | #trait_slot{slot_id=SlotId, type_name=TypeName, value=Value} -> 201 | io:format(Io, " ~s ~s ~s = ~s /* slot id: ~p */~n", [atom_to_list(Type), dtfmt(TypeName), dtfmt(Name), dtfmt(Value), SlotId]); 202 | #trait_method{disp_id=DispId, methodi=MethodI} -> 203 | io:format(" ~s /* disp id ~p */~n", [dtfmt(Name), DispId]), 204 | % try begin 205 | Methodbody = swfabc:method_body(MethodI, Abc), 206 | method(Io, Methodbody, Abc), 207 | % end catch error:X -> throw({traitexc, MethodI, X}) end, 208 | ok; 209 | _ -> %% unknown/unimplemented trait type 210 | io:format(Io, " trait ~s ~s~n", [atom_to_list(Type), dtfmt(Name)]), 211 | io:format(Io, " data: ~p~n", [Data]), 212 | io:format(Io, " metadata: ~p~n", [Metadata]) 213 | end. 214 | -------------------------------------------------------------------------------- /src/swfaction.erl: -------------------------------------------------------------------------------- 1 | -module(swfaction). 2 | -export([ 3 | actionrecords/1, 4 | action/1, 5 | action/2 6 | ]). 7 | 8 | 9 | %% 10 | %% actions 11 | %% 12 | 13 | %% @doc decode null-terminated list of ACTIONRECORD[] 14 | %% @spec actionrecords(binary()) -> {actions, paramlist(), rest()} 15 | %% where rest() = binary() 16 | actionrecords(B) -> 17 | actionrecords(B, 0, []). 18 | actionrecords(<<>> = B, Pos, Acc) -> 19 | {actions, lists:reverse([{invalid_end_of_actionrecord, [{code, -1},{pos, Pos}]}|Acc]), B}; 20 | actionrecords(<<0, B/binary>>, _Pos, Acc) -> 21 | {actions, lists:reverse(Acc), B}; 22 | actionrecords(<<0:1, ActionCode:7, B/binary>>, Pos, Acc) -> %% record w/o data 23 | swf:debug("action [~2.16.0B]~n", [ActionCode]), 24 | actionrecords(B, Pos + 1, [{action(ActionCode), [{code, ActionCode}, {pos, Pos}]}|Acc]); 25 | actionrecords(<>, Pos, Acc) -> %% record w/ data 26 | swf:debug("action [~2.16.0B]: ~p~n", [ActionCode, Data]), 27 | {Name, Content} = 28 | try action(ActionCode, Data) of 29 | Val -> Val 30 | catch 31 | error:X -> 32 | error_logger:warning_msg("<~8.10.0B> [~2.16.0B] action : ~p~n ~p~n", [Pos, ActionCode, X, erlang:get_stacktrace()]), 33 | {parseerror, X} 34 | end, 35 | actionrecords(B, Pos + 3 + Length, [{Name, [{code, ActionCode}, {pos, Pos}|Content]}|Acc]). 36 | 37 | %% @doc actions from 0x01 to 0x9f (actions w/o data) 38 | %% @spec action(integer()) -> atom() 39 | action(16#04) -> nextFrame; 40 | action(16#05) -> previousFrame; 41 | action(16#06) -> play; 42 | action(16#07) -> stop; 43 | action(16#08) -> toggleQuality; 44 | action(16#09) -> stopSounds; 45 | action(16#0A) -> add; 46 | action(16#0B) -> subtract; 47 | action(16#0C) -> multiply; 48 | action(16#0D) -> divide; 49 | action(16#0E) -> equals; 50 | action(16#0F) -> less; 51 | action(16#10) -> 'and'; 52 | action(16#11) -> 'or'; 53 | action(16#12) -> 'not'; 54 | action(16#13) -> stringEquals; 55 | action(16#14) -> stringLength; 56 | action(16#15) -> stringExtract; 57 | 58 | action(16#17) -> pop; 59 | action(16#18) -> toInteger; 60 | 61 | action(16#1C) -> getVariable; 62 | action(16#1D) -> setVariable; 63 | 64 | action(16#20) -> setTarget2; 65 | action(16#21) -> stringAdd; 66 | action(16#22) -> getProperty; 67 | action(16#23) -> setProperty; 68 | action(16#24) -> cloneSprite; 69 | action(16#25) -> removeSprite; 70 | action(16#26) -> trace; 71 | action(16#27) -> startDrag; 72 | action(16#28) -> endDrag; 73 | action(16#29) -> stringLess; 74 | action(16#2A) -> 'throw'; 75 | action(16#2B) -> castOp; 76 | action(16#2C) -> implementsOp; 77 | 78 | action(16#30) -> randomNumber; 79 | action(16#31) -> mbStringLength; 80 | action(16#32) -> charToAscii; 81 | action(16#33) -> asciiToChar; 82 | action(16#34) -> getTime; 83 | action(16#35) -> mbStringExtract; 84 | action(16#36) -> mbCharToAscii; 85 | action(16#37) -> mbAsciiToChar; 86 | 87 | action(16#3A) -> delete; 88 | action(16#3B) -> delete2; 89 | action(16#3C) -> defineLocal; 90 | action(16#3D) -> callFunction; 91 | action(16#3E) -> return; 92 | action(16#3F) -> modulo; 93 | action(16#40) -> newObject; 94 | action(16#41) -> defineLocal2; 95 | action(16#42) -> initArray; 96 | action(16#43) -> initObject; 97 | action(16#44) -> typeOf; 98 | action(16#45) -> targetPath; 99 | action(16#46) -> enumerate; 100 | action(16#47) -> add2; 101 | action(16#48) -> less2; 102 | action(16#49) -> equals2; 103 | action(16#4A) -> toNumber; 104 | action(16#4B) -> toString; 105 | action(16#4C) -> pushDuplicate; 106 | action(16#4D) -> stackSwap; 107 | action(16#4E) -> getMember; 108 | action(16#4F) -> setMember; 109 | action(16#50) -> increment; 110 | action(16#51) -> decrement; 111 | action(16#52) -> callMethod; 112 | action(16#53) -> newMethod; 113 | 114 | action(16#54) -> instanceOf; 115 | action(16#55) -> enumerate2; 116 | 117 | action(16#60) -> bitAnd; 118 | action(16#61) -> bitOr; 119 | action(16#62) -> bitXor; 120 | action(16#63) -> bitLShift; 121 | action(16#64) -> bitRShift; 122 | action(16#65) -> bitURShift; 123 | action(16#66) -> strictEquals; 124 | action(16#67) -> greater; 125 | action(16#68) -> stringGreater; 126 | action(16#69) -> extends; 127 | 128 | action(_ActionCode) -> unknownAction. 129 | 130 | %% @doc actions from 0x80 to 0xff (actions w/ data) 131 | %% @spec action(integer(), binary()) -> {atom(), paramlist()} 132 | action(16#81, <>) -> 133 | {gotoFrame, [{frame, Frame}]}; 134 | 135 | action(16#83, <>) -> 136 | {URL, R} = swfdt:string(B), 137 | {Target, _} = swfdt:string(R), 138 | {getURL, [{url, URL}, {target, Target}]}; 139 | 140 | action(16#87, <>) -> 141 | {storeRegister, [{registerNumber, RegisterNumber}]}; 142 | 143 | action(16#88, <<_Count:16, B/binary>>) -> 144 | {constantPool, [{pool, swfdt:stringpool(B)}]}; 145 | 146 | action(16#8A, <>) -> 147 | {waitForFrame, [{frame, Frame}, {skipCount, SkipCount}]}; 148 | 149 | action(16#8B, <>) -> 150 | {Name, _} = swfdt:string(B), 151 | {setTarget, [{name, Name}]}; 152 | 153 | action(16#8C, <>) -> 154 | {Label, _} = swfdt:string(B), 155 | {goToLabel, [{label, Label}]}; 156 | 157 | action(16#8D, <>) -> 158 | {waitForFrame2, [{skipCount, SkipCount}]}; 159 | 160 | action(16#8E, B) -> 161 | {Name, << 162 | NumParams:16/unsigned-integer-little, 163 | RegisterCount, 164 | PreloadParentFlag:1, 165 | PreloadRootFlag:1, 166 | SuppressSuperFlag:1, 167 | PreloadSuperFlag:1, 168 | SuppressArgumentsFlag:1, 169 | PreloadArgumentsFlag:1, 170 | SuppressThisFlag:1, 171 | PreloadThisFlag:1, 172 | _Reserved:7, 173 | PreloadGlobalFlag:1, 174 | R/binary 175 | >>} = swfdt:string(B), 176 | {RegisterParam, <>} = swfdt:registerparampool(R, NumParams), 177 | 178 | {defineFunction2, [ {name, Name}, 179 | {registerCout, RegisterCount}, 180 | {preloadParentFlag, PreloadParentFlag}, 181 | {preloadRootFlag, PreloadRootFlag}, 182 | {suppressSuperFlag, SuppressSuperFlag}, 183 | {preloadSuperFlag, PreloadSuperFlag}, 184 | {suppressArgumentsFlag, SuppressArgumentsFlag}, 185 | {preloadArgumentsFlag, PreloadArgumentsFlag}, 186 | {suppressThisFlag, SuppressThisFlag}, 187 | {preloadThisFlag, PreloadThisFlag}, 188 | {preloadGlobalFlag, PreloadGlobalFlag}, 189 | {registerParams, RegisterParam}, 190 | {codeSize, CodeSize}]}; 191 | 192 | action(16#8F, <<_Reserved:5, 193 | CatchInRegisterFlag:1, 194 | FinallyBlockFlag:1, 195 | CatchBlockFlag:1, 196 | _TrySize:16/unsigned-integer-little, 197 | _CatchSize:16/unsigned-integer-little, 198 | _FinallySize:16/unsigned-integer-little, 199 | B/binary>>) -> 200 | {CatchNameOrRegister, _R} = case CatchInRegisterFlag of 201 | 0 -> 202 | {Name, R0} = swfdt:string(B), 203 | {{catchName, Name}, R0}; 204 | 1 -> 205 | <> = B, 206 | {{catchRegister, CatchRegister}, R0} 207 | end, 208 | 209 | %% NOTE: opposed to the documentation the bodies are not contained within this action 210 | %<> = R, 211 | {'try', [CatchNameOrRegister, 212 | {finallyBlockFlag, FinallyBlockFlag}, 213 | {catchBlockFlag, CatchBlockFlag} 214 | %, {tryBody, TryBody}, {catchBody, CatchBody}, {finallyBody, FinallyBody} 215 | ]}; 216 | 217 | action(16#94, <>) -> 218 | {with, [{size, Size}]}; 219 | 220 | action(16#96, <<0, B/binary>>) -> 221 | {S, _} = swfdt:string(B), 222 | {push, [{data, {'string', S}}]}; 223 | action(16#96, <<1, Value:32/signed-float-little>>) -> {push, [{data, {'float', Value}}]}; 224 | action(16#96, <<4, Value>>) -> {push, [{data, {registerNumber, Value}}]}; 225 | action(16#96, <<5, Value>>) -> {push, [{data, {'boolean', Value}}]}; 226 | action(16#96, <<6, Value:64/signed-float-little>>) -> {push, [{data, {'double', Value}}]}; 227 | action(16#96, <<7, Value:32/unsigned-integer-little>>) -> {push, [{data, {'integer', Value}}]}; 228 | action(16#96, <<8, Value>>) -> {push, [{data, {'constant8', Value}}]}; 229 | action(16#96, <<9, Value:16/unsigned-integer-little>>) -> {push, [{data, {'constant16', Value}}]}; 230 | action(16#96, <>) -> {push, [{data, {Type, Value}}]}; 231 | 232 | action(16#99, <>) -> 233 | {jump, [{branchOffset, BranchOffset}]}; 234 | 235 | action(16#9A, <>) -> 236 | {getURL2, [{sendVarsMethod, Method}, {loadTargetFlag, LoadTargetFlag}, {loadVariablesFlag, LoadVariablesFlag}]}; 237 | 238 | action(16#9B, B) -> 239 | {Name, <>} = swfdt:string(B), 240 | {Params, <>} = swfdt:cstringpool(R, NumParams), 241 | {defineFunction, [{name, Name}, {params, Params}, {codeSize, CodeSize}]}; 242 | 243 | action(16#9D, <>) -> 244 | {'if', [{branchOffset, BranchOffset}]}; 245 | 246 | action(16#9E, _) -> 247 | {call, []}; 248 | 249 | action(16#9F, <<_Reserved:6, 0:1, PlayFlag:1>>) -> 250 | {gotoFrame2, [{playFlag, PlayFlag}]}; 251 | action(16#9F, <<_Reserved:6, 1:1, PlayFlag:1, SceneBias:16/unsigned-integer-little>>) -> 252 | {gotoFrame2, [{playFlag, PlayFlag}, {sceneBias, SceneBias}]}; 253 | 254 | action(_ActionCode, B) -> 255 | {unknownAction, [{raw, B}]}. 256 | 257 | 258 | -------------------------------------------------------------------------------- /src/swfdt.erl: -------------------------------------------------------------------------------- 1 | -module(swfdt). 2 | -compile(export_all). 3 | -include("swfdt.hrl"). 4 | 5 | -export([ 6 | ui16/1, 7 | sb/1, sb/2, 8 | encodedu32/1, 9 | string/1, 10 | rect/1, 11 | matrix/1 12 | ]). 13 | 14 | %% 15 | %% primitive datatypes 16 | %% 17 | 18 | %% @doc decode 16-bit unsigned integer 19 | %% @spec ui16(binary()) -> {integer(), binary()} 20 | ui16(<>) -> 21 | {X, Rest}. 22 | 23 | %% @doc decode signed-bit value (aka. sign extension) 24 | %% @spec sb(bitstring()) -> integer() 25 | sb(<<>>) -> <<>>; %% empty value (this should rarely occur) 26 | sb(<<0:1, X/bitstring>>) -> %% positive value 27 | Size = bit_size(X), 28 | <> = X, 29 | Value; 30 | sb(<<1:1, X/bitstring>>) -> %% negative value 31 | Size = bit_size(X), 32 | <> = <<16#ffffffffffffffff:(64-Size), X/bitstring>>, 33 | Value. 34 | 35 | sb(Bitstring, Count) -> 36 | <> = Bitstring, 37 | {sb(B), Rest}. 38 | 39 | %% @doc decode 1-5 bit variable length u32 40 | %% @spec encodedu32(binary()) -> {int(), binary()} 41 | encodedu32(B) -> 42 | encodedu32(B, 0, <<>>). 43 | encodedu32(<<0:1, X:7/bitstring, Rest/binary>>, _Count, Acc) -> 44 | BitValue = <>, 45 | Size = bit_size(BitValue), 46 | <> = BitValue, 47 | Value = Value35 band (1 bsl 32 - 1), %% 35bit -> 32bit 48 | {Value, Rest}; 49 | encodedu32(<<1:1, X:7/bitstring, Rest/binary>>, Count, Acc) when Count < 4 -> 50 | encodedu32(Rest, Count + 1, <>). 51 | 52 | %% @doc null-terminated string 53 | %% @spec string(binary()) -> {string(), binary()} 54 | string(S) -> string(S, []). 55 | string(<<>>, Acc) -> string(<<0>>, Acc); %% not nerminated by 0 ? 56 | string(<<0,R/binary>>, Acc) -> {list_to_binary(lists:reverse(Acc)), R}; 57 | string(<>, Acc) -> string(R, [A|Acc]). 58 | 59 | 60 | %% 61 | %% meta types and helpers 62 | %% 63 | 64 | condfun(0, _Fun, B) -> {none, B}; 65 | condfun(1, Fun, B) -> Fun(B). 66 | 67 | %% @doc traverse B Count times using Fun 68 | %% @spec metaarray(any(), integer(), fun()) -> {[any()], any()} 69 | metaarray(B, Count, Fun) -> 70 | metaarray(B, Count, Fun, []). 71 | metaarray(B, 0, _Fun, Acc) -> 72 | {lists:reverse(Acc), B}; 73 | metaarray(B, Count, Fun, Acc) -> 74 | {Element, Rest} = Fun(B), 75 | metaarray(Rest, Count - 1, Fun, [Element|Acc]). 76 | 77 | %% @doc align bits to a byte by cutting off bits at the beginning 78 | %% @spec bytealign(bitstring()) -> binary() 79 | bytealign(Bitstring) -> 80 | Padding = bit_size(Bitstring) rem 8, 81 | <<_:Padding, B/binary>> = Bitstring, 82 | B. 83 | 84 | 85 | 86 | %% 87 | %% data types 88 | %% 89 | 90 | rect(<>) -> 96 | {#rect{xmin=Xmin, xmax=Xmax, ymin=Ymin, ymax=Ymax}, bytealign(B)}. 97 | 98 | 99 | %% @doc decode matrix into scale/rotate/translate tuples (bytealigned) 100 | %% @spec matrix(binary()) -> {sbtuple(), sbtuple(), sbtuple()} 101 | %% where sbtuple() = {float(), float()} 102 | matrix(X) -> 103 | {Scale, R1} = matrixsr(X), 104 | {Rotate, R2} = matrixsr(R1), 105 | {Translate, R3} = matrixtranslate(R2), 106 | {{Scale, Rotate, Translate}, bytealign(R3)}. 107 | matrixsr(<<0:1, B/bitstring>>) -> {{}, B}; %% scale/rotate 108 | matrixsr(<<1:1, N:5, A:N/bitstring, B:N/bitstring, R/bitstring>>) -> 109 | {{sb(A), sb(B)}, R}. 110 | matrixtranslate(<>) -> 111 | {{sb(A), sb(B)}, R}. 112 | 113 | 114 | assets(B) -> 115 | assets(B, []). 116 | assets(<<>>, Acc) -> lists:reverse(Acc); 117 | assets(<>, Acc) -> 118 | {S, R} = string(B), 119 | assets(R, [{CharID, S}|Acc]). 120 | 121 | stringpool(B) -> 122 | stringpool(B, []). 123 | stringpool(<<>>, Acc) -> 124 | lists:reverse(Acc); 125 | stringpool(B, Acc) -> 126 | {S, R} = string(B), 127 | stringpool(R, [S|Acc]). 128 | 129 | 130 | cstringpool(B, Count) -> 131 | metaarray(B, Count, fun(X) -> {S, R} = string(X), {S, R} end). 132 | 133 | 134 | registerparampool(B, Count) -> 135 | metaarray(B, Count, fun(<>) -> 136 | {S, R} = string(X), 137 | {{Register, S}, R} 138 | end). 139 | 140 | soundinfoenveloperecords(B, Count) -> 141 | metaarray(B, Count, fun(<>) -> 144 | {{Pos44, LeftLevel, RightLevel}, R} 145 | end). 146 | 147 | soundinfo(<<_Reserved:2, %% always 0 148 | SyncStop:1, 149 | SyncNoMultiple:1, 150 | HasEnvelope:1, 151 | HasLoops:1, 152 | HasOutPoint:1, 153 | HasInPoint:1, 154 | B/binary>>) -> 155 | InPointSize = 32 * HasInPoint, 156 | <> = B, 157 | OutPointSize = 32 * HasOutPoint, 158 | <> = R1, 159 | LoopsSize = 16 * HasLoops, 160 | <> = R2, 161 | EnvPointsSize = 8 * HasEnvelope, 162 | <> = R3, 163 | {ER, _} = soundinfoenveloperecords(R4, EnvPoints), 164 | {soundinfo, [ 165 | {syncStop, SyncStop}, 166 | {syncNoMultiple, SyncNoMultiple}, 167 | {inPoint, InPoint}, 168 | {outPoint, OutPoint}, 169 | {loopCount, LoopCount}, 170 | {envelopeRecords, ER}]}. 171 | 172 | 173 | stylearray(B, Fun) -> 174 | {SCount, SBin} = case B of 175 | <<16#ff, Count:16/unsigned-integer-little, SB/binary>> -> 176 | {Count, SB}; 177 | <> -> 178 | {Count, SB} 179 | end, 180 | metaarray(SBin, SCount, Fun). 181 | 182 | 183 | fillstylearray(Type, Bin) -> 184 | Fun = fun(<<0, R, G, B, B1/binary>>) -> %% solid fill 185 | {{rgb, R, G, B}, B1}; 186 | (<>) when FSType =:= 16#10; FSType =:= 16#12 -> %% linear/radial gradient fill 187 | {Matrix, B2} = matrix(B1), 188 | {Gradient, B3} = gradient(Type, B2), 189 | {{Matrix, Gradient}, B3}; 190 | (<>) when FSType =:= 16#13 -> %% focal radial gradient fill 191 | {FG, B2} = focalgradient(Type, B1), 192 | {FG, B2}; 193 | (<>) when FSType >= 16#40, FSType =< 16#43 -> %% bitmap fill 194 | {Matrix, B2} = matrix(B1), 195 | {{BitmapID, Matrix}, B2}%; 196 | %(<>) -> 197 | % debug("~p, nonesense~n", [FSType]), 198 | % {FSType, nonesense, Foo} 199 | end, 200 | stylearray(Bin, Fun). 201 | 202 | gradient(Type, <>) -> 203 | {GradRecords, R} = gradrecords(Type, B, NumGradients), 204 | {{SpreadMode, InterpolationMode, GradRecords}, R}. 205 | 206 | gradrecords(Type, Bin, Count) -> 207 | Fun = case Type of 208 | shape1 -> fun(<>) -> {{ratio_rgb, {Ratio, R, G, B}}, Rest} end 209 | end, 210 | metaarray(Bin, Count, Fun). 211 | 212 | focalgradient(Type, B) -> 213 | {gradient, {S, I, G}, <>} = gradient(Type, B), 214 | {focalgradient, {S, I, G, FocalPoint}, R}. 215 | 216 | linestylearray(Type, Bin) -> 217 | LineStyleRGB = fun(<>) -> {{Width, {rgb, R, G, B}}, Rest} end, 218 | Fun = case Type of 219 | shape1 -> LineStyleRGB 220 | end, 221 | stylearray(Bin, Fun). 222 | 223 | buttonrecord(<<0:2, %% reserved 224 | _HasBlendMode:1, 225 | _HasFilterList:1, 226 | StateHitTest:1, 227 | StateDown:1, 228 | StateOver:1, 229 | StateUp:1, 230 | CharacterID:16/unsigned-integer-little, 231 | PlaceDepth:16/unsigned-integer-little, 232 | B/binary>>) -> 233 | {PlaceMatrix, Rest} = matrix(B), 234 | {buttonrecord, [{stateHitTest, StateHitTest}, 235 | {stateDown, StateDown}, 236 | {stateOver, StateOver}, 237 | {stateUp, StateUp}, 238 | {characterID, CharacterID}, 239 | {placeDepth, PlaceDepth}, 240 | {placeMatrix, PlaceMatrix}], Rest}. 241 | 242 | % buttonrecord2(<<0:2, 243 | % HasBlendMode:1, 244 | % HasFilterList:1, 245 | % _/bitstring>> = B) -> 246 | % {buttonrecord, BR, R1} = buttonrecord(B), 247 | % {cxformwithalpha, CX, R2} = cxformwithalpha(R1), 248 | % {filterlist, FL, R3} = filterlistcond(HasFilterList, R2), 249 | % {blendmode, BM, R4} = case HasBlendMode of 250 | % 0 -> {blendmode, none, R3}; 251 | % 1 -> <> = R3, {blendmode, BlendMode, R5} 252 | % end, 253 | % {buttonrecord, [{cxFormWithAlpha, CX}, {filterList, FL}, {blendMode, BM}|BR], R4}. 254 | 255 | 256 | records(B, Fun) -> 257 | records(B, Fun, []). 258 | records(<<0, R/binary>>, _Fun, Acc) -> 259 | {lists:reverse(Acc), R}; 260 | records(<<>>, _Fun, Acc) -> %% unconventional end of block 261 | {lists:reverse(Acc), <<>>}; 262 | records(B, Fun, Acc) -> 263 | {BR, Rest} = Fun(B), 264 | records(Rest, [BR|Acc]). 265 | 266 | buttonrecords(B) -> 267 | records(B, fun(X) -> 268 | {buttonrecord, BR, Rest} = buttonrecord(X), 269 | {BR, Rest} 270 | end). 271 | 272 | %buttonrecords2(B) -> 273 | % records(B, fun(X) -> 274 | % {buttonrecord, BR, Rest} = buttonrecord2(X), 275 | % {BR, Rest} 276 | % end). 277 | % 278 | 279 | cxform(<>) -> 280 | {MT, BS1} = case HasMultTerms of 281 | 0 -> {#cxform{}, BS}; 282 | 1 -> <> = BS, {#cxform{redmult=sb(R), greenmult=sb(G), bluemult=sb(B)}, R1} 283 | end, 284 | {AT, BS2} = case HasAddTerms of 285 | 0 -> {MT, BS1}; 286 | 1 -> <> = BS1, {MT#cxform{redadd=sb(Ra), greenadd=sb(Ga), blueadd=sb(Ba)}, R2} 287 | end, 288 | {AT, bytealign(BS2)}. 289 | 290 | cxformwithalpha(<>) -> 291 | {MT, BS1} = case HasMultTerms of 292 | 0 -> {#cxformwa{}, BS}; 293 | 1 -> <> = BS, {#cxformwa{redmult=sb(R), greenmult=sb(G), bluemult=sb(B), alphamult=sb(A)}, R1} 294 | end, 295 | {AT, BS2} = case HasAddTerms of 296 | 0 -> {MT, BS1}; 297 | 1 -> <> = BS1, {MT#cxformwa{redadd=sb(Ra), greenadd=sb(Ga), blueadd=sb(Ba), alphaadd=sb(Aa)}, R2} 298 | end, 299 | {AT, bytealign(BS2)}. 300 | 301 | 302 | %filterlistcond(0, B) -> {filterlist, none, B}; 303 | %filterlistcond(1, B) -> filterlist(B). 304 | % 305 | %filterlist(<>) -> 306 | % metaarray(B, N, fun filter/1). 307 | % 308 | %filter(<<0, 309 | % R, G, B, A, 310 | % BlurX:32/bitstring, 311 | % BlurY:32/bitstring, 312 | % Angle:32/bitstring, 313 | % Distance:32/bitstring, 314 | % Strength:16/bitstring, 315 | % InnerShadow:1, 316 | % Knockout:1, 317 | % CompositeSource:1, 318 | % Passes:5, 319 | % Rest/binary>>) -> 320 | % {{dropShadowFilter, 321 | % [{id, 0}, 322 | % {dropShadowColor, {rgba, R, G, B, A}}, 323 | % {blurX, BlurX}, 324 | % {blurY, BlurY}, 325 | % {angle, Angle}, 326 | % {distance, Distance}, 327 | % {strength, Strength}, 328 | % {innerShadow, InnerShadow}, 329 | % {knockout, Knockout}, 330 | % {compositeSource, CompositeSource}, 331 | % {passes, Passes}]}, 332 | % Rest}; 333 | %filter(<>) -> 334 | % {{unknownFilter, [{id, Id}]}, Rest}. 335 | 336 | 337 | 338 | %style(<>) -> 339 | % {foo, B}. 340 | %shaperecords(B) -> 341 | % stylerecords(B, []). 342 | %shaperecords(<<0:1, 0:5, _:2, Rest/binary>>, Acc) -> %% end shape record 343 | % {lists:reverse(Acc), Rest}; 344 | %shaperecords(<<>>, Acc) -> %% abrupt end of stylerecords 345 | % {lists:reverse(Acc), <<>>}; 346 | %shaperecords(B, Acc) -> 347 | % {SR, R} = stylerecord(B), 348 | % stylerecords(R, [SR|Acc]). 349 | %shaperecord(<<0:1, %% type flag 350 | % StateNewStyles:1, 351 | % StateLineStyle:1, 352 | % StateFillStyle1:1, 353 | % StateFillStyle0:1, 354 | % StateMoveTo:1, 355 | % B/binary>>) -> %% change shape record 356 | % {Move, R1} = case StateMoveTo of 357 | % 1 -> 358 | % <> = B, 359 | % {{DX, DY}, R}; 360 | % 0 -> {none, B} 361 | % end, 362 | % {FS0, R2} = case StateFillStyle0 of 363 | % 1 -> 364 | % <<...>>, 365 | % {foo, B}. 366 | 367 | 368 | %textrecords(Params, B) -> 369 | % textrecords(Params, B, []). 370 | %textrecords(_Params, <<0:1,_:7, B/binary>>, Acc) -> 371 | % {textrecords, lists:reverse(Acc), B}; 372 | %textrecords({GB, AB, IsDefineText2}, B, Acc) -> 373 | % {textrecord, TR, R} = textrecord(B, IsDefineText2, GB, AB), 374 | % textrecords(GB, AB, R, [TR|Acc]). 375 | % 376 | %textrecordfield(fontID, 1, <>) -> {FontID, B}; 377 | %textrecordfield({textColor, 0}, 1, B) -> 378 | % {rgb, RGB, Rest} = rgb(B), 379 | % {{rgb, RGB}, Rest}; 380 | %textrecordfield({textColor, 1}, 1, B) -> 381 | % {argb, ARGB, Rest} = argb(B), 382 | % {{argb, ARGB}, Rest}; 383 | %textrecordfield(offset, 1, <>) -> {Offset, B}; 384 | %textrecordfield(offset, 0, B) -> {0, B}; 385 | %textrecordfield(textHeight, 1, <>) -> {Height, B}; 386 | %textrecordfield(_Name, 0 = _Defined, B) -> {none, B}. 387 | % 388 | %textrecord(<<_TextRecordType:1, %% always 1 389 | % _StyleFlagsReserved:3, %% always 0 390 | % StyleFlagsHasFont:1, 391 | % StyleFlagsHasColor:1, 392 | % StyleFlagsHasXOffset:1, 393 | % StyleFlagsHasYOffset:1, 394 | % B/binary>>, IsDefineText2, GlyphBits, AdvanceBits) -> 395 | % {FontID, R1} = textrecordfield(fontID, StyleFlagsHasFont, B), 396 | % {TextColor, R2} = textrecordfield({textColor, IsDefineText2}, StyleFlagsHasColor, R1), 397 | % {XOffset, R3} = textrecordfield(offset, StyleFlagsHasXOffset, R2), 398 | % {YOffset, R4} = textrecordfield(offset, StyleFlagsHasYOffset, R3), 399 | % {TextHeight, <>} = textrecordfield(textHeight, StyleFlagsHasFont, R4), 400 | % glyphs, %% ???? 401 | % {textrecord, [ 402 | % {fontID, FontID}, 403 | % {textColor, TextColor}, 404 | % {xOffset, XOffset}, 405 | % {yOffset, YOffset}, 406 | % {textHeight, TextHeight}], Rest}. 407 | 408 | 409 | clipactions(<<0:16, %% reserved 410 | B/binary>>) -> 411 | {unimplemented, B}. 412 | 413 | rgb(<>) -> {{rgb, R, G, B}, Rest}. 414 | rgba(<>) -> {{rgba, R, G, B, A}, Rest}. 415 | argb(<>) -> {{argb, A, R, G, B}, Rest}. 416 | 417 | 418 | buttoncondactions(B) -> 419 | buttoncondactions(B, []). 420 | 421 | buttoncondactions(<> = B, Acc) -> 422 | Acc2 = [buttoncondaction(R)|Acc], 423 | 424 | <<_Skip:Size/binary, NextB/binary>> = B, 425 | case Size of 426 | 0 -> lists:reverse(Acc2); 427 | _ -> buttoncondactions(NextB, Acc2) 428 | end. 429 | 430 | buttoncondaction(<>) -> 431 | {actions, Actions, _} = swfaction:actionrecords(R), 432 | {buttoncondaction, Actions, Conditions}. 433 | 434 | -------------------------------------------------------------------------------- /src/swfformat.erl: -------------------------------------------------------------------------------- 1 | -module(swfformat). 2 | -export([ 3 | tagformat/1, 4 | tagformat/2, 5 | formatactions/1 6 | ]). 7 | 8 | -include("swf.hrl"). 9 | 10 | %% 11 | %% format tags 12 | %% 13 | 14 | %% @doc format tags for display/storage; 15 | %% ABC can be decoded by abcdump.exe (must be in path) 16 | %% @spec tagformat(tag()) -> [{postfix(), mimetype(), binary()}] 17 | tagformat(#tag{code=Code, name=Name, pos=Pos, raw=Raw, contents=Contents}) -> 18 | [{".meta.txt", "text/plain", list_to_binary(lists:flatten(io_lib:format("code: ~3.10.0B~nname: ~s~npos: ~.10B/0x~.16B~n", [Code, atom_to_list(Name), Pos, Pos])))}, 19 | {".raw", "application/octet-stream", Raw}, 20 | {".contents.erl", "text/plain", list_to_binary(io_lib:format("~p~n", [Contents]))} 21 | | tagformat(Name, Contents)]; 22 | tagformat(_) -> %% no tag 23 | []. 24 | 25 | tagformat(_, none) -> 26 | []; 27 | 28 | tagformat('defineBitsJPEG2', Contents) -> 29 | {value, {jpegData, Data}} = lists:keysearch(jpegData, 1, Contents), 30 | [{".jpg", "image/jpeg", Data}]; 31 | 32 | tagformat('doABC', Contents) -> 33 | {value, {data, Data}} = lists:keysearch(data, 1, Contents), 34 | Dump = case os:find_executable("abcdump.exe") of 35 | false -> 36 | []; 37 | AbcDump -> 38 | TmpFilename = "/tmp/ssa-tmp." ++ os:getpid(), 39 | file:write_file(TmpFilename, Data), 40 | Out = os:cmd(AbcDump ++ " " ++ TmpFilename), 41 | file:delete(TmpFilename), 42 | [{".abc.asm", "text/plain", Out}] 43 | end, 44 | [{".abc", "application/octet-stream", Data}|Dump]; 45 | 46 | tagformat('doABC1', Contents) -> 47 | tagformat('doABC', Contents); 48 | 49 | tagformat('doInitAction', Contents) -> 50 | {value, {actions, Data}} = lists:keysearch(actions, 1, Contents), 51 | [{".pseudo.asm", "text/plain", formatactions(Data)}]; 52 | 53 | tagformat('doAction', Contents) -> 54 | tagformat('doInitAction', Contents); 55 | 56 | %tagformat('defineBitsJPEG3', Contents) -> %% broken jpegs?? 57 | % tagformat('defineBitsJPEG2', Contents); 58 | 59 | 60 | tagformat(_OtherName, _Contents) -> 61 | []. 62 | 63 | %% 64 | %% format helpers 65 | %% 66 | 67 | formatactions(Data) -> 68 | OutList = lists:foldr(fun({Name, Content}, Acc) -> 69 | {value, {code, Code}} = lists:keysearch(code, 1, Content), 70 | {value, {pos, Pos}} = lists:keysearch(pos, 1, Content), 71 | Line = lists:flatten(io_lib:format("<~8.10.0B> [~2.16.0B] ~s", [Pos, Code, Name])), 72 | Params = lists:keydelete(pos, 1, lists:keydelete(code, 1, Content)), 73 | Combine = fun(L, []) -> L; %% no params 74 | (L, E) -> %% one or more params 75 | lists:flatten(io_lib:format("~-31.. s~p", [L, E])) 76 | end, 77 | [Combine(Line, Params)|Acc] 78 | end, [], Data), 79 | lists:flatmap(fun(L) -> L ++ "\n" 80 | end, OutList). 81 | 82 | -------------------------------------------------------------------------------- /src/swfjson.erl: -------------------------------------------------------------------------------- 1 | -module(swfjson). 2 | -compile(export_all). 3 | -include("swf.hrl"). 4 | -include("swfdt.hrl"). 5 | 6 | -define(record_obj(RName, Record), record_obj(RName, Record, record_info(fields, RName))). 7 | 8 | 9 | obj(#swf{}=X) -> ?record_obj(swf, X); 10 | obj(#swfheader{type=Type, version=Version, filelength=FileLength, framesize=FrameSize, framerate=FrameRate, framecount=FrameCount, headersize=HeaderSize, rawheader=_}) -> 11 | {obj, [ 12 | {cn, swfheader}, 13 | {type, Type}, 14 | {version, Version}, 15 | {filelength, FileLength}, 16 | {framesize, obj(FrameSize)}, 17 | {framerate, FrameRate}, 18 | {framecount, FrameCount}, 19 | {headersize, HeaderSize}]}; 20 | 21 | obj(#tag{code=Code, name=Name, pos=Pos, raw=_, contents=Contents}) -> 22 | {obj, [{cn, tag}, {code, Code}, {tagname, Name}, {pos, Pos}]}; 23 | 24 | obj(#rect{}=X) -> ?record_obj(rect, X); 25 | 26 | obj(X) when is_integer(X) -> X; 27 | %obj([]) -> []; 28 | obj(X) when is_list(X) -> [obj(A) || A <- X]; 29 | 30 | % obj([T|_]=X) when is_tuple(T) -> {obj, [obj(A) || A <- X]}; %% todo 31 | 32 | obj(_) -> not_implemented. 33 | 34 | 35 | record_obj(RName, Record, Fields) when element(1, Record) =:= RName -> 36 | record_obj(RName, 2, Record, Fields, []). 37 | record_obj(RName, _, _, [], Acc) -> 38 | {obj, lists:reverse([{cn, RName}|Acc])}; 39 | record_obj(RName, I, Record, [FieldName|R], Acc) -> 40 | Element = element(I, Record), 41 | Obj = obj(Element), 42 | record_obj(RName, I+1, Record, R, [{FieldName, Obj}|Acc]). 43 | -------------------------------------------------------------------------------- /src/swfjsonabc.erl: -------------------------------------------------------------------------------- 1 | -module(swfjsonabc). 2 | -compile(export_all). 3 | -include("swfabc.hrl"). 4 | -define(record_obj(RName, Record), record_obj(RName, Record, record_info(fields, RName))). 5 | 6 | 7 | obj(X) when is_atom(X) -> X; 8 | obj(X) when is_integer(X) -> X; 9 | obj([]) -> []; 10 | obj(X) when is_list(X) -> [obj(A) || A <- X]; 11 | 12 | obj(#abcfile{}=X) -> ?record_obj(abcfile, X); 13 | obj(#cpool{}=X) -> ?record_obj(cpool, X); 14 | obj(#method{}=X) -> ?record_obj(method, X); 15 | obj(#instance{}=X) -> ?record_obj(instance, X); 16 | obj(#class{}=X) -> ?record_obj(class, X); 17 | obj(#script{}=X) -> ?record_obj(script, X); 18 | obj(#method_body{}=X) -> ?record_obj(method_body, X); 19 | 20 | obj(#trait{}=X) -> ?record_obj(trait, X); 21 | obj(#instr{}=X) -> ?record_obj(instr, X); 22 | 23 | obj({string, X}) when is_binary(X) -> X; 24 | obj({string, X}) when is_list(X) -> list_to_binary(X); 25 | obj({string, X}) when is_atom(X) -> atom_to_list(X); 26 | 27 | obj({namespace, {Kind, Name}}) -> {obj, [{kind, obj(Kind)}, {name, obj(Name)}, {cn, namespace}]}; 28 | 29 | obj({keyvalue, {Key, Value}}) -> [Key, Value]; 30 | 31 | obj(_) -> 32 | not_implemented. 33 | 34 | %% encode special record members 35 | obj(abcfile, version, {X, Y}) -> [X, Y]; 36 | obj(abcfile, metadata, X) -> [obj({keyvalue, A}) || A <- X]; 37 | 38 | obj(cpool, integer, X) -> X; 39 | obj(cpool, uinteger, X) -> X; 40 | obj(cpool, double, X) -> X; 41 | obj(cpool, string, X) -> X; 42 | obj(cpool, namespace, X) -> [obj({namespace, A}) || A <- X]; 43 | 44 | obj(_, _, X) -> obj(X). 45 | 46 | record_obj(RName, Record, Fields) when element(1, Record) =:= RName -> 47 | record_obj(RName, 2, Record, Fields, []). 48 | record_obj(RName, _, _, [], Acc) -> 49 | {obj, lists:reverse([{cn, RName}|Acc])}; 50 | record_obj(RName, I, Record, [FieldName|R], Acc) -> 51 | Element = element(I, Record), 52 | Obj = obj(RName, FieldName, Element), 53 | record_obj(RName, I+1, Record, R, [{FieldName, Obj}|Acc]). 54 | -------------------------------------------------------------------------------- /src/swfmime.erl: -------------------------------------------------------------------------------- 1 | -module(swfmime). 2 | -export([getmime/1]). 3 | 4 | %% @doc match for known swf-related mimetype 5 | %% @spec getmime(binary()) -> swf | abc | empty | unknown 6 | getmime(<<"CWS", _/binary>>) -> swf; 7 | getmime(<<"FWS", _/binary>>) -> swf; 8 | getmime(<>) when X =:= 16; X =:= 15; X =:= 14 -> abc; 9 | getmime(<<>>) -> empty; 10 | getmime(X) when is_binary(X) -> unknown. -------------------------------------------------------------------------------- /src/swfutils.erl: -------------------------------------------------------------------------------- 1 | -module(swfutils). 2 | -export([ 3 | dumpswf/1, 4 | dumpswftags/1, 5 | filedumpswf/2, 6 | dumptags/2, 7 | dumpsecuritycheck/1, 8 | silentsecuritycheck/1, 9 | filtertags/2, 10 | savetofile/3, 11 | tagencode/2, 12 | abcdata/1, 13 | actiondata/1, 14 | abc2oplist/1, 15 | actions2oplist/1]). 16 | 17 | -include("swf.hrl"). 18 | -include("swfabc.hrl"). 19 | 20 | %% 21 | %% utils 22 | %% 23 | 24 | dumpswf({swf, Header, Tags}) -> 25 | io:format("~n-----------------------------------~n", []), 26 | io:format("** header: ~p~n", [Header]), 27 | dumpswftags(Tags). 28 | 29 | dumpswftags([]) -> done; 30 | dumpswftags([{tag, Code, Name, Pos, _Raw, Contents}|Rest]) -> 31 | io:format("** <~8.10.0B> [~3.10.0B] ~p : ~p~n", [Pos, Code, Name, Contents]), 32 | dumpswftags(Rest); 33 | dumpswftags([{rawtag, Code, Name, Pos, _Raw, _}|Rest]) -> 34 | io:format("** <~8.10.0B> [~3.10.0B] ~p : (not decoded)~n", [Pos, Code, Name]), 35 | dumpswftags(Rest). 36 | 37 | 38 | 39 | filedumpswf(#swf{header=Header, tags=Tags}, Prefix) -> 40 | %% dump header 41 | savetofile("~s-header.erl", [Prefix], list_to_binary(io_lib:format("~p~n", [Header]))), 42 | 43 | %% dump tags 44 | lists:foldl(fun(Tag, Acc) -> 45 | lists:foreach(fun({Postfix, _MimeType, B}) -> 46 | savetofile("~s-tag~5.10.0B~s", [Prefix, Acc, Postfix], B) 47 | end, swfformat:tagformat(Tag)), 48 | Acc + 1 end, 1, Tags). 49 | 50 | 51 | dumptags(#swf{tags=RawTags}, Tagnames) -> 52 | dumpswftags([swf:tagdecode(Tag) || Tag <- filtertags(Tagnames, RawTags)]). 53 | 54 | 55 | 56 | getactions(doAction, TagContents) -> 57 | {value, {actions, Actions}} = lists:keysearch(actions, 1, TagContents), 58 | Actions; 59 | getactions(doInitAction, TC) -> 60 | getactions(doAction, TC). 61 | 62 | checkactions([]) -> ok; 63 | checkactions(Actions) -> %% >= 1 element 64 | [{_, LastActionArgs}|_] = lists:reverse(Actions), %% assumption: min 1 element available 65 | {value, {pos, LastActionPos}} = lists:keysearch(pos, 1, LastActionArgs), 66 | 67 | case lists:keysearch(unknownAction, 1, Actions) of 68 | false -> ok; 69 | {value, X} -> throw(X) 70 | end, 71 | 72 | Branches = lists:filter(fun({Op, _Args}) -> lists:member(Op, ['jump', 'if']) end, Actions), 73 | lists:foreach(fun({_, Args}) -> 74 | {value, {branchOffset, BO}} = lists:keysearch(branchOffset, 1, Args), 75 | {value, {pos, Pos}} = lists:keysearch(pos, 1, Args), 76 | Report = fun(Cond) -> 77 | case Cond of 78 | true -> throw({branchOOB, Pos}); 79 | false -> ok 80 | end 81 | end, 82 | Report(BO + Pos < 0), 83 | Report(BO + Pos > LastActionPos) 84 | end, Branches), 85 | ok. 86 | 87 | has_invalid_actions_t(Tags) -> 88 | ActiveTags = filtertags(['doInitAction', 'doAction'], Tags), 89 | 90 | lists:foreach(fun({tag, _Code, Name, _Pos, _Raw, Contents}) -> 91 | Actions = getactions(Name, Contents), 92 | checkactions(Actions) 93 | end, ActiveTags), 94 | ok. 95 | 96 | has_invalid_actions(Tags) -> 97 | try has_invalid_actions_t(Tags) of 98 | _ -> false 99 | catch 100 | throw:X -> {true, X} 101 | end. 102 | 103 | dumpsecuritycheck(#swf{tags=Tags}) -> 104 | T1 = case lists:keysearch(unknownTag, 3, Tags) of 105 | false -> ok; 106 | {value, X} -> io:format(" contains unknown tag: ~p~n", [X]) 107 | end, 108 | 109 | T2 = case has_invalid_actions(Tags) of 110 | false -> ok; 111 | {true, Why} -> io:format(" contains invalid action: ~p~n", [Why]) 112 | end, 113 | 114 | (T1 =:= T2) =:= ok. 115 | 116 | silentsecuritycheck(#swf{tags=Tags}) -> 117 | case lists:keysearch(unknownTag, 3, Tags) of 118 | false -> ok; 119 | {value, X} -> throw({not_ok, X}) 120 | end, 121 | 122 | case has_invalid_actions(Tags) of 123 | false -> ok; 124 | {true, Why} -> throw({not_ok, Why}) 125 | end, 126 | 127 | ok. 128 | 129 | %% 130 | %% helpers 131 | %% 132 | filtertags(Names, Tags) -> 133 | lists:filter(fun(#tag{name=Name}) -> lists:member(Name, Names) end, Tags). 134 | 135 | 136 | savetofile(Fmt, Args, Data) -> 137 | Outfilename = lists:flatten(io_lib:format(Fmt, Args)), 138 | io:format("saving ~p~n", [Outfilename]), 139 | file:write_file(Outfilename, Data). 140 | 141 | %% encode tag 142 | tagencode(Code, B) -> 143 | BSize = size(B), 144 | TagAndLength = case BSize >= 16#3f of 145 | true -> 146 | <> = <>, 147 | <>; 148 | false -> 149 | <> = <>, 150 | <> 151 | end, 152 | <>. 153 | 154 | 155 | abcdata(#swf{tags=RawTags}) -> 156 | AbcTags = [swf:tagdecode(Tag) || Tag <- filtertags(['doABC', 'doABC1'], RawTags)], 157 | lists:map(fun(#tag{contents=Contents}) -> 158 | {value, {data, Abc}} = lists:keysearch(data, 1, Contents), 159 | Abc 160 | end, AbcTags). 161 | 162 | actiondata(#swf{tags=RawTags}) -> 163 | ActionTags = [swf:tagdecode(Tag) || Tag <- filtertags(['doAction', 'doInitAction', 'defineButton', 'defineButton2'], RawTags)], 164 | lists:map(fun(#tag{contents=Contents}) -> 165 | case lists:keysearch(actions, 1, Contents) of 166 | {value, {_, Actions}} -> Actions; 167 | false -> case lists:keysearch(buttoncondaction, 1, Contents) of 168 | {value, {_, Actions, _}} -> Actions; 169 | false -> [] 170 | end 171 | end 172 | end, ActionTags). 173 | 174 | 175 | %% get op-list-list for n-gram analysis 176 | %% returns [[atom(),...],...] 177 | abc2oplist(#abcfile{method_body=MB}) -> 178 | lists:map(fun(#method_body{code=Code}) -> 179 | [C#instr.name || C <- Code] 180 | end, MB). 181 | 182 | actions2oplist(Actions) -> 183 | lists:map(fun({Name, _}) -> Name end, Actions). 184 | 185 | -------------------------------------------------------------------------------- /ssacli.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | -mode(compile). 3 | %% 4 | %% Command Line Interface for the simple swf analyzer 5 | %% author: Ben Fuhrmannek 6 | %% license: GPLv3 - http://www.gnu.org/licenses/licenses.html#GPL 7 | %% 8 | 9 | addlibpath() -> 10 | %% search for erlswf base directory 11 | SN = escript:script_name(), 12 | ScriptName = case file:read_link(SN) of 13 | {ok, X} -> X; 14 | _ -> SN 15 | end, 16 | SwfBaseDir = filename:dirname(ScriptName), 17 | 18 | code:add_patha(filename:join(SwfBaseDir, "ebin")), 19 | 20 | %% search for json library 21 | case code:which(rfc4627) of 22 | non_existing -> 23 | code:add_patha(filename:join([SwfBaseDir, "lib", "erlang-rfc4627", "ebin"])); 24 | _ -> ok 25 | end. 26 | 27 | error_msg(Msg, Args) -> 28 | io:format("error: "++Msg++"~n", Args), 29 | halt(1). 30 | 31 | main(Args) -> 32 | %% include path 33 | addlibpath(), 34 | 35 | %% log errors to console 36 | error_logger:tty(true), 37 | 38 | Commands = ssamod:commands(), 39 | 40 | case Args of 41 | [] -> %% show help 42 | credits(), 43 | usage(Commands); 44 | ["help", CmdName] -> 45 | case lists:keysearch(CmdName, 1, Commands) of 46 | {value, {_, _, CmdDetails}} -> longhelp(CmdName, CmdDetails); 47 | false -> error_msg("no such command: \"~s\"", [CmdName]) 48 | end; 49 | [Cmd|CArgs] -> 50 | case lists:keysearch(Cmd, 1, Commands) of 51 | {value, {_, Mod, _}} -> 52 | run(Mod, [Cmd|CArgs]); 53 | false -> 54 | error_msg("invalid cmd: \"~s\"", [Cmd]) 55 | end 56 | end, 57 | 58 | %% give io some time to complete 59 | timer:sleep(500). 60 | 61 | run(Mod, Args) -> 62 | try Mod:run(Args) of 63 | ok -> ok; 64 | invalid_usage -> 65 | error_msg("invalid usage. see help", []); 66 | Err -> 67 | error_msg("something went wrong: ~p", [Err]) 68 | catch 69 | error:Err -> error_msg("~p: ~p", [Err, erlang:get_stacktrace()]) 70 | end. 71 | 72 | usage(Cmds) -> 73 | io:format("usage: ~s [cmd] [args]~n", [escript:script_name()]), 74 | io:format(" where [cmd] is one of~n~n"), 75 | shorthelp(Cmds), 76 | io:format("detailed help: help [cmd]~n~n"). 77 | 78 | shorthelp(Cmds) -> 79 | lists:foreach(fun({CmdName, Mod, CmdDetails}) -> 80 | Usage = proplists:get_value(usage, CmdDetails, "(no usage / do not use)"), 81 | ShortHelp = proplists:get_value(shorthelp, CmdDetails, "(no short help available)"), 82 | io:format(" -> ~s (module: ~s)~n", [CmdName, atom_to_list(Mod)]), 83 | io:format(" | ~s~n", [Usage]), 84 | io:format(" | ~s~n~n", [ShortHelp]) 85 | end, lists:keysort(1, Cmds)). 86 | 87 | longhelp(CmdName, CmdDetails) -> 88 | Usage = proplists:get_value(usage, CmdDetails, "(no usage / do not use)"), 89 | ShortHelp = proplists:get_value(shorthelp, CmdDetails, "(no short help available)"), 90 | LongHelp = proplists:get_value(longhelp, CmdDetails, "(no detailed help available)"), 91 | io:format("*** help for '~s' ***~n", [CmdName]), 92 | io:format("~s~n~n~s~n~n~s~n~n", [Usage, ShortHelp, LongHelp]). 93 | 94 | credits() -> 95 | io:format(" ____ ____ _ ~n/ ___/ ___| / \\ ~n\\___ \\___ \\ / _ \\ ~n ___) |__) / ___ \\ ~n|____/____/_/ \\_\\ (cl) Ben Fuhrmannek ~n~n", []). 96 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | mkdir -p run 3 | cd run ; erl -sname ct -noshell -pa `pwd`/../../ebin -pa `pwd`/.. -s ct_run script_start -s erlang halt -dir .. 4 | 5 | clean: 6 | rm -rf run 7 | rm -f *.beam 8 | 9 | -------------------------------------------------------------------------------- /test/ngram_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(ngram_SUITE). 2 | -compile(export_all). 3 | 4 | all() -> [ngram_n_lt_l, ngram, incr_pl, ngramfold, cut_profile, intersect_profiles, expand_profiles, simplified_distance, merge_profiles]. 5 | 6 | 7 | ngram_n_lt_l(_Config) -> 8 | [] = ngram:ngram(4, [], [a,b,c]), 9 | ok. 10 | 11 | ngram(_) -> 12 | [{[a,b],2}, {[b,a],1}] = ngram:ngram(2, [], [a,b,a,b]), ok. 13 | 14 | incr_pl(_) -> 15 | [{a, 1}] = ngram:incr_pl(a, [], 1), 16 | [{a, 3}, {b,1}] = ngram:incr_pl(a, [{a,1}, {b,1}], 2), ok. 17 | 18 | ngramfold(_) -> 19 | [{[a], 3}] = ngram:ngramfold(1, [[a,a],[a]]). 20 | 21 | cut_profile(_) -> 22 | [{d,23}, {b,4}, {c,2}] = ngram:cut_profile(3, [{a,1}, {b,4}, {c,2}, {d,23}]), ok. 23 | 24 | intersect_profiles(_) -> 25 | {[{b,2}],[{b,1}]} = ngram:intersect_profiles([{a,1}, {b,2}, {c,3}], [{d,4}, {b,1}]), ok. 26 | 27 | expand_profiles(_) -> 28 | {[{a,1},{b,0}],[{b,2},{a,0}]} = ngram:expand_profiles([{a,1}], [{b,2}]), ok. 29 | 30 | simplified_distance(_) -> 31 | {1, 1.0} = ngram:simplified_distance([{a,1}, {b,3}, {c,3}], [{d,4}, {b,1}]), ok. 32 | 33 | merge_profiles(_) -> 34 | [{b,4},{d,4},{a,1},{c,3}] = ngram:merge_profiles([{a,1}, {b,3}, {c,3}], [{d,4}, {b,1}]), ok. -------------------------------------------------------------------------------- /test/swfabc_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(swfabc_SUITE). 2 | -compile(export_all). 3 | 4 | all() -> [u8, u16, s24, u32, u30, d64]. 5 | 6 | %% simple data types 7 | 8 | u8(_) -> 9 | {123, <<234>>} = swfabc:u8(<<123, 234>>), ok. 10 | 11 | u16(_) -> 12 | {513, <<234>>} = swfabc:u16(<<1,2,234>>), ok. 13 | 14 | s24(_) -> 15 | {-2#100000000000000000000000 + 2#000000000000001000000001, <<4>>} = swfabc:s24(<<1,2,128,4>>), ok. 16 | 17 | u32(_) -> 18 | {127, <<>>} = swfabc:u32(<<2#01111111>>), 19 | {2#0011111011111110, <<>>} = swfabc:u32(<<2#11111110, 2#01111101>>), 20 | {2#000111101111111011111110, <<>>} = swfabc:u32(<<2#11111110, 2#11111101, 2#01111011>>), 21 | {2#00001110111111101111111011111110, <<>>} = swfabc:u32(<<2#11111110, 2#11111101, 2#11111011, 2#01110111>>), 22 | {2#11111110111111101111111011111110, <<>>} = swfabc:u32(<<2#11111110, 2#11111101, 2#11111011, 2#11110111, 2#00001111>>), 23 | ok. 24 | 25 | u30(_) -> 26 | {2#00111110111111101111111011111110, <<>>} = swfabc:u30(<<2#11111110, 2#11111101, 2#11111011, 2#11110111, 2#00001111>>), 27 | ok. 28 | 29 | d64(_) -> 30 | {23.42, <<>>} = swfabc:d64(<<236,81,184,30,133,107,55,64>>), ok. 31 | 32 | string(_) -> 33 | {"lorem ipsum", <<>>} = swfabc:string(<<11, "lorem ipsum">>). 34 | 35 | -------------------------------------------------------------------------------- /url_rewrite.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | -mode(compile). 3 | 4 | %% 5 | %% squid url rewriter 6 | %% 7 | 8 | %% squid.conf: 9 | %% acl flashfiles urlpath_regex -i \.swf$ 10 | %% acl redirector browser -i eswfurlrewrite 11 | %% 12 | %% url_rewrite_program /Users/BeF/devel/stuff/trunk/swf/url_rewrite.erl 13 | %% url_rewrite_children 1 14 | %% url_rewrite_concurrency 1 15 | %% url_rewrite_access deny to_localhost 16 | %% url_rewrite_access deny redirector 17 | %% url_rewrite_access allow flashfiles 18 | %% url_rewrite_access deny all 19 | %% 20 | 21 | 22 | main(_) -> 23 | code:add_pathsz([ 24 | "/Users/BeF/Desktop/software/jungerl/lib/ibrowse/ebin", %% ibrowse 25 | "/Users/BeF/devel/erlswf/trunk/ebin" %% swf analyzer 26 | ]), 27 | 28 | error_logger:tty(false), 29 | ibrowse:start(), 30 | Sender = spawn(fun sender/0), 31 | receiver(Sender). 32 | 33 | sender() -> 34 | receive 35 | quit -> ok; 36 | {put, S} -> 37 | io:format("~s", [S]), 38 | sender(); 39 | {log, S} -> 40 | file:write_file("/tmp/urlrewritedump", list_to_binary(S), [append]), 41 | sender() 42 | end. 43 | 44 | receiver(Sender) -> 45 | case io:get_line('') of 46 | eof -> 47 | Sender ! quit, 48 | halt(0); 49 | String -> 50 | spawn(fun() -> worker(Sender, String) end), 51 | receiver(Sender) 52 | end. 53 | 54 | worker(Sender, String) -> 55 | [ID, URL, _Client, _User, _Method, _URLGroup|_] = string:tokens(String, " "), 56 | NewURL = case check_url(URL) of 57 | true -> URL; 58 | {false, Reason} -> "302:about:" ++ lists:flatten(io_lib:format("~p", [Reason])) 59 | end, 60 | Sender ! {log, lists:flatten(io_lib:format("~s ~s -> ~s~n", [ID, URL, NewURL]))}, 61 | Sender ! {put, lists:flatten(io_lib:format("~s ~s~n", [ID, NewURL]))}. 62 | 63 | check_url(URL) -> 64 | case ibrowse:send_req(URL, [{"User-Agent", "eswfurlrewrite/0.1"}], get, [], [{proxy_host, "localhost"}, {proxy_port, 3128}]) of 65 | {error, X} -> {false, X}; 66 | {ok, _Code, _Header, Body} -> 67 | B = list_to_binary(Body), 68 | try swfutils:silentsecuritycheck(swf:swf(B)) of 69 | ok -> true 70 | catch 71 | throw:{not_ok, Reason} -> {false, Reason}; 72 | throw:no_swf -> {false, no_swf}; 73 | throw:X -> {false, X}; 74 | error:X -> {false, X} 75 | end 76 | end. 77 | --------------------------------------------------------------------------------