swxruby 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +21 -0
- data/LICENSE +20 -0
- data/README +143 -0
- data/Rakefile +111 -0
- data/bin/swxruby +58 -0
- data/examples/standalone/standalone.fla +0 -0
- data/examples/standalone/standalone.rb +37 -0
- data/examples/standalone/standalone.swf +0 -0
- data/init.rb +22 -0
- data/install.rb +69 -0
- data/lib/swxruby.rb +6 -0
- data/lib/swxruby/bytecode_converter.rb +139 -0
- data/lib/swxruby/core_extensions.rb +41 -0
- data/lib/swxruby/helper_module.rb +72 -0
- data/lib/swxruby/rails_integration/render_decorator.rb +20 -0
- data/lib/swxruby/rails_integration/swx.yml +18 -0
- data/lib/swxruby/rails_integration/swx_controller.rb +6 -0
- data/lib/swxruby/services/arithmetic.rb +9 -0
- data/lib/swxruby/services/discovery_service.rb +3 -0
- data/lib/swxruby/services/hello_world.rb +7 -0
- data/lib/swxruby/services/simple.rb +5 -0
- data/lib/swxruby/services/test_data_types.rb +94 -0
- data/lib/swxruby/swx_assembler.rb +138 -0
- data/lib/swxruby/swx_gateway.rb +104 -0
- data/lib/swxruby/version.rb +9 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/swxruby/bytecode_converter_spec.rb +162 -0
- data/spec/swxruby/core_extensions_spec.rb +16 -0
- data/spec/swxruby/fixtures/number_one_no_debug_compression_4.swx +0 -0
- data/spec/swxruby/fixtures/number_one_no_debug_no_compression.swx +0 -0
- data/spec/swxruby/fixtures/number_one_no_debug_no_compression_arbitrary_allow_domain.swx +0 -0
- data/spec/swxruby/fixtures/number_one_with_debug_compression_4.swx +0 -0
- data/spec/swxruby/fixtures/number_one_with_debug_no_compression.swx +0 -0
- data/spec/swxruby/rails_integration/init_spec.rb +0 -0
- data/spec/swxruby/rails_integration/render_decorator_spec.rb +73 -0
- data/spec/swxruby/rails_integration/swx_controller_spec.rb +5 -0
- data/spec/swxruby/swx_assembler_spec.rb +60 -0
- data/spec/swxruby/swx_gateway_spec.rb +160 -0
- metadata +108 -0
data/lib/swxruby.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'core_extensions'
|
2
|
+
require 'date'
|
3
|
+
require 'helper_module'
|
4
|
+
|
5
|
+
class BytecodeConverter
|
6
|
+
include HelperMethods
|
7
|
+
class << self
|
8
|
+
NULL_TERMINATOR = '00'
|
9
|
+
|
10
|
+
def convert(data)
|
11
|
+
conversion_method = "#{data.class.to_s.downcase}_to_bytecode"
|
12
|
+
|
13
|
+
if respond_to?(conversion_method)
|
14
|
+
send(conversion_method, data)
|
15
|
+
else
|
16
|
+
# Convert the object to a hash of its instance variables
|
17
|
+
object_hash = data.instance_values
|
18
|
+
|
19
|
+
if object_hash.empty?
|
20
|
+
raise StandardError, "#{data.class} is an unhandled data type."
|
21
|
+
else
|
22
|
+
hash_to_bytecode(object_hash)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
# = Data Type Conversion Methods
|
29
|
+
def complex_data_structure_to_bytecode(data) #:nodoc#
|
30
|
+
bytecode = []
|
31
|
+
|
32
|
+
# Keeps track of bytecode when recursing into nested data structures
|
33
|
+
stack = []
|
34
|
+
|
35
|
+
# Add the bytecode to initialize the data structure
|
36
|
+
bytecode.push(data.is_a?(Array) ? ActionCodes::INIT_ARRAY : ActionCodes::INIT_OBJECT)
|
37
|
+
|
38
|
+
# Add the length of the data structure to the bytecode
|
39
|
+
bytecode.push integer_to_bytecode(data.length)
|
40
|
+
|
41
|
+
# Convert each element in the data structure to bytecode
|
42
|
+
data.each do |element|
|
43
|
+
|
44
|
+
# assume we're not iterating into a recursive data structure
|
45
|
+
complex_data_structure = false
|
46
|
+
|
47
|
+
# if we're converting a hash, then convert the hash's value
|
48
|
+
if data.is_a?(Hash)
|
49
|
+
value_bytecode = convert(element[1])
|
50
|
+
# if we just converted an array or a hash, set complex_data_structure to true
|
51
|
+
complex_data_structure = true if value_bytecode.concludes_with?(ActionCodes::INIT_OBJECT, ActionCodes::INIT_ARRAY)
|
52
|
+
# set the element local variable to the key of the hash
|
53
|
+
element = element[0]
|
54
|
+
end
|
55
|
+
|
56
|
+
# element will always contain something (whether iterating over a hash or an array)
|
57
|
+
element_bytecode = convert(element)
|
58
|
+
# if we just converted an array or a hash, set complex_data_structure to true
|
59
|
+
complex_data_structure = true if element_bytecode.concludes_with?(ActionCodes::INIT_OBJECT, ActionCodes::INIT_ARRAY)
|
60
|
+
|
61
|
+
# Create a push of the current bytecode, if
|
62
|
+
# we recursed into a complex data structure
|
63
|
+
# or we're approaching the 65535 byte limit that can be stored in a single push.
|
64
|
+
if (complex_data_structure || calculate_bytecode_length(bytecode) > 65518)
|
65
|
+
|
66
|
+
# If we haven't written any bytecode into the local
|
67
|
+
# buffer yet (if it's empty), or all the data is already pushed, skip writing the push statement
|
68
|
+
bytecode.push generate_push_statement(bytecode) unless bytecode.empty? || bytecode.last.begins_with?('96')
|
69
|
+
|
70
|
+
# Store current instruction on the stack (SWF bytecode is stored in reverse, so we reverse it here)
|
71
|
+
stack.push bytecode.reverse.join
|
72
|
+
|
73
|
+
# Reset the bytecode
|
74
|
+
bytecode = []
|
75
|
+
end
|
76
|
+
|
77
|
+
# value_bytecode will be nil unless we converted a Hash
|
78
|
+
bytecode.push value_bytecode unless value_bytecode.nil?
|
79
|
+
|
80
|
+
bytecode.push element_bytecode
|
81
|
+
end
|
82
|
+
|
83
|
+
# If we haven't written any bytecode into the local
|
84
|
+
# buffer yet (if it's empty), or all the data is already pushed, skip writing the push statement
|
85
|
+
bytecode.push generate_push_statement(bytecode) unless bytecode.empty? || bytecode.last.begins_with?('96')
|
86
|
+
|
87
|
+
# Add the bytecode to the local stack variable (SWF bytecode is stored in reverse, so we reverse it here)
|
88
|
+
stack.push bytecode.reverse.join
|
89
|
+
|
90
|
+
# Join the stack array into a string and return it (SWF bytecode is stored in reverse, so we reverse it here)
|
91
|
+
stack.reverse.join
|
92
|
+
end
|
93
|
+
alias array_to_bytecode complex_data_structure_to_bytecode
|
94
|
+
alias hash_to_bytecode complex_data_structure_to_bytecode
|
95
|
+
|
96
|
+
def date_to_bytecode(date) #:nodoc#
|
97
|
+
# Format: 2006-09-14
|
98
|
+
string_to_bytecode(date.strftime('%Y-%m-%d'))
|
99
|
+
end
|
100
|
+
|
101
|
+
def datetime_to_bytecode(datetime) #:nodoc#
|
102
|
+
# Format: 2006-09-14 02:21:10
|
103
|
+
string_to_bytecode(datetime.strftime('%Y-%m-%d %I:%M:%S'))
|
104
|
+
end
|
105
|
+
|
106
|
+
def falseclass_to_bytecode(*args) #:nodoc#
|
107
|
+
DataTypeCodes::BOOLEAN + '00'
|
108
|
+
end
|
109
|
+
|
110
|
+
def float_to_bytecode(float) #:nodoc#
|
111
|
+
hex = []
|
112
|
+
[float].pack('E').each_byte { |byte| hex << '%02X' % byte }
|
113
|
+
# Aral did this in SWX PHP, so I'm doing it here
|
114
|
+
DataTypeCodes::FLOAT + (hex[4..-1] + hex[0..3]).join
|
115
|
+
end
|
116
|
+
|
117
|
+
def integer_to_bytecode(integer) #:nodoc#
|
118
|
+
DataTypeCodes::INTEGER + integer_to_hexadecimal(integer, 4)
|
119
|
+
end
|
120
|
+
alias bignum_to_bytecode integer_to_bytecode
|
121
|
+
alias fixnum_to_bytecode integer_to_bytecode
|
122
|
+
|
123
|
+
def nilclass_to_bytecode(*args) #:nodoc#
|
124
|
+
'02'
|
125
|
+
end
|
126
|
+
|
127
|
+
def string_to_bytecode(string) #:nodoc#
|
128
|
+
DataTypeCodes::STRING + string.unpack('H*').join.upcase + NULL_TERMINATOR
|
129
|
+
end
|
130
|
+
|
131
|
+
def symbol_to_bytecode(symbol)
|
132
|
+
string_to_bytecode(symbol.to_s)
|
133
|
+
end
|
134
|
+
|
135
|
+
def trueclass_to_bytecode(*args) #:nodoc#
|
136
|
+
DataTypeCodes::BOOLEAN + '01'
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# = Core Extensions
|
2
|
+
class Array
|
3
|
+
def begins_with?(string)
|
4
|
+
self.join.begins_with?(string)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Object
|
9
|
+
# Taken from ActiveRecord
|
10
|
+
def instance_values
|
11
|
+
instance_variables.inject({}) do |values, name|
|
12
|
+
values[name[1..-1]] = instance_variable_get(name)
|
13
|
+
values
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class String
|
19
|
+
def begins_with?(string)
|
20
|
+
self[0..string.length-1] == string
|
21
|
+
end
|
22
|
+
|
23
|
+
def concludes_with?(*strings)
|
24
|
+
strings.any? { |string| self[-(string.length)..-1] == string }
|
25
|
+
end
|
26
|
+
|
27
|
+
def hex_to_ascii
|
28
|
+
hex = self.gsub(' ', '')
|
29
|
+
[hex].pack('H*')
|
30
|
+
end
|
31
|
+
|
32
|
+
# Taken from Rails' Inflector module
|
33
|
+
def underscore
|
34
|
+
self.to_s.gsub(/::/, '/').
|
35
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
36
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
37
|
+
tr("-", "_").
|
38
|
+
downcase
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'core_extensions'
|
2
|
+
|
3
|
+
module DataTypeCodes
|
4
|
+
BOOLEAN = '05'
|
5
|
+
FLOAT = '06'
|
6
|
+
INTEGER = '07'
|
7
|
+
NULL = '02'
|
8
|
+
STRING = '00'
|
9
|
+
end
|
10
|
+
|
11
|
+
module ActionCodes
|
12
|
+
DO_ACTION = '3F03'
|
13
|
+
END_SWF = '0000'
|
14
|
+
INIT_ARRAY = '42'
|
15
|
+
INIT_OBJECT = '43'
|
16
|
+
PUSH = '96LLLL'
|
17
|
+
SET_VARIABLE = '1D' # 00
|
18
|
+
SHOW_FRAME = '4000'
|
19
|
+
end
|
20
|
+
|
21
|
+
module HelperMethods
|
22
|
+
module ClassMethods
|
23
|
+
def calculate_bytecode_length(bytecode)
|
24
|
+
# Calculate bytecode length *without* counting the init array or init object action
|
25
|
+
bytecode.join.sub(/^(#{ActionCodes::INIT_ARRAY}|#{ActionCodes::INIT_OBJECT})/, '').length/2
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_push_statement(bytecode)
|
29
|
+
unpushed_data = []
|
30
|
+
# Iterate over the bytecode array in reverse and add all of the unpushed
|
31
|
+
# data to 'unpushed_data'
|
32
|
+
bytecode.reverse_each do |bytecode_chunk|
|
33
|
+
if bytecode_chunk.begins_with?('96') || bytecode_chunk.begins_with?('1D') then break else unpushed_data << bytecode_chunk end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Since we iterated over the bytecode in reverse, unpushed_data is reversed, so
|
37
|
+
# reverse it again before passing it into #calculate_bytecode_length
|
38
|
+
bytecode_length = calculate_bytecode_length(unpushed_data.reverse)
|
39
|
+
|
40
|
+
# TODO: Replace with constant
|
41
|
+
'96' + integer_to_hexadecimal(bytecode_length, 2)
|
42
|
+
end
|
43
|
+
|
44
|
+
def integer_to_hexadecimal(integer, number_of_bytes=1)
|
45
|
+
make_little_endian("%0#{number_of_bytes*2}X" % integer)
|
46
|
+
end
|
47
|
+
|
48
|
+
def make_little_endian(hex_string)
|
49
|
+
# split into an array of string pairs
|
50
|
+
# reverse the array and join back into a string
|
51
|
+
pad_string_to_byte_boundary(hex_string).scan(/../).reverse.join
|
52
|
+
end
|
53
|
+
|
54
|
+
def pad_string_to_byte_boundary(hex_string)
|
55
|
+
hex_string += '0' if hex_string.length % 2 == 1
|
56
|
+
hex_string
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns a string with the length of the passed hex string in bytes
|
60
|
+
# padded to display in 'number_of_bytes' bytes.
|
61
|
+
def string_length_in_bytes_hex(string, number_of_bytes)
|
62
|
+
# Divide length in chars by 2 to get length in bytes
|
63
|
+
bytecode_length_in_hex = integer_to_hexadecimal(string.length/2, number_of_bytes)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
extend ClassMethods
|
68
|
+
|
69
|
+
def self.included(receiver)
|
70
|
+
receiver.extend(ClassMethods)
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'swx_assembler'
|
2
|
+
require 'swx_gateway'
|
3
|
+
|
4
|
+
ActionController::Base.class_eval do
|
5
|
+
def render_with_swx(options = nil, &block)
|
6
|
+
if options.is_a?(Hash) && options.keys.include?(:swx)
|
7
|
+
swf_bytecode = SwxAssembler.write_swf(
|
8
|
+
options[:swx],
|
9
|
+
params[:debug],
|
10
|
+
SwxGateway.swx_config['compression_level'],
|
11
|
+
params[:url],
|
12
|
+
SwxGateway.swx_config['allow_domain']
|
13
|
+
)
|
14
|
+
send_data(swf_bytecode, :type => 'application/swf', :filename => 'data.swf')
|
15
|
+
else
|
16
|
+
render_without_swx(options, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
alias_method_chain :render, :swx
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Service Path: Put your service classes in this folder.
|
2
|
+
# Is relative to RAILS_ROOT
|
3
|
+
services_path: app/services
|
4
|
+
|
5
|
+
# Allow any domain to access the data SWFs from the SWX gateway?
|
6
|
+
# If you set this to false, you will only be able to call the
|
7
|
+
# SWX gateway from SWFs that reside on exactly the same domain
|
8
|
+
# (not even subdomains are allowed.)
|
9
|
+
#
|
10
|
+
# TODO: Allow user to specify a domain (or domains) to allow by
|
11
|
+
# providing URLs.
|
12
|
+
allow_domain: true
|
13
|
+
|
14
|
+
# How much to compress the returned SWF files. Values range from
|
15
|
+
# 0 (no compression) to 9 (maximum compression). The default
|
16
|
+
# compression level is 4. Higher levels of compression may result in
|
17
|
+
# smaller SWF files but will take longer to process.
|
18
|
+
compression_level: 4
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class TestTO
|
2
|
+
def initialize
|
3
|
+
@prop1 = 'A string'
|
4
|
+
@prop2 = 42
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
###
|
9
|
+
# Data type test service. Run the TestDataTypes.fla to run through all these tests using SWX RPC.
|
10
|
+
#
|
11
|
+
# @package default
|
12
|
+
# @author Aral Balkan
|
13
|
+
###
|
14
|
+
class TestDataTypes
|
15
|
+
###
|
16
|
+
# Returns the boolean true.
|
17
|
+
# @author Aral Balkan
|
18
|
+
###
|
19
|
+
def test_true
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
###
|
24
|
+
# Returns the boolean false.
|
25
|
+
# @author Aral Balkan
|
26
|
+
###
|
27
|
+
def test_false
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
###
|
32
|
+
# Returns the array ['It', 'works']
|
33
|
+
# @author Aral Balkan
|
34
|
+
###
|
35
|
+
def test_array
|
36
|
+
%w(It works)
|
37
|
+
end
|
38
|
+
|
39
|
+
###
|
40
|
+
# Returns the nested array ['It', ['also'], 'works]
|
41
|
+
# @author Aral Balkan
|
42
|
+
###
|
43
|
+
def test_nested_array
|
44
|
+
['It', ['also'], 'works']
|
45
|
+
end
|
46
|
+
|
47
|
+
###
|
48
|
+
# Returns the integer 42.
|
49
|
+
# @author Aral Balkan
|
50
|
+
###
|
51
|
+
def test_integer
|
52
|
+
42
|
53
|
+
end
|
54
|
+
|
55
|
+
###
|
56
|
+
# Returns the float 42.12345.
|
57
|
+
# @author Aral Balkan
|
58
|
+
###
|
59
|
+
def test_float
|
60
|
+
42.12345
|
61
|
+
end
|
62
|
+
|
63
|
+
###
|
64
|
+
# Returns the string "It works!"
|
65
|
+
# @author Aral Balkan
|
66
|
+
###
|
67
|
+
def test_string
|
68
|
+
"It works!"
|
69
|
+
end
|
70
|
+
|
71
|
+
###
|
72
|
+
# Returns the associative array ['it' => 'works', 'number' => 42]
|
73
|
+
# @author Aral Balkan
|
74
|
+
###
|
75
|
+
def test_associative_array
|
76
|
+
{'it' => 'works', 'number' => 42}
|
77
|
+
end
|
78
|
+
|
79
|
+
###
|
80
|
+
# Returns an instance of the TestTO class with properties prop1: "A string" and prop2: 42.
|
81
|
+
# @author Aral Balkan
|
82
|
+
###
|
83
|
+
def test_object
|
84
|
+
TestTO.new
|
85
|
+
end
|
86
|
+
|
87
|
+
###
|
88
|
+
# Returns null.
|
89
|
+
# @author Aral Balkan
|
90
|
+
###
|
91
|
+
def test_null
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'bytecode_converter'
|
2
|
+
require 'helper_module'
|
3
|
+
require 'uri/common.rb'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
class SwxAssembler
|
7
|
+
include HelperMethods
|
8
|
+
|
9
|
+
# Header - FCS (uncompressed), version Flash 6
|
10
|
+
UNCOMPRESSED_SWF = '46'
|
11
|
+
COMPRESSED_SWF = '43'
|
12
|
+
HEADER = '575306LLLLLLLL300A00A0000101004302FFFFFF'
|
13
|
+
|
14
|
+
# Misc
|
15
|
+
NULL_TERMINATOR = '00'
|
16
|
+
|
17
|
+
# Allow domain (*)
|
18
|
+
ALLOW_DOMAIN = '960900005F706172656E74001C960600005F75726C004E960D0007010000000053797374656D001C960A00007365637572697479004E960D0000616C6C6F77446F6D61696E005217'
|
19
|
+
SYSTEM_ALLOW_DOMAIN = '07010000000053797374656D001C960A00007365637572697479004E960D0000616C6C6F77446F6D61696E005217'
|
20
|
+
|
21
|
+
# Debug SWX bytecode. Creates a local connection to the SWX Debugger front-end.)
|
22
|
+
DEBUG_START = '883C000700726573756C74006C63004C6F63616C436F6E6E656374696F6E005F737778446562756767657200636F6E6E6563740064656275670073656E6400'
|
23
|
+
DEBUG_END = '960D0008010600000000000000000802403C9609000803070100000008011C9602000804521796020008001C960500070100000042960B0008050803070300000008011C96020008065217'
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def allow_domain_bytecode(allow_domain_url = '')
|
27
|
+
if (allow_domain_url.nil? || allow_domain_url.empty?)
|
28
|
+
# No URL passed -- possibly called by legacy code, use the old _parent._url version.
|
29
|
+
# error_log('[SWX] INFO: No URL passed from client. Defaulting to old behavior. You must call System.security.allowDomain on the dataHolder for cross domain data loading to work.');
|
30
|
+
ALLOW_DOMAIN
|
31
|
+
else
|
32
|
+
# Firefox/Flash (at least, and tested only on a Mac), sends
|
33
|
+
# file:/// (three slashses) in the URI and that fails the validation
|
34
|
+
# so replacing that with two slashes instead.
|
35
|
+
allow_domain_url.gsub!('///', '//')
|
36
|
+
# URL is passed, write that into the returned code
|
37
|
+
allow_domain_bytecode = BytecodeConverter.convert(URI.unescape(allow_domain_url))
|
38
|
+
|
39
|
+
# The -13 is to accomodate the other elements being pushed to the
|
40
|
+
# stack in the hard-coded part of the bytecode.
|
41
|
+
allow_domain_bytecode_length_dec = allow_domain_bytecode.length/2 + 13
|
42
|
+
|
43
|
+
allow_domain_bytecode_length = integer_to_hexadecimal(allow_domain_bytecode_length_dec, 2);
|
44
|
+
allow_domain_bytecode = '96' + allow_domain_bytecode_length + allow_domain_bytecode + SYSTEM_ALLOW_DOMAIN;
|
45
|
+
allow_domain_bytecode
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def compress_swx_file(swx_file, compression_level)
|
50
|
+
# The first eight bytes of a compressed SWF file are left uncompressed
|
51
|
+
swx_file.slice!(0...8) + Zlib::Deflate.deflate(swx_file, compression_level)
|
52
|
+
end
|
53
|
+
|
54
|
+
def generate_data_bytecode(data)
|
55
|
+
data_bytecode = []
|
56
|
+
|
57
|
+
# Add a flag to the beginning of the bytecode that tells Flash we're setting a variable (result)
|
58
|
+
data_bytecode.push ActionCodes::SET_VARIABLE
|
59
|
+
|
60
|
+
# Convert the data (payload) to bytecode
|
61
|
+
data_bytecode.push BytecodeConverter.convert(data)
|
62
|
+
|
63
|
+
# Generate a push tag if the data was not an Array or a Hash
|
64
|
+
data_bytecode.push generate_push_statement(data_bytecode) unless data.is_a?(Array) || data.is_a?(Hash)
|
65
|
+
|
66
|
+
# Add the 'result' variable name -- either
|
67
|
+
# using the constant table if in debug mode
|
68
|
+
# or as a regular string otherwise
|
69
|
+
if @debug
|
70
|
+
data_bytecode.push '9602000800'
|
71
|
+
else
|
72
|
+
data_bytecode.push '96080000726573756C7400'
|
73
|
+
end
|
74
|
+
|
75
|
+
# (SWF bytecode is stored in reverse, so we reverse it here)
|
76
|
+
data_bytecode.reverse.join
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_swx_bytecode(data)
|
80
|
+
# Create the DoAction tag
|
81
|
+
do_action_block = []
|
82
|
+
|
83
|
+
# Wrap the data bytecode in debug flags if debugging is turned on
|
84
|
+
do_action_block.push DEBUG_START if @debug
|
85
|
+
|
86
|
+
# Generate bytecode for the data (payload)
|
87
|
+
do_action_block.push generate_data_bytecode(data)
|
88
|
+
|
89
|
+
# Allow domain? If so add allow domain statement to the SWF
|
90
|
+
if (@allow_domain)
|
91
|
+
do_action_block.push allow_domain_bytecode(@allow_domain_url)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Wrap the data bytecode in debug flags if debugging is turned on
|
95
|
+
do_action_block.push DEBUG_END if @debug
|
96
|
+
|
97
|
+
# Calculate the size of the do_action block
|
98
|
+
do_action_block_size_in_bytes = string_length_in_bytes_hex(do_action_block.join, 4)
|
99
|
+
# Add the appropriate flags to the do_action block and concat the finished do_action block into a string
|
100
|
+
do_action_block_string = ActionCodes::DO_ACTION + do_action_block_size_in_bytes + do_action_block.join
|
101
|
+
|
102
|
+
# Create the rest of the SWF
|
103
|
+
header_type = if @compression_level > 0 then COMPRESSED_SWF else UNCOMPRESSED_SWF end
|
104
|
+
|
105
|
+
swf = header_type + HEADER + do_action_block_string + ActionCodes::SHOW_FRAME + ActionCodes::END_SWF
|
106
|
+
swf_size_in_bytes = string_length_in_bytes_hex(swf, 4)
|
107
|
+
|
108
|
+
# Replace length placeholder (from HEADER constant) with actual bytecode length
|
109
|
+
swf.sub('LLLLLLLL', swf_size_in_bytes)
|
110
|
+
end
|
111
|
+
|
112
|
+
def write_swf(data, debug=false, compression_level=4, allow_domain_url='', allow_domain=true)
|
113
|
+
# Set up SwfAssembler state
|
114
|
+
@debug = debug
|
115
|
+
@compression_level = compression_level
|
116
|
+
@allow_domain_url = allow_domain_url
|
117
|
+
@allow_domain = allow_domain
|
118
|
+
|
119
|
+
swx_bytecode = generate_swx_bytecode(data)
|
120
|
+
|
121
|
+
# Convert the bytecode string to ASCII file format
|
122
|
+
swx_file = swx_bytecode.hex_to_ascii
|
123
|
+
|
124
|
+
# Compress the file if compression is turned on
|
125
|
+
swx_file = compress_swx_file(swx_file, compression_level) if compression_level > 0
|
126
|
+
|
127
|
+
# ====================================
|
128
|
+
# = TODO: Remove this before release =
|
129
|
+
# ====================================
|
130
|
+
# Write the file (for manual 'loadMovie' testing in Flash)
|
131
|
+
# File.open('/Users/Jed/Development/Libraries/rSWX/testing/flash/rswx_data.swx', 'w+') do |file|
|
132
|
+
# file << swx_file
|
133
|
+
# end
|
134
|
+
|
135
|
+
swx_file
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|