Permalink
Browse files

Initial commit.

  • Loading branch information...
0 parents commit ac13c849b7937129f312f8550606134126674c0a @whitequark committed Feb 4, 2012
@@ -0,0 +1,5 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
+*.swf
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in furnace-swf.gemspec
+gemspec
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
@@ -0,0 +1,96 @@
+#!/usr/bin/env ruby
+
+require "rubygems"
+require "bundler/setup"
+
+$: << File.join(File.dirname(__FILE__), '..', 'lib')
+
+require "trollop"
+require "furnace-swf"
+
+include Furnace
+
+SUBCOMMANDS = %w(abclist abcextract abcreplace)
+
+opts = Trollop::options do
+ version "furnace-swf #{SWF::VERSION}"
+ banner <<-EOS
+ furnace-swf is a processing tool which operates on Flash SWF files.
+
+ Possible subcommands: #{SUBCOMMANDS.join ' '}
+ Try #{__FILE__} subcommand --help.
+
+ Usage: #{__FILE__} [options] <subcommand>
+EOS
+
+ opt :input, "SWF input file", :type => :io
+ opt :trace, "Trace reading", :default => false
+
+ stop_on SUBCOMMANDS
+end
+
+subcommand = ARGV.shift
+subopts = case subcommand
+when 'abclist'
+ Trollop::options do
+ end
+
+when 'abcextract'
+ Trollop::options do
+ opt :name, "ABC tag name", :type => :string, :required => true
+ opt :output, "ABC output file", :type => :string, :required => true
+ end
+
+when 'abcreplace'
+ Trollop::options do
+ opt :name, "ABC tag name", :type => :string, :required => true
+ opt :input, "ABC input file", :type => :io, :required => true
+ opt :output, "Combined SWF output file", :type => :string, :required => true
+ end
+end
+
+Trollop::die "Option --input is required" unless opts[:input]
+
+swf = nil
+File.open(opts[:input]) do |file|
+ swf = SWF::File.new
+
+ if opts[:trace]
+ BinData::trace_reading do
+ swf.read(file)
+ end
+ else
+ swf.read(file)
+ end
+end
+
+case subcommand
+when 'abclist'
+ puts "ABC tags:"
+
+ swf.stream.tags(SWF::DoABCTag).each do |tag|
+ puts " #{tag.name.inspect}: #{tag.bytecode.length} byte(s)"
+ end
+
+when 'abcextract'
+ if tag = swf.stream.tags(SWF::DoABCTag).find { |tag| tag.name == subopts[:name] }
+ File.open(subopts[:output], 'w') do |f|
+ f.write tag.bytecode
+ end
+ else
+ puts "Tag #{subopts[:name].inspect} was not found"
+ exit 1
+ end
+
+when 'abcreplace'
+ if tag = swf.stream.tags(SWF::DoABCTag).find { |tag| tag.name == subopts[:name] }
+ tag.bytecode = subopts[:input].read
+
+ File.open(subopts[:output], 'w') do |f|
+ swf.write f
+ end
+ else
+ puts "Tag #{subopts[:name].inspect} was not found"
+ exit 1
+ end
+end
@@ -0,0 +1,22 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+
+Gem::Specification.new do |s|
+ s.name = "furnace-swf"
+ s.version = "0.0.1"
+ s.authors = ["Peter Zotov", "Sergey Gridassov"]
+ s.email = ["whitequark@whitequark.org", "grindars@gmail.com"]
+ s.homepage = "http://github.com/whitequark/furnace-swf"
+ s.summary = %q{Flash SWF file reader and writer}
+ s.description = %q{furnace-swf allows one to load, modify and write back } <<
+ %q{Flash SWF files. It can be used with furnace-avm2 in } <<
+ %q{order to achieve impressive results.}
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+s.add_runtime_dependency "bindata"
+ s.add_runtime_dependency "trollop"
+end
@@ -0,0 +1,8 @@
+module Furnace
+ module SWF
+ end
+end
+
+require "furnace-swf/version"
+
+require "furnace-swf/swf"
@@ -0,0 +1,17 @@
+require "bindata"
+
+require_relative "swf/sbit"
+require_relative "swf/ubit"
+
+require_relative "swf/rect"
+
+require_relative "swf/tag_wrapper"
+require_relative "swf/tag"
+
+Dir[File.join(File.dirname(__FILE__), 'swf', 'tags', '*.rb')].each do |tag|
+ require tag
+end
+
+require_relative "swf/header"
+require_relative "swf/stream"
+require_relative "swf/file"
@@ -0,0 +1,40 @@
+require 'zlib'
+
+module Furnace::SWF
+ class File < BinData::Record
+ header :header
+ stream :stream
+
+ def do_read(io)
+ instantiate_all_objs
+
+ header = find_obj_for_name(:header)
+ stream = find_obj_for_name(:stream)
+
+ header.do_read(io)
+
+ case header.signature
+ when 'FWS'
+ stream.do_read io
+ when 'CWS'
+ uncompressed = Zlib.inflate(io.read_all_bytes)
+ stream.do_read BinData::IO.new(StringIO.new(uncompressed))
+ else
+ raise ArgumentError, "invalid signature"
+ end
+ end
+
+ def do_write(io)
+ instantiate_all_objs
+
+ header = find_obj_for_name(:header)
+ stream = find_obj_for_name(:stream)
+
+ header.signature = 'FWS'
+ header.file_length = header.num_bytes + stream.real_num_bytes
+
+ header.do_write(io)
+ stream.do_write(io)
+ end
+ end
+end
@@ -0,0 +1,9 @@
+module Furnace::SWF
+ class Header < BinData::Record
+ endian :little
+
+ string :signature, :length => 3
+ uint8 :version
+ uint32 :file_length
+ end
+end
@@ -0,0 +1,9 @@
+module Furnace::SWF
+ class Rect < BinData::Record
+ bit5 :num_bits
+ sbit :x_min, :length => :num_bits
+ sbit :x_max, :length => :num_bits
+ sbit :y_min, :length => :num_bits
+ sbit :y_max, :length => :num_bits
+ end
+end
@@ -0,0 +1,21 @@
+module Furnace::SWF
+ class Sbit < BinData::BasePrimitive
+ mandatory_parameter :length
+
+ def do_write(io)
+ io.writebits(_value, eval_parameter(:length), :big)
+ end
+
+ def do_num_bytes
+ eval_parameter(:length) / 8.0
+ end
+
+ def read_and_return_value(io)
+ io.readbits(eval_parameter(:length), :big)
+ end
+
+ def sensible_default
+ 0
+ end
+ end
+end
@@ -0,0 +1,25 @@
+module Furnace::SWF
+ class Stream < BinData::Record
+ endian :little
+
+ rect :frame_size
+ uint8 :frame_rate_lo
+ uint8 :frame_rate_hi
+ uint16 :frame_count
+
+ array :tag_wrappers, :type => :tag_wrapper, :read_until => :eof
+
+ def frame_rate
+ BigDecimal.new(frame_rate_hi) +
+ BigDecimal.new(frame_rate_lo) / 100
+ end
+
+ def tags(type)
+ tag_wrappers.select { |tw| tw.content.is_a? type }.map(&:content)
+ end
+
+ def real_num_bytes
+ num_bytes + tag_wrappers.map(&:content_size).reduce(0, :+)
+ end
+ end
+end
@@ -0,0 +1,34 @@
+module Furnace::SWF
+ class Tag < BinData::Record
+ mandatory_parameter :stream
+
+ endian :little
+
+ REGISTRY = {}
+
+ def self.exists_for?(type)
+ REGISTRY.has_key? type
+ end
+
+ def self.instantiate(type, parent, stream)
+ REGISTRY[type].new({ stream: stream }, parent)
+ end
+
+ # DSL
+
+ class << self
+ def type(id)
+ Tag::REGISTRY[id] = self
+
+ define_method(:type) do
+ id
+ end
+ end
+ end
+
+ def initialize_instance
+ super
+ @stream = @params[:stream]
+ end
+ end
+end
@@ -0,0 +1,85 @@
+module Furnace::SWF
+ class TagWrapper < BinData::Record
+ endian :little
+
+ uint16 :type_and_length
+ int32 :long_length, :onlyif => lambda { short_length == 0x3f }
+
+ attr_accessor :content
+
+ # Accessors for braindead format.
+
+ def type
+ type_and_length >> 6
+ end
+
+ def type=(value)
+ self.type_and_length = (type_and_length & 0x3f) | (value << 6)
+ end
+
+ def short_length
+ type_and_length & 0x3f
+ end
+
+ def short_length=(value)
+ self.type_and_length = (type_and_length & 0xfffc) | (value & 0x3f)
+ end
+
+ def real_length
+ short_length == 0x3f ? long_length : short_length
+ end
+
+ def real_length=(value)
+ if value > 0x3f
+ self.short_length = 0x3f
+ self.long_length = value
+ else
+ self.short_length = value
+ self.long_length = 0
+ end
+ end
+
+ # Nested reads/writes.
+
+ def do_read(io)
+ super
+
+ orig_offset = io.offset
+
+ if Tag.exists_for?(type)
+ @content = Tag.instantiate(type, self, parent)
+ @content.do_read io
+
+ if io.offset - orig_offset != real_length
+ raise "Invalid tag #{@content}"
+ end
+ else
+ @content = io.readbytes(real_length)
+ end
+ end
+
+ def do_write(io)
+ super
+
+ if Tag.exists_for?(type)
+ @content.do_write io
+ else
+ io.writebytes @content
+ end
+ end
+
+ def do_num_bytes
+ self.real_length = content_size
+
+ super
+ end
+
+ def content_size
+ if @content.is_a? Tag
+ @content.num_bytes
+ else
+ @content.length
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit ac13c84

Please sign in to comment.