├── .bundle └── config ├── .gitignore ├── COPYING ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── bin ├── swf_as_var_replace ├── swf_dump ├── swf_jpeg_replace ├── swf_lossless_replace └── swf_sprite_replace ├── lib ├── swf_ruby.rb └── swf_ruby │ ├── do_action_dumper.rb │ ├── replace_target.rb │ ├── sprite_dumper.rb │ ├── swf.rb │ ├── swf │ ├── action_push.rb │ ├── action_record.rb │ ├── bits_lossless2.rb │ ├── cxformwithalpha.rb │ ├── fillstyle.rb │ ├── focalgradient.rb │ ├── gradient.rb │ ├── gradrecord.rb │ ├── header.rb │ ├── matrix.rb │ ├── rectangle.rb │ ├── rgb.rb │ ├── rgba.rb │ ├── shapewithstyle.rb │ ├── swf_string.rb │ └── tag.rb │ ├── swf_dumper.rb │ ├── swf_tamperer.rb │ └── version.rb ├── samples ├── bg.jpg ├── icon.gif └── sample.swf └── swf_ruby.gemspec /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_DISABLE_SHARED_GEMS: "1" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | *.swp 4 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :gemcutter 2 | 3 | # Specify your gem's dependencies in swf_ruby.gemspec 4 | gemspec -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | swf_ruby (0.0.1) 5 | bundler (>= 1.0.0.rc.5) 6 | rmagick (>= 2.13.0) 7 | 8 | GEM 9 | remote: http://rubygems.org/ 10 | specs: 11 | rmagick (2.13.1) 12 | 13 | PLATFORMS 14 | ruby 15 | 16 | DEPENDENCIES 17 | bundler (>= 1.0.0.rc.5) 18 | rmagick (>= 2.13.0) 19 | swf_ruby! 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SwfRuby 2 | ======= 3 | 4 | [![endorse](http://api.coderwall.com/tmtysk/endorsecount.png)](http://coderwall.com/tmtysk) 5 | 6 | SwfRuby is a utilities to dump or manipulate a SWF with Ruby. 7 | 8 | * features 9 | * SwfRuby::SwfDumper 10 | * Analysing the structure of specified SWF. 11 | * Used from 'swf_dump' command. 12 | * SwfRuby::SwfTamperer 13 | * Manipulating(replagcing) resources in SWF. 14 | * Used from 'swf_jpeg_replace', 'swf_lossless_replace', 'swf_as_var_replace' and 'swf_sprite_replace' command. 15 | * compatible on ruby-1.8.7 and ruby-1.9.3. 16 | 17 | Dependencies 18 | ============ 19 | 20 | * RMagick >= 2.13.0 (ImageMagick >= 6.5.7) 21 | 22 | Installation 23 | ============ 24 | 25 | $ gem install swf_ruby 26 | 27 | or 28 | 29 | $ git clone git://github.com/tmtysk/swf_ruby.git 30 | $ cd swf_ruby 31 | $ rake install 32 | 33 | Command-Line Tool Usage 34 | ======================= 35 | 36 | Analysing the structure of SWF 37 | ------------------------------ 38 | 39 | $ swf_dump samples/sample.swf 40 | SetBackgroundColor, offset: 20, length: 5 41 | DefineFont2, offset: 25, length: 34 42 | DefineEditText, offset: 59, length: 50 43 | PlaceObject2, offset: 109, length: 11 44 | DefineBitsLossless2, offset: 120, length: 178 45 | DefineShape, offset: 298, length: 55 46 | : 47 | DefineBitsJPEG2, offset: 623, length: 10986 48 | : 49 | 50 | Replacing Jpeg in SWF 51 | --------------------- 52 | 53 | $ swf_jpeg_replace samples/sample.swf 623 samples/bg.jpg > samples/sample2.swf 54 | # <623> is offset to DefineBitsJPEG2 resource getting by 'swf_dump'. 55 | 56 | Replacing GIF/PNG in SWF 57 | ------------------------ 58 | 59 | $ swf_lossless_replace samples/sample.swf 120 samples/icon.gif > samples/sample3.swf 60 | # <120> is offset to DefineBitsLossless2 resource getting by 'swf_dump'. 61 | 62 | Replacing ActionScript Variable in SWF 63 | -------------------------------------- 64 | 65 | $ swf_as_var_replace foo.swf bar piyo > foo2.swf 66 | # is variable name. is new value to the variable. 67 | 68 | Replacing Sprite(internal movieclip) in SWF 69 | ------------------------------------------- 70 | 71 | $ swf_sprite_replace foo.swf bar piyo.swf > foo2.swf 72 | # is instance variable name of movieclip. is new movieclip file to replace. 73 | 74 | Thanks 75 | ====== 76 | 77 | * Sample GIF image is provided by ICHIGO-APORO. 78 | 79 | Copyright (c) 2011 tmtysk. 80 | released under the GNU GENERAL PUBLIC LICENSE Version 2. 81 | See COPYING for details. 82 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks -------------------------------------------------------------------------------- /bin/swf_as_var_replace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 3 | # SWF中のAS変数の値(foo = var; な箇所に限る)を差し替える. 4 | require 'swf_ruby' 5 | 6 | if ARGV.size != 3 7 | print "Specify target swf path, AS var name, and new value for arguments.\n" 8 | exit 9 | end 10 | 11 | swf_dumper = SwfRuby::SwfDumper.new 12 | swf_dumper.open(ARGV[0]) 13 | 14 | repl_targets = SwfRuby::AsVarReplaceTarget.build_by_var_name(swf_dumper, ARGV[1]).collect do |t| 15 | t.str = ARGV[2].dup 16 | t 17 | end 18 | 19 | st = SwfRuby::SwfTamperer.new 20 | 21 | print st.replace(swf_dumper.swf.dup, repl_targets) 22 | -------------------------------------------------------------------------------- /bin/swf_dump: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 3 | # SWF構造をダンプし、タグ群情報を出力する. DefineSpriteとDoActionに対しては、さらに潜ってダンプする. 4 | 5 | require 'swf_ruby' 6 | 7 | if ARGV.size != 1 8 | print "Specify swf file path to dump for argument.\n" 9 | exit 10 | end 11 | 12 | swf = SwfRuby::SwfDumper.new 13 | swf.open(ARGV[0]) 14 | swf.tags.each_with_index do |tag, i| 15 | character_id = tag.character_id ? "character_id: #{tag.character_id}, " : "" 16 | refer_character_id = tag.refer_character_id ? "refer_character_id: #{tag.refer_character_id}, " : "" 17 | refer_character_inst_name = tag.refer_character_inst_name ? "refer_character_inst_name: #{tag.refer_character_inst_name}, " : "" 18 | refer_bitmap_id = "" 19 | tag.refer_bitmap_offsets_to_ids.each do |offset, bitmap_id| 20 | refer_bitmap_id += "bitmap_id: #{bitmap_id}(offset: #{offset}), " 21 | end if tag.refer_bitmap_offsets_to_ids 22 | print "#{SwfRuby::Swf::TAG_TYPE[tag.code]}, offset: #{swf.tags_addresses[i]}, #{character_id}#{refer_character_id}#{refer_character_inst_name}#{refer_bitmap_id}length: #{tag.length}\n" 23 | if tag.code == 39 24 | # DefineSprite 25 | sd = SwfRuby::SpriteDumper.new 26 | sd.dump(tag) 27 | print " Sprite ID: #{sd.sprite_id}, Frame Count: #{sd.frame_count}\n" 28 | sd.tags.each_with_index do |tag2, k| 29 | character_id = tag2.character_id ? "character_id: #{tag2.character_id}, " : "" 30 | refer_character_id = tag2.refer_character_id ? "refer_character_id: #{tag2.refer_character_id}, " : "" 31 | refer_character_inst_name = tag2.refer_character_inst_name ? "refer_character_inst_name: #{tag2.refer_character_inst_name}, " : "" 32 | print " #{SwfRuby::Swf::TAG_TYPE[tag2.code]}, offset: #{sd.tags_addresses[k]}, #{character_id}#{refer_character_id}#{refer_character_inst_name}length: #{tag2.length}\n" 33 | if tag2.code == 12 34 | # DoAction 35 | dad = SwfRuby::DoActionDumper.new 36 | dad.dump(swf.swf[swf.tags_addresses[i] + sd.tags_addresses[k], tag2.length]) 37 | dad.actions.each_with_index do |ar, l| 38 | print " #{SwfRuby::Swf::ACTION_RECORDS[ar.code]}, offset: #{dad.actions_addresses[l]}, length: #{ar.length}\n" 39 | if ar.code == 150 40 | # ActionPush 41 | ap = SwfRuby::Swf::ActionPush.new(ar) 42 | print " type: #{ap.data_type}, offset: #{dad.actions_addresses[l]}, data: #{ap.data}\n" 43 | end 44 | end 45 | end 46 | end 47 | end 48 | if tag.code == 12 49 | # DoAction 50 | dad = SwfRuby::DoActionDumper.new 51 | dad.dump(swf.swf[swf.tags_addresses[i], tag.length]) 52 | dad.actions.each_with_index do |ar, j| 53 | print " #{SwfRuby::Swf::ACTION_RECORDS[ar.code]}, offset: #{dad.actions_addresses[j]}, length: #{ar.length}\n" 54 | if ar.code == 150 55 | # ActionPush 56 | ap = SwfRuby::Swf::ActionPush.new(ar) 57 | print " type: #{ap.data_type}, offset: #{dad.actions_addresses[j]}, data: #{ap.data}\n" 58 | end 59 | end 60 | end 61 | end 62 | 63 | print "\n" 64 | -------------------------------------------------------------------------------- /bin/swf_jpeg_replace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 3 | # SWF中のJpeg(DefineBitsJpeg2)イメージを差し替える. 4 | require 'swf_ruby' 5 | 6 | if ARGV.size != 3 7 | print "Specify target swf path, offset to jpeg, new jpeg path for arguments.\n" 8 | exit 9 | end 10 | 11 | swf = File.open(ARGV[0], "rb").read 12 | st = SwfRuby::SwfTamperer.new 13 | jpeg = File.open(ARGV[2], "rb").read 14 | repl_targets = [ 15 | SwfRuby::Jpeg2ReplaceTarget.new(ARGV[1].to_i, jpeg) 16 | ] 17 | 18 | print st.replace(swf, repl_targets) 19 | -------------------------------------------------------------------------------- /bin/swf_lossless_replace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 3 | # SWF中のLossless(DefineBitsLossless2)イメージを差し替える. 4 | require 'swf_ruby' 5 | 6 | if ARGV.size != 3 7 | print "Specify target swf path, offset to image, new image(png,gif) path for arguments.\n" 8 | exit 9 | end 10 | 11 | swf = File.open(ARGV[0], "rb").read 12 | st = SwfRuby::SwfTamperer.new 13 | image = File.open(ARGV[2], "rb").read 14 | repl_targets = [ 15 | SwfRuby::Lossless2ReplaceTarget.new(ARGV[1].to_i, image) 16 | ] 17 | 18 | print st.replace(swf, repl_targets) 19 | -------------------------------------------------------------------------------- /bin/swf_sprite_replace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 3 | # SWF中の指定したインスタンス変数名で設置されているDefineSpriteを入れ替える. 4 | require 'swf_ruby' 5 | 6 | if ARGV.size != 3 7 | print "Specify target swf path, sprite instance variable name, and swf path to embed for arguments.\n" 8 | exit 9 | end 10 | 11 | swf_dumper = SwfRuby::SwfDumper.new 12 | swf_dumper.open(ARGV[0]) 13 | sprite_swf = File.open(ARGV[2], "rb").read 14 | 15 | repl_targets = SwfRuby::SpriteReplaceTarget.build_list_by_instance_var_names(swf_dumper, { ARGV[1] => sprite_swf }) 16 | 17 | st = SwfRuby::SwfTamperer.new 18 | 19 | print st.replace(swf_dumper.swf.dup, repl_targets) 20 | -------------------------------------------------------------------------------- /lib/swf_ruby.rb: -------------------------------------------------------------------------------- 1 | require 'zlib' 2 | require 'RMagick' 3 | Dir[File.join(File.dirname(__FILE__), 'swf_ruby', '*.rb')].sort.each { |f| require f } 4 | -------------------------------------------------------------------------------- /lib/swf_ruby/do_action_dumper.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | class DoActionDumper 5 | 6 | attr_accessor :tag 7 | attr_accessor :actions 8 | attr_accessor :actions_addresses 9 | 10 | def initialize 11 | @actions = nil 12 | @actions_addresses = nil 13 | end 14 | 15 | def dump(tag) 16 | @tag = tag 17 | @tag = @tag.force_encoding("ASCII-8BIT") if @tag.respond_to? :force_encoding 18 | record_header = @tag[0, 2].unpack("v").first 19 | t = Swf::Tag.new(@tag) 20 | # tag check 21 | raise NotDoActionTagError if t.code != 12 22 | # length check 23 | raise InvalidTagLengthError if t.length != @tag.length 24 | 25 | @actions = [] 26 | @actions_addresses = [] 27 | tags_index = (t.long_header) ? 6 : 2 28 | while tags_index < t.length 29 | @actions_addresses << tags_index 30 | ar = Swf::ActionRecord.new(@tag[tags_index..-1]) 31 | tags_index += ar.length 32 | @actions << ar 33 | end 34 | end 35 | end 36 | 37 | class NotDoActionTagError < StandardError; end 38 | class InvalidTagLengthError < StandardError; end 39 | end 40 | -------------------------------------------------------------------------------- /lib/swf_ruby/replace_target.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | class ReplaceTarget 5 | attr_accessor :offset 6 | end 7 | 8 | class Jpeg2ReplaceTarget < ReplaceTarget 9 | attr_accessor :jpeg 10 | 11 | def initialize(offset, jpeg) 12 | @offset = offset 13 | @jpeg = jpeg 14 | end 15 | end 16 | 17 | class Lossless2ReplaceTarget < ReplaceTarget 18 | attr_accessor :image 19 | 20 | def initialize(offset, image) 21 | @offset = offset 22 | @image = SwfRuby::Swf::BitsLossless2.new(image) 23 | end 24 | end 25 | 26 | class SpriteReplaceTarget < ReplaceTarget 27 | attr_accessor :swf 28 | attr_accessor :frame_count 29 | attr_accessor :define_tags 30 | attr_accessor :control_tags 31 | attr_accessor :idmap 32 | attr_accessor :target_define_tags_string 33 | attr_accessor :target_control_tags_string 34 | attr_reader :target_swf_dumper 35 | 36 | def initialize(offset, swf) 37 | @offset = offset 38 | @swf = swf 39 | @target_swf_dumper = SwfDumper.new.dump(@swf) 40 | @frame_count = @target_swf_dumper.header.frame_count 41 | @define_tags = @target_swf_dumper.tags.select { |t| t.define_tag? } 42 | @control_tags = @target_swf_dumper.tags - @define_tags 43 | @idmap = { 65535 => 65535 } 44 | end 45 | 46 | def self.build_list_by_instance_var_names(swf_dumper, var_name_to_swf) 47 | from_character_id = (swf_dumper.tags.collect { |t| t.define_tag? ? t.character_id : nil }).compact.max + 1 48 | repl_targets = [] 49 | var_name_to_swf.each do |var_name, swf| 50 | repl_target, from_character_id = SwfRuby::SpriteReplaceTarget.build_by_instance_var_name(swf_dumper, var_name, swf, from_character_id) 51 | repl_targets << repl_target 52 | end 53 | repl_targets 54 | end 55 | 56 | # 指定したインスタンス変数名に対するSpriteReplaceTargetを生成する 57 | def self.build_by_instance_var_name(swf_dumper, var_name, swf, from_character_id = nil) 58 | from_character_id ||= (swf_dumper.tags.collect { |t| t.define_tag? ? t.character_id : nil }).compact.max + 1 59 | refer_character_id = nil 60 | sprite_indices = {} 61 | swf_dumper.tags.each_with_index do |t,i| 62 | if t.character_id 63 | sprite_indices[t.character_id] = i 64 | end 65 | if Swf::TAG_TYPE[t.code] == "DefineSprite" 66 | sd = SwfRuby::SpriteDumper.new 67 | sd.dump(t) 68 | sd.tags.each do |t2| 69 | if var_name == t2.refer_character_inst_name 70 | refer_character_id = t2.refer_character_id 71 | break 72 | end 73 | end 74 | else 75 | if var_name == t.refer_character_inst_name 76 | refer_character_id = t.refer_character_id 77 | end 78 | end 79 | break if refer_character_id 80 | end 81 | raise ReplaceTargetError unless refer_character_id 82 | offset = swf_dumper.tags_addresses[sprite_indices[refer_character_id]] 83 | srt = SpriteReplaceTarget.new(offset, swf) 84 | srt.target_define_tags_string, from_character_id = srt.build_define_tags_string(from_character_id) 85 | srt.target_control_tags_string = srt.build_control_tags_string 86 | [srt, from_character_id] 87 | end 88 | 89 | # 置換するSWFからCharacterIdを付け替えながらDefineタグを抽出する. 90 | # 対象のSWFにBitmapIDの参照が含まれる場合、これも合わせて付け替える. 91 | # 同時に、CharacterIdの対応付けマップを作成する. 92 | def build_define_tags_string(from_character_id) 93 | str = "" 94 | @define_tags.each do |t| 95 | if t.character_id 96 | from_character_id += 1 97 | @idmap[t.character_id] = from_character_id 98 | str << t.rawdata_with_define_character_id(@idmap, @idmap[t.character_id]) 99 | end 100 | end 101 | [str, from_character_id+1] 102 | end 103 | 104 | # DefineSpriteに埋め込むためのControl tagsのみを抽出する. 105 | # 参照先のcharacter_idを変更する必要がある場合は付け替える. 106 | def build_control_tags_string 107 | str = "" 108 | valid_control_tag_codes = [0, 1, 4, 5, 12, 18, 19, 26, 28, 43, 45, 70, 72] 109 | @control_tags.each do |t| 110 | next unless valid_control_tag_codes.include? t.code 111 | if @idmap[t.refer_character_id] 112 | str << t.rawdata_with_refer_character_id(@idmap[t.refer_character_id]) 113 | else 114 | str << t.rawdata 115 | end 116 | end 117 | str 118 | end 119 | end 120 | 121 | class AsVarReplaceTarget < ReplaceTarget 122 | attr_accessor :do_action_offset 123 | attr_accessor :parent_sprite_offset 124 | attr_reader :str 125 | 126 | def initialize(action_push_offset, do_action_offset, str, parent_sprite_offset = nil) 127 | @offset = action_push_offset 128 | @do_action_offset = do_action_offset 129 | @str = str 130 | @parent_sprite_offset = parent_sprite_offset 131 | end 132 | 133 | def str=(str) 134 | @str << str 135 | end 136 | 137 | # 指定したAS変数名に対するAsVarReplaceTargetのリストを生成する 138 | def self.build_by_var_name(swf_dumper, var_name) 139 | as_var_replace_targets = [] 140 | swf_dumper.tags.each_with_index do |t, i| 141 | if t.code == 39 142 | # DefineSprite 143 | sd = SpriteDumper.new 144 | sd.dump(t) 145 | sd.tags.each_with_index do |u, j| 146 | if u.code == 12 147 | # DoAction in DefineSprite 148 | as_var_replace_targets += AsVarReplaceTarget.generate_as_var_replace_target_by_do_action(var_name, swf_dumper, j, sd, swf_dumper.tags_addresses[i]) 149 | end 150 | end 151 | end 152 | if t.code == 12 153 | # DoAction 154 | as_var_replace_targets += AsVarReplaceTarget.generate_as_var_replace_target_by_do_action(var_name, swf_dumper, i) 155 | end 156 | end 157 | as_var_replace_targets 158 | end 159 | 160 | # 指定したインデックス(SWFまたはSpriteの先頭からカウント)にあるDoAction以下を走査し、 161 | # 指定したAS変数名の代入部分を発見し、AsVarReplaceTargetのリストを生成する. 162 | def self.generate_as_var_replace_target_by_do_action(var_name, swf_dumper, do_action_index, sprite_dumper = nil, parent_sprite_offset = nil) 163 | as_var_replace_targets = [] 164 | action_records = [] 165 | do_action_offset = 0 166 | 167 | dad = DoActionDumper.new 168 | if sprite_dumper 169 | do_action_offset = parent_sprite_offset + sprite_dumper.tags_addresses[do_action_index] 170 | dad.dump(swf_dumper.swf[do_action_offset, sprite_dumper.tags[do_action_index].length]) 171 | else 172 | do_action_offset = swf_dumper.tags_addresses[do_action_index] 173 | dad.dump(swf_dumper.swf[do_action_offset, swf_dumper.tags[do_action_index].length]) 174 | end 175 | dad.actions.each_with_index do |ar, i| 176 | # ActionPush, SetVariableの並びを検出したら変数名をチェック. 177 | action_records.shift if action_records.length > 2 178 | action_records << ar 179 | if ar.code == 29 && action_records[-2] && action_records[-2].code == 150 180 | # 直前のActionPushが複数データをpushしているかどうかチェック. 181 | ars = action_records[-2].data.split("\0").reject { |e| e.empty? } 182 | if ars[0] == var_name 183 | if ars[1] 184 | # 複数データpushなので\0\0 separatedなデータをつくる 185 | as_var_replace_targets << AsVarReplaceTarget.new( 186 | do_action_offset + dad.actions_addresses[i] - action_records[-2].length, 187 | do_action_offset, 188 | "#{var_name}\0\0", 189 | parent_sprite_offset 190 | ) 191 | end 192 | elsif action_records[-3] && action_records[-3].code == 150 && action_records[-3].data.delete("\0") == var_name 193 | # 連続ActionPush 194 | as_var_replace_targets << AsVarReplaceTarget.new( 195 | do_action_offset + dad.actions_addresses[i] - action_records[-2].length, 196 | do_action_offset, 197 | "", 198 | parent_sprite_offset 199 | ) 200 | end 201 | end 202 | end 203 | as_var_replace_targets 204 | end 205 | end 206 | 207 | # 置換対象指定エラー. 208 | class ReplaceTargetError < StandardError; end 209 | end 210 | -------------------------------------------------------------------------------- /lib/swf_ruby/sprite_dumper.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | # DefineSpriteをダンプするクラス. 5 | class SpriteDumper 6 | attr_reader :sprite_id 7 | attr_reader :frame_count 8 | attr_reader :tags 9 | attr_reader :tags_addresses 10 | 11 | # 初期化. 12 | def initialize 13 | @sprite_id = nil 14 | @frame_count = nil 15 | @tag = nil 16 | @tags_addresses = nil 17 | end 18 | 19 | # ダンプして構造をインスタンス変数に格納. 20 | def dump(tag) 21 | data = tag.data 22 | @sprite_id = data[0, 2].unpack("v").first 23 | @frame_count = data[2, 2].unpack("v").first 24 | @tags = [] 25 | @tags_addresses = [] 26 | sprite_header_length = (tag.long_header) ? 6 : 2 27 | tag_index = 4 28 | while tag_index < data.length 29 | @tags_addresses << tag_index + sprite_header_length 30 | tag = Swf::Tag.new(data[tag_index..-1]) 31 | tag_index += tag.length 32 | @tags << tag 33 | end 34 | self 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf.rb: -------------------------------------------------------------------------------- 1 | Dir[File.join(File.dirname(__FILE__), 'swf', '*.rb')].sort.each { |f| require f } 2 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/action_push.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | # ActionPushタグ. 6 | class ActionPush 7 | attr_reader :data_type 8 | attr_reader :data 9 | 10 | def initialize(action_record) 11 | raise NotActionPushError if action_record.code != 0x96 12 | ad = action_record.data 13 | @data_type = ad[0].chr.unpack("C").first 14 | @data = nil 15 | d = ad[1..-1] 16 | case @data_type 17 | when 0 18 | @data = d 19 | when 1 20 | @data = d.unpack("e") 21 | when 4,5,8 22 | @data = d.unpack("C") 23 | when 6 24 | @data = d.unpack("E") 25 | when 7 26 | @data = d.unpack("V") 27 | when 9 28 | @data = d.unpack("v") 29 | end 30 | end 31 | end 32 | 33 | class NotActionPushError < StandardError; end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/action_record.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class ActionRecord 6 | attr_reader :code 7 | attr_reader :length 8 | attr_reader :data 9 | 10 | def initialize(tag) 11 | tag.force_encoding("ASCII-8BIT") if tag.respond_to? :force_encoding 12 | # action code 13 | @code = tag[0].chr.unpack("C").first 14 | # length (including action record header) and data 15 | @length = 1 16 | @data = nil 17 | if @code >= 0x80 18 | len = tag[1,2].unpack("v").first 19 | @length += len + 2 20 | @data = tag[3,len] 21 | end 22 | end 23 | end 24 | 25 | ACTION_RECORDS = { 26 | 71=>"Add (typed)", 27 | 10=>"Add", 28 | 96=>"And", 29 | 153=>"Branch Always", 30 | 157=>"Branch If True", 31 | 158=>"Call Frame", 32 | 61=>"Call Function", 33 | 82=>"Call Method", 34 | 43=>"Cast Object", 35 | 55=>"Chr (multi-byte)", 36 | 51=>"Chr", 37 | 33=>"Concatenate Strings", 38 | 66=>"Declare Array", 39 | 136=>"Declare Dictionary", 40 | 142=>"Declare Function (V7)", 41 | 155=>"Declare Function", 42 | 65=>"Declare Local Variable", 43 | 67=>"Declare Object", 44 | 81=>"Decrement", 45 | 58=>"Delete", 46 | 59=>"Delete All", 47 | 13=>"Divide", 48 | 76=>"Duplicate", 49 | 36=>"Duplicate Sprite", 50 | 0=>"End (action)", 51 | 70=>"Enumerate", 52 | 85=>"Enumerate Object", 53 | 73=>"Equal (typed)", 54 | 14=>"Equal", 55 | 105=>"Extends", 56 | 45=>"FSCommand2", 57 | 78=>"Get Member", 58 | 34=>"Get Property", 59 | 69=>"Get Target", 60 | 52=>"Get Timer", 61 | 131=>"Get URL", 62 | 154=>"Get URL2", 63 | 28=>"Get Variable", 64 | 159=>"Goto Expression", 65 | 129=>"Goto Frame", 66 | 140=>"Goto Label", 67 | 103=>"Greater Than (typed)", 68 | 44=>"Implements", 69 | 80=>"Increment", 70 | 84=>"Instance Of", 71 | 24=>"Integral Part", 72 | 72=>"Less Than (typed)", 73 | 15=>"Less Than", 74 | 16=>"Logical And", 75 | 18=>"Logical Not", 76 | 17=>"Logical Or", 77 | 63=>"Modulo", 78 | 12=>"Multiply", 79 | 64=>"New", 80 | 83=>"New Method", 81 | 4=>"Next Frame", 82 | 74=>"Number", 83 | 97=>"Or", 84 | 54=>"Ord (multi-byte)", 85 | 50=>"Ord", 86 | 6=>"Play", 87 | 23=>"Pop", 88 | 5=>"Previous Frame", 89 | 150=>"Push Data", 90 | 48=>"Random", 91 | 37=>"Remove Sprite", 92 | 62=>"Return", 93 | 60=>"Set Local Variable", 94 | 79=>"Set Member", 95 | 35=>"Set Property", 96 | 32=>"Set Target (dynamic)", 97 | 139=>"Set Target", 98 | 29=>"Set Variable", 99 | 99=>"Shift Left", 100 | 100=>"Shift Right", 101 | 101=>"Shift Right Unsigned", 102 | 39=>"Start Drag", 103 | 7=>"Stop", 104 | 40=>"Stop Drag", 105 | 9=>"Stop Sound", 106 | 135=>"Store Register", 107 | 102=>"Strict Equal", 108 | 137=>"Strict Mode", 109 | 75=>"String", 110 | 19=>"String Equal", 111 | 104=>"String Greater Than", 112 | 49=>"String Length (multi-byte)", 113 | 20=>"String Length", 114 | 41=>"String Less Than", 115 | 53=>"SubString (multi-byte)", 116 | 21=>"SubString", 117 | 11=>"Subtract", 118 | 77=>"Swap", 119 | 42=>"Throw", 120 | 8=>"Toggle Quality", 121 | 38=>"Trace", 122 | 143=>"Try", 123 | 68=>"Type Of", 124 | 141=>"Wait For Frame (dynamic)", 125 | 138=>"Wait For Frame", 126 | 148=>"With", 127 | 98=>"XOr" 128 | } 129 | 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/bits_lossless2.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class BitsLossless2 6 | attr_accessor :format, :width, :height, :color_table_size, :zlib_bitmap_data 7 | 8 | ShiftDepth = Magick::QuantumDepth - 8 9 | MaxRGB = 2 ** Magick::QuantumDepth - 1 10 | 11 | def initialize(image_bytearray) 12 | image = Magick::Image.from_blob(image_bytearray).first 13 | data = "" 14 | data.force_encoding("ASCII-8BIT") if data.respond_to? :force_encoding 15 | @format = nil 16 | colormap = [] 17 | # creating colormap to check number of colors 18 | image.get_pixels(0, 0, image.columns, image.rows).each_with_index do |pixel,i| 19 | break if colormap.length > 256 20 | idx = colormap.index(pixel) 21 | if idx 22 | data << [idx].pack("C") 23 | else 24 | colormap << pixel 25 | data << [colormap.length-1].pack("C") 26 | end 27 | if (i+1) % image.columns == 0 28 | # padding 29 | data += [0].pack("C") * (4-image.columns&3) 30 | end 31 | end 32 | 33 | # checking image format by size of colormap 34 | if colormap.length > 256 35 | # format=5 36 | # reset and re-build data_stream without colopmap 37 | data = "" 38 | image.get_pixels(0, 0, image.columns, image.rows).each_with_index do |pixel,i| 39 | opacity = (MaxRGB-pixel.opacity) >> ShiftDepth 40 | data += [opacity].pack("C") 41 | data += [pixel.red >> ShiftDepth].pack("C") 42 | data += [pixel.green >> ShiftDepth].pack("C") 43 | data += [pixel.blue >> ShiftDepth].pack("C") 44 | end 45 | @format = 5 46 | else 47 | # format=3 48 | # added colormap before data_stream 49 | data = colormap.inject("") { |r,c| 50 | opacity = (MaxRGB-c.opacity) >> ShiftDepth 51 | if opacity == 0 52 | r += 53 | [0].pack("C") + 54 | [0].pack("C") + 55 | [0].pack("C") + 56 | [opacity].pack("C") 57 | else 58 | r += 59 | [c.red >> ShiftDepth].pack("C") + 60 | [c.green >> ShiftDepth].pack("C") + 61 | [c.blue >> ShiftDepth].pack("C") + 62 | [opacity].pack("C") 63 | end 64 | } + data 65 | @format = 3 66 | @color_table_size = colormap.length-1 67 | end 68 | 69 | @width = image.columns 70 | @height = image.rows 71 | @zlib_bitmap_data = Zlib::Deflate.deflate(data) 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/cxformwithalpha.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Cxformwithalpha 6 | attr_accessor :has_add_terms 7 | attr_accessor :has_mult_terms 8 | attr_accessor :nbits 9 | attr_accessor :red_mult_term 10 | attr_accessor :green_mult_term 11 | attr_accessor :blue_mult_term 12 | attr_accessor :alpha_mult_term 13 | attr_accessor :red_add_term 14 | attr_accessor :green_add_term 15 | attr_accessor :blue_add_term 16 | attr_accessor :alpha_add_term 17 | attr_reader :length # byte_aligned 18 | 19 | def initialize(bytearray) 20 | bits_str = bytearray.unpack("B*").first 21 | @has_add_terms = bits_str[0, 1].to_i(2) 22 | @has_mult_terms = bits_str[1, 1].to_i(2) 23 | @nbits = bits_str[2, 4].to_i(2) 24 | offset = 6 25 | if @has_mult_terms == 1 26 | # TODO to get as signed bit values!! 27 | @red_mult_term = bits_str[offset, @nbits].to_i(2) 28 | @green_mult_term = bits_str[offset+@nbits, @nbits].to_i(2) 29 | @blue_mult_term = bits_str[offset+2*@nbits, @nbits].to_i(2) 30 | @alpha_mult_term = bits_str[offset+3*@nbits, @nbits].to_i(2) 31 | offset += 4 * @nbits 32 | end 33 | if @has_add_terms == 1 34 | # TODO to get as signed bit values!! 35 | @red_add_term = bits_str[offset, @nbits].to_i(2) 36 | @green_add_term = bits_str[offset+@nbits, @nbits].to_i(2) 37 | @blue_add_term = bits_str[offset+2*@nbits, @nbits].to_i(2) 38 | @alpha_add_term = bits_str[offset+3*@nbits, @nbits].to_i(2) 39 | offset += 4 * @nbits 40 | end 41 | @length = offset >> 3 42 | @length += 1 if offset & 7 > 0 43 | self 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/fillstyle.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Fillstyle 6 | attr_accessor :shape_version 7 | attr_accessor :fill_style_type 8 | attr_accessor :color 9 | attr_accessor :gradient_matrix 10 | attr_accessor :gradient 11 | attr_accessor :bitmap_id 12 | attr_accessor :bitmap_id_offset 13 | attr_accessor :bitmap_matrix 14 | attr_reader :length 15 | 16 | def initialize(bytearray, shape_version) 17 | @shape_version = shape_version 18 | @fill_style_type = bytearray[0, 1].unpack("C").first 19 | offset = 1 20 | if @fill_style_type == 0x00 21 | if @shape_version >= 3 22 | @color = SwfRuby::Swf::Rgba.new(bytearray[offset..-1]) 23 | else 24 | @color = SwfRuby::Swf::Rgb.new(bytearray[offset..-1]) 25 | end 26 | offset += @color.length 27 | end 28 | if @fill_style_type == 0x10 or @fill_style_type == 0x12 or @fill_style_type == 0x13 29 | @gradient_matrix = SwfRuby::Swf::Matrix.new(bytearray[offset..-1]) 30 | offset += @gradient_matrix.length 31 | end 32 | if @fill_style_type == 0x10 or @fill_style_type == 0x12 33 | @gradient = SwfRuby::Swf::Gradient.new(bytearray[offset..-1], @shape_version) 34 | offset += @gradient.length 35 | elsif @fill_style_type == 0x13 36 | @gradient = SwfRuby::Swf::Focalgradient.new(bytearray[offset..-1], @shape_version) 37 | offset += @gradient.length 38 | end 39 | if @fill_style_type == 0x40 or @fill_style_type == 0x41 or @fill_style_type == 0x42 or @fill_style_type == 0x43 40 | @bitmap_id = bytearray[offset, 2].unpack("v").first 41 | @bitmap_id_offset = offset 42 | offset += 2 43 | @bitmap_matrix = SwfRuby::Swf::Matrix.new(bytearray[offset..-1]) 44 | offset += @bitmap_matrix.length 45 | end 46 | @length = offset 47 | self 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/focalgradient.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Focalgradient 6 | attr_accessor :shape_version 7 | attr_accessor :spread_mode 8 | attr_accessor :interpolation_mode 9 | attr_accessor :num_gradients 10 | attr_accessor :gradient_records 11 | attr_accessor :focal_point 12 | attr_reader :length 13 | 14 | def initialize(bytearray, shape_version) 15 | @shape_version = shape_version 16 | bits_str = bytearray[0, 1].unpack("B*").first 17 | @spread_mode = bits_str[0, 2].to_i(2) 18 | @interpolation_mode = bits_str[2, 2].to_i(2) 19 | @num_gradients = bits_str[4, 4].to_i(2) 20 | offset = 1 21 | @gradient_records = [] 22 | @num_gradients.times do 23 | gradient_record = SwfRuby::Swf::Gradrecord.new(bytearray[offset..-1], @shape_version) 24 | @gradient_records << gradient_record 25 | offset += gradient_record.length 26 | end 27 | # TODO focal_point to be 8bit.8bit fixed_point number. 28 | @focal_point = bytearray[offset, 2] 29 | offset += 2 30 | @length = offset 31 | self 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/gradient.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Gradient 6 | attr_accessor :shape_version 7 | attr_accessor :spread_mode 8 | attr_accessor :interpolation_mode 9 | attr_accessor :num_gradients 10 | attr_accessor :gradient_records 11 | attr_reader :length 12 | 13 | def initialize(bytearray, shape_version) 14 | @shape_version = shape_version 15 | bits_str = bytearray[0, 1].unpack("B*").first 16 | @spread_mode = bits_str[0, 2].to_i(2) 17 | @interpolation_mode = bits_str[2, 2].to_i(2) 18 | @num_gradients = bits_str[4, 4].to_i(2) 19 | offset = 1 20 | @gradient_records = [] 21 | @num_gradients.times do 22 | gradient_record = SwfRuby::Swf::Gradrecord.new(bytearray[offset..-1], @shape_version) 23 | @gradient_records << gradient_record 24 | offset += gradient_record.length 25 | end 26 | @length = offset 27 | self 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/gradrecord.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Gradrecord 6 | attr_accessor :shape_version 7 | attr_accessor :ratio 8 | attr_accessor :color 9 | attr_reader :length 10 | 11 | def initialize(bytearray, shape_version) 12 | @shape_version = shape_version 13 | @ratio = bytearray[0, 1].unpack("C").first 14 | offset = 1 15 | if @shape_version >= 3 16 | @color = SwfRuby::Swf::Rgba.new(bytearray[offset..-1]) 17 | else 18 | @color = SwfRuby::Swf::Rgb.new(bytearray[offset..-1]) 19 | end 20 | offset += @color.length 21 | @length = offset 22 | self 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/header.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Header 6 | attr_accessor :compressed 7 | attr_accessor :version 8 | attr_accessor :file_length 9 | attr_accessor :frame_size 10 | attr_accessor :frame_rate 11 | attr_accessor :frame_count 12 | attr_accessor :length 13 | 14 | def self.parse(swf) 15 | header = Header.new 16 | # check if compressed 17 | if swf[1].chr == "W" and swf[2].chr == "S" 18 | if swf[0].chr == "F" 19 | header.compressed = false 20 | elsif swf[0].chr == "C" 21 | header.compressed = true 22 | end 23 | end 24 | # version 25 | header.version = swf[3].chr.unpack("C").first 26 | # file size 27 | header.file_length = swf[4, 4].unpack("V").first 28 | 29 | # Zlib inflate 30 | if header.compressed 31 | swf[8..-1] = Zlib::Inflate.inflate(swf[8..-1]) 32 | end 33 | 34 | # frame size 35 | header.frame_size = Rectangle.new(swf[8..-1]) 36 | # frame rate 37 | header.frame_rate = (swf[8+header.frame_size.length].chr.unpack("C").first).to_f / 0x100 38 | header.frame_rate += swf[9+header.frame_size.length].chr.unpack("C").first 39 | # frame count 40 | header.frame_count = swf[10+header.frame_size.length, 2].unpack("v").first 41 | # header length 42 | header.length = 12+header.frame_size.length 43 | 44 | [header, swf] 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/matrix.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Matrix 6 | attr_accessor :has_scale 7 | attr_accessor :n_scale_bits 8 | attr_accessor :scale_x 9 | attr_accessor :scale_y 10 | attr_accessor :has_rotate 11 | attr_accessor :n_rotate_bits 12 | attr_accessor :rotate_skew_0 13 | attr_accessor :rotate_skew_1 14 | attr_accessor :n_translate_bits 15 | attr_accessor :translate_x 16 | attr_accessor :translate_y 17 | attr_reader :length # byte_aligned 18 | 19 | def initialize(bytearray) 20 | bits_str = bytearray.unpack("B*").first 21 | @has_scale = bits_str[0, 1].to_i(2) 22 | offset = 1 23 | if @has_scale == 1 24 | @n_scale_bits = bits_str[offset, 5].to_i(2) 25 | offset += 5 26 | @scale_x = bits_str[offset, self.n_scale_bits].to_i(2) 27 | @scale_y = bits_str[offset+self.n_scale_bits, self.n_scale_bits].to_i(2) 28 | offset += 2 * self.n_scale_bits 29 | end 30 | @has_rotate = bits_str[offset, 1].to_i(2) 31 | offset += 1 32 | if @has_rotate == 1 33 | @n_rotate_bits = bits_str[offset, 5].to_i(2) 34 | offset += 5 35 | @rotate_skew_0 = bits_str[offset, self.n_rotate_bits].to_i(2) 36 | @rotate_skew_1 = bits_str[offset+self.n_rotate_bits, self.n_rotate_bits].to_i(2) 37 | offset += 2 * self.n_rotate_bits 38 | end 39 | @n_translate_bits = bits_str[offset, 5].to_i(2) 40 | offset += 5 41 | @translate_x = bits_str[offset, self.n_translate_bits].to_i(2) 42 | @translate_y = bits_str[offset+self.n_translate_bits, self.n_translate_bits].to_i(2) 43 | offset += 2 * self.n_translate_bits 44 | @length = offset >> 3 45 | @length += 1 if offset & 7 > 0 46 | self 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/rectangle.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Rectangle 6 | attr_accessor :bits 7 | attr_accessor :xmin 8 | attr_accessor :xmax 9 | attr_accessor :ymin 10 | attr_accessor :ymax 11 | attr_reader :length 12 | 13 | def initialize(bytearray) 14 | @bits = bytearray[0].chr.unpack("B5").first.to_i(2) 15 | rect_bitstr = bytearray[0..((self.bits/2)+1)].unpack("B*").first 16 | @xmin = rect_bitstr[5, self.bits].to_i(2) 17 | @xmax = rect_bitstr[5+self.bits, self.bits].to_i(2) 18 | @ymin = rect_bitstr[5+2*self.bits, self.bits].to_i(2) 19 | @ymax = rect_bitstr[5+3*self.bits, self.bits].to_i(2) 20 | @length = (5+self.bits*4)/8+1 # including padded byte 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/rgb.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Rgb 6 | 7 | attr_accessor :red 8 | attr_accessor :green 9 | attr_accessor :blue 10 | attr_reader :length 11 | 12 | def initialize(bytearray) 13 | @red = bytearray[0, 1].unpack("C").first 14 | @green = bytearray[1, 1].unpack("C").first 15 | @blue = bytearray[2, 1].unpack("C").first 16 | @length = 3 17 | self 18 | end 19 | 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/rgba.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Rgba 6 | attr_accessor :red 7 | attr_accessor :green 8 | attr_accessor :blue 9 | attr_accessor :alpha 10 | attr_reader :length 11 | 12 | def initialize(bytearray) 13 | @red = bytearray[0, 1].unpack("C").first 14 | @green = bytearray[1, 1].unpack("C").first 15 | @blue = bytearray[2, 1].unpack("C").first 16 | @alpha = bytearray[3, 1].unpack("C").first 17 | @length = 4 18 | self 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/shapewithstyle.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Shapewithstyle 6 | attr_accessor :shape_version 7 | attr_accessor :fill_styles_with_offset 8 | attr_accessor :after_line_styles 9 | 10 | def initialize(bytearray, shape_version) 11 | @shape_version = shape_version 12 | fill_style_count = bytearray[0, 1].unpack("C").first 13 | offset = 1 14 | if fill_style_count == 255 15 | fill_style_count = bytearray[1, 2].unpack("v").first 16 | offset += 2 17 | end 18 | @fill_styles_with_offset = {} 19 | fill_style_count.times do 20 | fill_style = SwfRuby::Swf::Fillstyle.new(bytearray[offset..-1], @shape_version) 21 | @fill_styles_with_offset[offset] = fill_style 22 | offset += fill_style.length 23 | end 24 | # TODO after_line_styles are including outside tag data.. 25 | @after_line_styles = bytearray[offset..-1] 26 | self 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/swf_string.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class SwfString 6 | attr_accessor :string 7 | attr_reader :length # including StringEndMark 8 | 9 | def initialize(bytearray) 10 | @length = 0 11 | bytearray.each_byte do |byte| 12 | break if byte == 0 13 | @length += 1 14 | end 15 | @string = bytearray[0, @length] 16 | @length += 1 # Endmark 17 | self 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf/tag.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | module Swf 5 | class Tag 6 | attr_reader :rawdata 7 | attr_reader :code 8 | attr_reader :length 9 | attr_reader :long_header 10 | attr_reader :data 11 | attr_reader :character_id 12 | attr_reader :refer_character_id 13 | attr_reader :refer_character_id_offset 14 | attr_reader :refer_character_inst_name 15 | attr_reader :refer_bitmap_offsets_to_ids 16 | 17 | def initialize(swf) 18 | swf.force_encoding("ASCII-8BIT") if swf.respond_to? :force_encoding 19 | record_header = swf[0, 2].unpack("v").first 20 | # tag type 21 | @code = (record_header >> 6) & 1023 22 | # tag length (including record header) 23 | @length = record_header & 63 24 | @long_header = false 25 | @data = nil 26 | if @length == 63 27 | len = swf[2, 4].unpack("i").first 28 | @data = swf[6, len] 29 | @length = len + 6 30 | @long_header = true 31 | else 32 | @data = swf[2, @length] 33 | @length += 2 34 | end 35 | @character_id = nil 36 | if self.define_tag? 37 | @character_id = @data[0, 2].unpack("v").first 38 | end 39 | @refer_character_id = nil 40 | @refer_character_id_offset = nil 41 | @refer_character_inst_name = nil 42 | data_offset = @long_header ? 6 : 2 43 | case Swf::TAG_TYPE[@code] 44 | when "PlaceObject" 45 | @refer_character_id_offset = data_offset 46 | @refer_character_id = @data[0, 2].unpack("v").first 47 | when "PlaceObject2" 48 | flags = @data[0, 1].unpack("C").first 49 | offset = 3 50 | if flags & 2 == 2 51 | @refer_character_id_offset = data_offset + offset 52 | @refer_character_id = @data[offset, 2].unpack("v").first 53 | offset += 2 54 | end 55 | if flags & 32 == 32 56 | if flags & 4 == 4 57 | matrix = SwfRuby::Swf::Matrix.new(@data[offset..-1]) 58 | offset += matrix.length 59 | end 60 | if flags & 8 == 8 61 | cxtfm = SwfRuby::Swf::Cxformwithalpha.new(@data[offset..-1]) 62 | offset += cxtfm.length 63 | end 64 | offset += 2 if flags & 16 == 16 65 | @refer_character_inst_name = SwfRuby::Swf::SwfString.new(@data[offset..-1]).string 66 | end 67 | when "PlaceObject3" 68 | flags = @data[0, 2].unpack("n").first 69 | offset = 4 70 | if flags & 8 == 8 71 | class_name = SwfRuby::Swf::SwfString.new(@data[offset..-1]) 72 | offset += class_name.length 73 | end 74 | if flags & 512 == 512 75 | @refer_character_id_offset = data_offset + offset 76 | @refer_character_id = @data[offset, 2].unpack("v").first 77 | end 78 | if flags & 8192 == 8192 79 | if flags & 1024 == 1024 80 | matrix = SwfRuby::Swf::Matrix.new(@data[offset..-1]) 81 | offset += matrix.length 82 | end 83 | if flags & 2048 == 2048 84 | cxtfm = SwfRuby::Swf::Cxformwithalpha.new(@data[offset..-1]) 85 | offset += cxtfm.length 86 | end 87 | offset += 2 if flags & 4096 == 4096 88 | @refer_character_inst_name = SwfRuby::Swf::SwfString.new(@data[offset..-1]).string 89 | end 90 | when "RemoveObject" 91 | @refer_character_id_offset = data_offset 92 | @refer_character_id = @data[0, 2].unpack("v").first 93 | when "DefineShape", "DefineShape2", "DefineShape3" 94 | version = Swf::TAG_TYPE[@code][-1].to_i 95 | version = 1 if version == 0 96 | @refer_bitmap_offsets_to_ids = {} 97 | rect = SwfRuby::Swf::Rectangle.new(@data[2..-1]) 98 | offset = 2+rect.length 99 | shapewithstyle = SwfRuby::Swf::Shapewithstyle.new(@data[offset..-1], version) 100 | shapewithstyle.fill_styles_with_offset.each do |fs_offset, fs| 101 | if fs.bitmap_id 102 | o = offset + fs_offset + fs.bitmap_id_offset 103 | o += @long_header ? 6 : 2 104 | @refer_bitmap_offsets_to_ids[o] = fs.bitmap_id 105 | end 106 | end 107 | when "DefineShape4" 108 | version = 4 109 | @refer_bitmap_offsets_to_ids = {} 110 | shape_rect = SwfRuby::Swf::Rectangle.new(@data[2..-1]) 111 | offset = 2+shape_rect.length 112 | edge_rect = SwfRuby::Swf::Rectangle.new(@data[offset..-1]) 113 | offset += edge_rect.length 114 | offset += 1 115 | shapewithstyle = SwfRuby::Swf::Shapewithstyle.new(@data[offset..-1], version) 116 | shapewithstyle.fill_styles_with_offset.each do |fs_offset, fs| 117 | if fs.bitmap_id 118 | o = offset + fs_offset + fs.bitmap_id_offset 119 | o += @long_header ? 6 : 2 120 | @refer_bitmap_offsets_to_ids[o] = fs.bitmap_id 121 | end 122 | end 123 | else 124 | # do nothing. 125 | end 126 | @rawdata = swf[0, @length] 127 | self 128 | end 129 | 130 | # Define系タグの冒頭のcharacter_id参照を指定のIDに書き換える. 131 | # 内部にBitmapIDの参照を含む場合、渡されたidmapから読み替える値を参照し、書き換える. 132 | def rawdata_with_define_character_id(idmap, character_id) 133 | if self.define_tag? 134 | offset = @long_header ? 6 : 2 135 | @rawdata[offset, 2] = [character_id].pack("v") 136 | if Swf::TAG_TYPE[@code] == "DefineSprite" 137 | sd = SwfRuby::SpriteDumper.new.dump(self) 138 | rdata = "" 139 | sd.tags.each do |t| 140 | if t.refer_character_id 141 | rdata << t.rawdata_with_refer_character_id(idmap[t.refer_character_id]) 142 | else 143 | rdata << t.rawdata 144 | end 145 | end 146 | @rawdata[offset+4..-1] = rdata 147 | else 148 | @refer_bitmap_offsets_to_ids.each do |bitmap_id_offset, bitmap_id| 149 | @rawdata[bitmap_id_offset, 2] = [idmap[bitmap_id]].pack("v") 150 | end if @refer_bitmap_offsets_to_ids 151 | end 152 | end 153 | @rawdata 154 | end 155 | 156 | def rawdata_with_refer_character_id(character_id) 157 | @rawdata[@refer_character_id_offset, 2] = [character_id].pack("v") if @refer_character_id_offset 158 | @rawdata 159 | end 160 | 161 | def define_tag? 162 | Swf::TAG_TYPE[@code] =~ /^Define/ 163 | end 164 | 165 | end 166 | 167 | class TagError < StandardError; end 168 | 169 | TAG_TYPE = { 170 | 0=>"End", 171 | 1=>"ShowFrame", 172 | 2=>"DefineShape", 173 | 3=>"FreeCharacter", 174 | 4=>"PlaceObject", 175 | 5=>"RemoveObject", 176 | 6=>"DefineBitsJPEG", 177 | 7=>"DefineButton", 178 | 8=>"JPEGTables", 179 | 9=>"SetBackgroundColor", 180 | 10=>"DefineFont", 181 | 11=>"DefineText", 182 | 12=>"DoAction", 183 | 13=>"DefineFontInfo", 184 | 14=>"DefineSound", 185 | 15=>"StopSound", 186 | 17=>"DefineButtonSound", 187 | 18=>"SoundStreamHead", 188 | 19=>"SoundStreamBlock", 189 | 20=>"DefineBitsLossless", 190 | 21=>"DefineBitsJPEG2", 191 | 22=>"DefineShape2", 192 | 23=>"DefineButtonCxform", 193 | 24=>"Protect", 194 | 25=>"PathsArePostscript", 195 | 26=>"PlaceObject2", 196 | 28=>"RemoveObject2", 197 | 29=>"SyncFrame", 198 | 31=>"FreeAll", 199 | 32=>"DefineShape3", 200 | 33=>"DefineText2", 201 | 34=>"DefineButton2", 202 | 35=>"DefineBitsJPEG3", 203 | 36=>"DefineBitsLossless2", 204 | 37=>"DefineEditText", 205 | 38=>"DefineVideo", 206 | 39=>"DefineSprite", 207 | 40=>"NameCharacter", 208 | 41=>"ProductInfo", 209 | 42=>"DefineTextFormat", 210 | 43=>"FrameLabel", 211 | 45=>"SoundStreamHead2", 212 | 46=>"DefineMorphShape", 213 | 47=>"GenerateFrame", 214 | 48=>"DefineFont2", 215 | 49=>"GeneratorCommand", 216 | 50=>"DefineCommandObject", 217 | 51=>"CharacterSet", 218 | 52=>"ExternalFont", 219 | 56=>"Export", 220 | 57=>"Import", 221 | 58=>"ProtectDebug", 222 | 59=>"DoInitAction", 223 | 60=>"DefineVideoStream", 224 | 61=>"VideoFrame", 225 | 62=>"DefineFontInfo2", 226 | 63=>"DebugID", 227 | 64=>"ProtectDebug2", 228 | 65=>"ScriptLimits", 229 | 66=>"SetTabIndex", 230 | 69=>"FileAttributes", 231 | 70=>"PlaceObject3", 232 | 71=>"Import2", 233 | 72=>"DoABC", 234 | 73=>"DefineFontAlignZones", 235 | 74=>"CSMTextSettings", 236 | 75=>"DefineFont3", 237 | 76=>"SymbolClass", 238 | 77=>"Metadata", 239 | 78=>"DefineScalingGrid", 240 | 82=>"DoABCDefine", 241 | 83=>"DefineShape4", 242 | 84=>"DefineMorphShape2", 243 | 86=>"DefineSceneAndFrameData", 244 | 87=>"DefineBinaryData", 245 | 88=>"DefineFontName", 246 | 90=>"DefineBitsJPEG4" 247 | } 248 | 249 | end 250 | end 251 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf_dumper.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | 3 | module SwfRuby 4 | # Swf構造をダンプするクラス. 5 | class SwfDumper 6 | attr_reader :swf 7 | attr_reader :header 8 | attr_reader :tags 9 | attr_reader :tags_addresses 10 | attr_accessor :character_ids 11 | 12 | # 初期化. 13 | def initialize 14 | @swf = nil 15 | @header = nil 16 | @tags = nil 17 | @tags_addresses = nil 18 | end 19 | 20 | # ファイルをバイナリモードで読み込み. 21 | # 1.9環境の場合はエンコーディング指定. 22 | def open(file) 23 | f = File.open(file, "rb").read 24 | f.force_encoding("ASCII-8BIT") if f.respond_to? :force_encoding 25 | dump(f) 26 | end 27 | 28 | # ダンプして構造をインスタンス変数に格納. 29 | def dump(swf) 30 | @swf = swf 31 | @header, @swf = Swf::Header.parse(@swf) 32 | @tags = [] 33 | @tags_addresses = [] 34 | tags_length = 0 35 | while @header.length + tags_length < @header.file_length 36 | addr = @header.length + tags_length 37 | @tags_addresses << addr 38 | tag = Swf::Tag.new(@swf[addr..-1]) 39 | tags_length += tag.length 40 | @tags << tag 41 | end 42 | self 43 | end 44 | 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/swf_ruby/swf_tamperer.rb: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 filetype=ruby ts=2 : 2 | module SwfRuby 3 | # Swfに含まれるリソースを置換するクラス. 4 | class SwfTamperer 5 | # 対象Swf(バイナリ)のリソースを置換. 6 | # 置換対象はReplaceTargetオブジェクトの配列で渡す. 7 | def replace(swf, replace_targets) 8 | replace_targets.sort_by { |a| a.offset }.reverse.each do |rt| 9 | case rt 10 | when Jpeg2ReplaceTarget 11 | swf = repl_jpeg2(swf, rt.offset, rt.jpeg) 12 | when Lossless2ReplaceTarget 13 | swf = repl_lossless2(swf, rt.offset, rt.image) 14 | when AsVarReplaceTarget 15 | swf = repl_action_push_string(swf, rt.do_action_offset, rt.offset, rt.str, rt.parent_sprite_offset) 16 | when SpriteReplaceTarget 17 | swf = repl_sprite(swf, rt.offset, rt.target_define_tags_string, rt.frame_count, rt.target_control_tags_string) 18 | end 19 | end 20 | swf 21 | end 22 | 23 | private 24 | 25 | # 対象オフセット位置にあるDefineSpriteを指定したControlTags文字列で置換. 26 | # 新しいSprite含むDefineTagsは、DefineSpriteの直前に挿入する. 27 | def repl_sprite(swf, offset, define_tags, frame_count, control_tags) 28 | swf.force_encoding("ASCII-8BIT") if swf.respond_to? :force_encoding 29 | define_tags.force_encoding("ASCII-8BIT") if define_tags.respond_to? :force_encoding 30 | control_tags.force_encoding("ASCII-8BIT") if control_tags.respond_to? :force_encoding 31 | record_header = swf[offset, 2].unpack("v").first 32 | # tag check 33 | raise ReplaceTargetError if (record_header >> 6) & 1023 != 39 34 | # error for short header (not implemented yet.) 35 | raise ReplaceTargetError if record_header & 63 < 63 36 | # rewrite frame count 37 | swf[offset+8, 2] = [frame_count].pack("v") 38 | # rewrite control tag and length 39 | sprite_len = swf[offset+2, 4].unpack("i").first # without recordheader 40 | old_control_tags_len = sprite_len - 4 41 | delta_control_tags_len = control_tags.length - old_control_tags_len 42 | swf[offset+10, old_control_tags_len] = control_tags 43 | swf[offset+2, 4] = [sprite_len + delta_control_tags_len].pack("V") 44 | # insert define tags before define sprite 45 | swf[offset, 0] = define_tags 46 | # rewrite swf header 47 | swf[4, 4] = [swf[4, 4].unpack("V").first + define_tags.length + delta_control_tags_len].pack("V") 48 | swf 49 | end 50 | 51 | # 対象オフセット位置にあるActionPushデータを置換. 52 | def repl_action_push_string(swf, do_action_offset, action_push_offset, str, parent_sprite_offset = nil) 53 | swf.force_encoding("ASCII-8BIT") if swf.respond_to? :force_encoding 54 | str.force_encoding("ASCII-8BIT") if str.respond_to? :force_encoding 55 | record_header = swf[do_action_offset, 2].unpack("v").first 56 | # tag check 57 | raise ReplaceTargetError if (record_header >> 6) & 1023 != 12 58 | # action check 59 | raise ReplaceTargetError if swf[action_push_offset].chr.unpack("C").first != 0x96 60 | raise ReplaceTargetError if swf[action_push_offset + 3].chr.unpack("C").first != 0 61 | # error for short header (not implemented yet.) 62 | raise ReplaceTargetError if record_header & 63 < 63 63 | # calc length 64 | do_action_len = swf[do_action_offset+2, 4].unpack("i").first # without recordheader 65 | action_push_len = swf[action_push_offset+1, 2].unpack("v").first 66 | org_str_length = action_push_len - 2 # data type & terminated null 67 | delta_str_length = str.length - org_str_length 68 | # replace string and rewrite length 69 | swf[action_push_offset+4, org_str_length] = str 70 | swf[action_push_offset+1, 2] = [action_push_len + delta_str_length].pack("v") 71 | swf[do_action_offset+2, 4] = [do_action_len + delta_str_length].pack("V") 72 | if parent_sprite_offset 73 | swf[parent_sprite_offset+2, 4] = [swf[parent_sprite_offset+2, 4].unpack("V").first + delta_str_length].pack("V") 74 | end 75 | swf[4, 4] = [swf[4, 4].unpack("V").first + delta_str_length].pack("V") 76 | swf 77 | end 78 | 79 | # DefineBitsJpeg2のイメージバイナリを置換. 80 | def repl_jpeg2(swf, offset, jpeg) 81 | swf.force_encoding("ASCII-8BIT") if swf.respond_to? :force_encoding 82 | jpeg.force_encoding("ASCII-8BIT") if jpeg.respond_to? :force_encoding 83 | record_header = swf[offset, 2].unpack("v").first 84 | # tag check (対象がSWF全体なので、効率の面からTagインスタンスを作らないでチェック) 85 | raise ReplaceTargetError if (record_header >> 6) & 1023 != 21 86 | # calc length 87 | org_jpeg_length = record_header & 63 - 2 88 | before_long_header = false 89 | after_long_header = false 90 | if org_jpeg_length == 61 91 | before_long_header = true 92 | org_jpeg_length = swf[offset+2, 4].unpack("i").first - 2 93 | end 94 | target_jpeg_length = jpeg.length + 4 95 | delta_jpeg_length = target_jpeg_length - org_jpeg_length 96 | after_long_header = true if target_jpeg_length + 2 >= 63 97 | # replace jpeg and rewrite length 98 | # タグのヘッダ長さが入れ替え前後で変化する場合があるので場合分けする 99 | if before_long_header and after_long_header 100 | swf[offset+8, org_jpeg_length] = [0xff, 0xd9, 0xff, 0xd8].pack("C*") + jpeg 101 | swf[offset+2, 4] = [target_jpeg_length + 2].pack("i") 102 | swf[4, 4] = [swf[4, 4].unpack("V").first + delta_jpeg_length].pack("V") 103 | elsif before_long_header and !after_long_header 104 | swf[offset+8, org_jpeg_length] = [0xff, 0xd9, 0xff, 0xd8].pack("C*") + jpeg 105 | swf[offset, 4] = [(((record_header >> 6) & 1023) << 6) + target_jpeg_length + 2].pack("v") 106 | swf[4, 4] = [swf[4, 4].unpack("V").first + delta_jpeg_length - 4].pack("V") 107 | elsif !before_long_header and after_long_header 108 | swf[offset+4, org_jpeg_length] = [0xff, 0xd9, 0xff, 0xd8].pack("C*") + jpeg 109 | swf[offset, 2] = [(((record_header >> 6) & 1023) << 6) + 63].pack("v") + [target_jpeg_length + 2].pack("i") 110 | swf[4, 4] = [swf[4, 4].unpack("V").first + delta_jpeg_length + 4].pack("V") 111 | elsif !before_long_header and !after_long_header 112 | swf[offset+4, org_jpeg_length] = [0xff, 0xd9, 0xff, 0xd8].pack("C*") + jpeg 113 | swf[offset, 2] = [(((record_header >> 6) & 1023) << 6) + target_jpeg_length + 2].pack("v") 114 | swf[4, 4] = [swf[4, 4].unpack("V").first + delta_jpeg_length].pack("V") 115 | end 116 | swf 117 | end 118 | 119 | # DefineBitsLossless2のイメージバイナリを置換. 120 | def repl_lossless2(swf, offset, lossless) 121 | swf.force_encoding("ASCII-8BIT") if swf.respond_to? :force_encoding 122 | 123 | org_format = swf[offset+8, 1].unpack("C").first 124 | # replace lossless2 data 125 | if lossless.format == 3 126 | if org_format == 3 127 | org_image_length = swf[offset+2, 4].unpack("i").first - 8 128 | delta_length = lossless.zlib_bitmap_data.size - org_image_length 129 | swf[offset+14, org_image_length] = lossless.zlib_bitmap_data 130 | swf[offset+13, 1] = [lossless.color_table_size].pack("C") 131 | swf[offset+11, 2] = [lossless.height].pack("v") 132 | swf[offset+9, 2] = [lossless.width].pack("v") 133 | swf[offset+8, 1] = [lossless.format].pack("C") 134 | swf[offset+2, 4] = [lossless.zlib_bitmap_data.size + 8].pack("i") 135 | elsif org_format == 5 136 | org_image_length = swf[offset+2, 4].unpack("i").first - 7 137 | delta_length = lossless.zlib_bitmap_data.size - org_image_length + 1 138 | swf[offset+13, org_image_length] = [lossless.color_table_size].pack("C") + lossless.zlib_bitmap_data 139 | swf[offset+11, 2] = [lossless.height].pack("v") 140 | swf[offset+9, 2] = [lossless.width].pack("v") 141 | swf[offset+8, 1] = [lossless.format].pack("C") 142 | swf[offset+2, 4] = [lossless.zlib_bitmap_data.size + 8].pack("i") 143 | else 144 | raise ReplaceTargetError 145 | end 146 | elsif lossless.format == 5 147 | if org_format == 3 148 | org_image_length = swf[offset+2, 4].unpack("i").first - 8 149 | delta_length = lossless.zlib_bitmap_data.size - org_image_length - 1 150 | swf[offset+13, org_image_length+1] = lossless.zlib_bitmap_data 151 | swf[offset+11, 2] = [lossless.height].pack("v") 152 | swf[offset+9, 2] = [lossless.width].pack("v") 153 | swf[offset+8, 1] = [lossless.format].pack("C") 154 | swf[offset+2, 4] = [lossless.zlib_bitmap_data.size + 7].pack("i") 155 | elsif org_format == 5 156 | org_image_length = swf[offset+2, 4].unpack("i").first - 7 157 | delta_length = lossless.zlib_bitmap_data.size - org_image_length 158 | swf[offset+13, org_image_length] = lossless.zlib_bitmap_data 159 | swf[offset+11, 2] = [lossless.height].pack("v") 160 | swf[offset+9, 2] = [lossless.width].pack("v") 161 | swf[offset+8, 1] = [lossless.format].pack("C") 162 | swf[offset+2, 4] = [lossless.zlib_bitmap_data.size + 7].pack("i") 163 | else 164 | raise ReplaceTargetError 165 | end 166 | else 167 | raise ReplaceTargetError 168 | end 169 | swf[4, 4] = [swf[4, 4].unpack("V").first + delta_length].pack("V") 170 | swf 171 | end 172 | end 173 | 174 | end 175 | -------------------------------------------------------------------------------- /lib/swf_ruby/version.rb: -------------------------------------------------------------------------------- 1 | module SwfRuby 2 | VERSION = "0.2.3" 3 | end 4 | -------------------------------------------------------------------------------- /samples/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtysk/swf_ruby/97e1e18c4e7b7a67e21378f6e13f40c7b9ea27c8/samples/bg.jpg -------------------------------------------------------------------------------- /samples/icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtysk/swf_ruby/97e1e18c4e7b7a67e21378f6e13f40c7b9ea27c8/samples/icon.gif -------------------------------------------------------------------------------- /samples/sample.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtysk/swf_ruby/97e1e18c4e7b7a67e21378f6e13f40c7b9ea27c8/samples/sample.swf -------------------------------------------------------------------------------- /swf_ruby.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $LOAD_PATH.unshift File.expand_path('../lib', __FILE__) 3 | require 'swf_ruby/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "swf_ruby" 7 | s.version = SwfRuby::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["tmtysk"] 10 | s.email = [] 11 | s.homepage = "http://github.com/tmtysk/swf_ruby" 12 | s.summary = "Utilities to dump or manipulate Swf binary file." 13 | s.description = "SwfRuby is utilities to dump or manipulate Swf binary file." 14 | 15 | s.required_rubygems_version = ">= 1.3.6" 16 | s.rubyforge_project = "swf_ruby" 17 | 18 | s.add_dependency "bundler", ">= 1.0.0.rc.5" 19 | s.add_dependency "rmagick", ">= 2.13.0" 20 | 21 | s.bindir = 'bin' 22 | s.executables = ['swf_dump', 'swf_jpeg_replace', 'swf_lossless_replace', 'swf_as_var_replace', 'swf_sprite_replace'] 23 | 24 | s.files = `git ls-files`.split("\n") 25 | s.require_path = 'lib' 26 | end 27 | --------------------------------------------------------------------------------