<?php
  
/*
   * 2010/07/24 (c) yoya@awm.jp
   */

class YSwf {
    
//
    
var $_data// input_data
    
var $_header_length null;
    var 
$_movie_info_list = array(); // offset, length, ...
    //
    
var $_tagName// all known tags
    
var $_tagHasCharaId;
    var 
$_tagMisc = array(
        
0  => 'End',
        
1  => 'ShowFrame',
        
8  => 'JPEGTables',
        
9  => 'SetBackgroundColor',
        
12 => 'DoAction',
        
28 => 'RemoveObject2',
        
34 => 'DefineButton2',
        
39 => 'DefineSprite',
        
43 => 'FrameLabel',
        );
    var 
$_tagJpeg = array(
        
6  => 'DefineBitsJPEG',
        
21 => 'DefineBitsJPEG2',
        
35 => 'DefineBitsJPEG3',
        
90 => 'DefineBitsJPEG4',
        );
    var 
$_tagShape = array(
        
2  => 'DefineShape',
        
22 => 'DefineShape2',
        
32 => 'DefineShape3',
        
46 => 'DefineMorphShape',
        
83 => 'DefineShape4',
        
84 => 'DefineMorphShape2',
        );
    var 
$_tagPlace = array(
        
4  => 'PlaceObject',
        
26 => 'PlaceObject2',
        );
    
    
// constructor
    
function YSwf() {
        
$this->_tagName $this->_tagShape $this->_tagPlace +
            
$this->_tagJpeg $this->_tagMisc;
        
$this->_tagHasCharaId $this->_tagShape $this->_tagPlace $this->_tagJpeg;
    }
    
    
/*
     * utility function for byte processing
     */
    
function getData($offset$length) { // XXX
        
return substr($this->_data$offset$length);
    }
    function 
getUI8($offset) { // XXX
        
return ord($this->_data{$offset}); // UI8
                
    
}
    function 
getUI16LE($offset) {
        
$ret unpack('v'substr($this->_data$offset2)); // UI16LE
        
return $ret[1];
    }
    function 
getUI32LE($offset) {
        
$ret unpack('V'substr($this->_data$offset4)); // UI32LE
        
return $ret[1];
    }
    
    
/*
     * input swf data
     */
    
function input($data) {
        
$this->_data $data;
        
$ret $this->_validate_header($data);
        
        
$ret $this->_get_header_length($data);
        if (
$ret === false) {
            
trigger_error("get_header_length failed");
            return 
$ret;
        }
        
$this->_header_length $ret;
        
$offset $this->_header_length;
        
        
$ret $this->_input_movie($data$offsetstrlen($data) - $offset$this->_movie_info_list);
        return 
$ret;
    }
    function 
_input_movie($data$offset$length, &$movie_info_list) {
        while (
true) {
            
$ret $this->_get_movie_info($data$offset);
            if (
$ret === false) {
                
trigger_error("get_movie_info failed");
                return 
$ret;
            }
            
$movie_info $ret;
            
$tag $movie_info['tag'];
            
$length $movie_info['length'];
            
$tag_and_length_size $movie_info['tag_and_length_size'];
            if (
$tag == 39) { // SpriteTag (MovieClip)
                // Sprite is nest movie tag
                
$movie_info['frame_count'] = $this->getUI16LE($offset $tag_and_length_size 2);
                
$movie_offset $tag_and_length_size 2;
                
$ret $this->_input_movie($data$offset $movie_offset$length $movie_offset$movie_info['movie']);
                if (
$ret === false) {
                    return 
false;
                }
            }
            
$movie_info_list[] = $movie_info;
            if (
$tag == 0) { // End Tag
                
break;
            }
            if (
$length <= 0) { // Error
                
trigger_error("movie_info length <= 0");
                break;
            }
            
$offset += $length;
        }
    }
    function 
_validate_header($data) {
        
// validate
        
$magic $this->getData(03);
        if (
$magic != 'FWS') {
            
trigger_error("no FWS");
            return 
false;
        }
        
$version $this->getUI8(4);
        if (
$version <= 4) {
            
trigger_error("version($version) must be le 4");
            return 
false;
        }
        
$file_length $this->getUI32LE(4);
        
$data_length strlen($this->_data);
        if (
$file_length != $data_length) {
            
trigger_error("file_length($file_length) != data length($data_length");
            return 
false;
        }
        return 
true;
    }
    function 
_get_header_length($data) {
        
$rect_field_bit_width + (* ($this->getUI8(8) >> 3));
        
// bit width => byte length
        
$rect_field_length  ceil($rect_field_bit_width 8);
        return 
$rect_field_length 4;
    }
    function 
_get_movie_info($data$offset) {
        
$tag_and_length $this->getUI16LE($offset);
        
$tag = ($tag_and_length >> 6) & 0x3ff;
        
$length $tag_and_length 0x3f;
        if (
$length 0x3f) {
            
$tag_and_length_size 2;
        } else {
            
$length $this->getUI32LE($offset 2);
            
$tag_and_length_size 4;
        }
        
$length $tag_and_length_size $length;

        if (isset(
$this->_tagHasCharaId[$tag])) {
            if (isset(
$this->_tagShape[$tag]) ||
                isset(
$this->_tagJpeg[$tag])) {
                
$chara_id $this->getUI16LE($offset $tag_and_length_size);
            } else {
                switch (
$tag) {
                case 
39// DefineSprite
                
case 4:  // PlaceObject
                    
$chara_id $this->getUI16LE($offset $tag_and_length_size);
                    break;
                case 
26// PlaceObject2
                    
$chara_id $this->getUI16LE($offset $tag_and_length_size 3);
                    break;
                default:
                    
$chara_id = -1;
                    break;
                }
            }
        }
        
$movie_info = array('tag' => $tag,
                            
'offset' => $offset'length' => $length,
                            
'tag_and_length_size' => $tag_and_length_size);
        if (isset(
$this->_tagHasCharaId[$tag])) {
            
$movie_info['chara_id'] = $chara_id;
        }
        return 
$movie_info;
    }
    
/*
     * tag contents checksum
     */
    
function _set_checksum_tag($tag_table) {
        foreach (
$this->_movie_info_list as &$movie_info) {
            
$tag $movie_info['tag'];
            if (isset(
$tag_table[$tag])) {
                
$offset $movie_info['offset'];
                
$length $movie_info['length'];
                
$tag_and_length_size $movie_info['tag_and_length_size'];
                
$movie_info['checksum'] = crc32($this->getData($offset $tag_and_length_size 2$length $tag_and_length_size 2));
            }
        }
    }
    function 
set_jpeg_checksum() {
        
$this->_set_checksum_tag($this->_tagJpeg);
    }
    function 
set_shape_checksum() {
        
$this->_set_checksum_tag($this->_tagShape);
    }
    function 
unique_jpeg_by_checksum() {
        ; 
// edit id shape => jpeg
    
}
    function 
unique_shape_by_checksum() {
        ; 
// edit id place => shape
    
}
    
/*
     * dump swf data structure
     */
    
function dump($opts = array()) {
        echo 
"head_length=".$this->_header_length.PHP_EOL;
        if (isset(
$opts['hex_dump'])) {
            
$this->hex_dump(0$this->_header_length);
            echo 
PHP_EOL// for look attractive
        
}
        
$this->dump_movie($this->_movie_info_list0$opts);
    }
    function 
dump_movie(&$movie_info_list$indent$opts) {
        foreach (
$movie_info_list as $movie_info) {
            echo 
str_pad(''$indent' ');
            
$tag $movie_info['tag'];
            echo 
"tag=$tag";
            if (isset(
$this->_tagName[$movie_info['tag']])) {
                echo 
'('.$this->_tagName[$movie_info['tag']].')';
            }
            echo 
" length=".$movie_info['length'];
            if (isset(
$movie_info['chara_id'])) {
                echo 
" id=".$movie_info['chara_id'];
            }
            if (isset(
$movie_info['checksum'])) {
                echo 
" checksum=".$movie_info['checksum'];
            }
            echo 
PHP_EOL;
                    
            if (isset(
$movie_info['movie'])) {
                
$this->dump_movie($movie_info['movie'], $indent+1$opts);
            } else {
                if (isset(
$opts['hex_dump'])) {
                    
$this->hex_dump($movie_info['offset'], $movie_info['length']);
                    echo 
PHP_EOL;
                }
            }
        }
    }
    
/*
     * general purpose hex_dump routine
     */
    
function hex_dump($offset$length) {
        
printf("            0  1  2  3  4  5  6  7   8  9  a  b  c  d  e  f  0123456789abcdef\n");
        
$dump_str '';
        if (
$offset 0x10) {
            
printf("0x%08x "$offset - ($offset 0x10));
            
$dump_str str_pad(' '$offset 0x10);
        }
        for (
$i 0$i $offset 0x10$i++) {
            if (
$i == 8) {
                echo(
' ');
            }
            echo(
'   ');
        }
        for (
$i $offset $i $offset $length$i++) {
            if ((
$i 0x10) == 0) {
                
printf("0x%08x "$i);
            }
            if (isset(
$this->_data[$i])) {
                
$value $this->getUI8($i);
                if ((
0x20 $value) && ($value 0x7f)) { // XXX: printable
                    
$dump_str .= $this->_data{$i};
                } else {
                    
$dump_str .= ' ';
                }
                
printf("%02x "$value);
            } else {
                
$dump_str .= ' ';
                echo 
'   ';
            }
            if ((
$i 0x10) == 0x0f) {
                echo 
" ";
                echo 
$dump_str;
                echo 
PHP_EOL;
                
$dump_str '';
            }
        }
        if ((
$i 0x10) != 0) {
            echo 
str_pad(' '* (0x10 - ($i 0x10)));
            if (
$i 8) {
                echo 
' ';
            }
            echo 
" ";
            echo 
$dump_str;
            echo 
PHP_EOL;
        }
    }
}

if (
$argc != 2) {
    echo 
"Usage: php YSwf.php <swf_file>".PHP_EOL;
    exit(
1);
}
$swf = new YSwf();
$swfdata file_get_contents($argv[1]);
$swf->input($swfdata);
$swf->set_jpeg_checksum($swfdata);
$swf->set_shape_checksum($swfdata);
$swf->dump(array('hex_dump' => true));