
SWF研究の第2弾として、内部構造が大体理解できたので、比較的簡単そうな「jpeg」データの取得を行なってみたいと思います。
SWFで使用できる画像は主にjpeg、GIF,PNGの3つ(※BMPも使えたっけ?)、まあ、WEBで使う画像としてもこの3つなので、
これをコントロールできる事が、実践の第一歩であると考えてます。
前回の
SWFファイルの情報取得のソースを流用して、内部に格納されているJPEGデータを吸い出してJPEGファイルを作成してみたいと思います。
ちなみに、yoyaさんをリスペクトしてPHPコマンドで行なってみたいと思います。
※他言語へのローカライズは別の機会に行いたいと思います。
ソース
getImage.php
ソースの冒頭に簡単な仕様記述をしておきました。
 
<?php
date_default_timezone_set('Asia/Tokyo');
/**
* Images of swf-file to pick-up.
* write : yugeta
* date  : 2016/4/29
*
* [sample]
* getImage.php f=../swf/hoge.swf d=true
*
* [specification]
* param @ f : target-file (*must)
* param @ d : view dump [true / false(*default)]
**/
//CLIの場合argvをREQUESTに変換する。
if(isset($argv)){
	for($i=1,$c=count($argv);$i<$c;$i++){
		if(!$argv[$i]){continue;}
		//各クエリの分解
		$q = explode("=",$argv[$i]);
		if(count($q)<2){continue;}
		if($q[0]!=''){
			//requestに格納
			$key = $q[0];
			$val = join("=",array_slice($q,1));
			$_REQUEST[$key]=$val;
		}
	}
}
// File-check
if(!$_REQUEST["f"] || !is_file($_REQUEST["f"])){
	echo "Error ! : not-file".PHP_EOL;
	exit(0);
}
// Init
require_once "BitReader.php";
$swfdata = file_get_contents($_REQUEST["f"]);
$reader = new BitReader;
$reader->input($swfdata);
$outputPath = "output/".date("YmdHis").".".explode(" ",microtime())[1];
mkdir($outputPath,0777,true);
file_put_contents($outputPath."/".basename($_REQUEST["f"]) , $swfdata);
// Get header
$signature  = $reader->getData(3);
$version    = $reader->getUI8();
$fileLength = $reader->getUI32LE();
$NBits = $reader->getUIBits(5);
$xmin       = ($reader->getUIBits($NBits) / 20);
$xmax       = ($reader->getUIBits($NBits) / 20);
$ymin       = ($reader->getUIBits($NBits) / 20);
$ymax       = ($reader->getUIBits($NBits) / 20);
$reader->byteAlign();
$frameRate  = ($reader->getUI16LE() / 0x100);
$frameCount = $reader->getUI16LE();
// SWF Tags
$imgCnt=0;
$tagsView = array();
array_push($tagsView, 'Tag:'.PHP_EOL);
while (true) {
	$startOffset = $reader->_byte_offset;
	$tagAndLength = $reader->getUI16LE();
	$type = $tagAndLength >> 6;
	$length = $tagAndLength & 0x3f;
	if ($length == 0x3f) {
		$length = $reader->getUI32LE();
	}
	$contents = $reader->getData($length);
	array_push($tagsView, "\t"."type: $type  length: $length tagAndLength: $tagAndLength");
	array_push($tagsView, "\t(".$startOffset." / ".$reader->_byte_offset." / ".($reader->_byte_offset-$startOffset).")");
	array_push($tagsView, PHP_EOL);
	// END Tag
	if ($type == 0) {break;}
	// JPEG -----
	// getImage (jpeg)
	else if($type == 21){
		$img = LIB::data2jpeg($contents);
		file_put_contents($outputPath."/img_".$imgCnt."_type_".$type.".jpg", $img);
		$imgCnt++;
	}
}
if($_REQUEST["d"]=="true"){
	echo 'Signature: '.$signature.PHP_EOL;
	echo 'Version: '.$version.PHP_EOL;
	echo 'FileLength: '.$fileLength.PHP_EOL;
	echo 'FrameSize: '.PHP_EOL;
	echo "\tXmin: ".$xmin.PHP_EOL;
	echo "\tXmax: ".$xmax.PHP_EOL;
	echo "\tYmin: ".$ymin.PHP_EOL;
	echo "\tYmax: ".$ymax.PHP_EOL;
	echo 'FrameRate: '.$frameRate.PHP_EOL;
	echo 'FrameCount: '.$frameCount.PHP_EOL;
	echo join("",$tagsView);
}
// finish
echo "Finished !!".PHP_EOL;
exit(0);
// class-library
class LIB{
	// type:--
	function data2img($contents){
		return substr($contents, 4, strlen($contents)-4);
	}
	// type:20 gif
	function data2gif_rgb($contents){
		$image_id  = ord(substr($contents, 0, 2));
		$compossed = ord(substr($contents, 2, 1));
		$width     = substr($contents, 3, 2);
		$height    = substr($contents, 5, 2);
		//$uncomp    = gzuncompress(substr($contents, 8, strlen($contents)-8));
		//color-table
		$colorTable = "";
		$offset = "";
		//data Bitmap-Format(compossed)==3 only
		if($compossed==3){
			$colortable_num = ord(substr($contents, 7, 1));
			$cnt = (($colortable_num+1)*3);
			for($i=0;$i<=255;$i++){
				if($i<=$colortable_num){
					$colorTable.= substr($contents, 8+($i*3), 3);
				}
				else{
					$colorTable.= pack("h",0).pack("h",0).pack("h",0);
				}
			}
			//$colorTable = substr($contents, 8, $cnt);
			$cnt+=9;
			$pixeldata = substr($contents, $cnt, strlen($contents) - $cnt);
		}
		else{
			$colortable_num = 0;
			$cnt = 7;
			//$pixeldata = gzuncompress(substr($contents, $cnt, strlen($contents) - $cnt));
			$pixeldata = substr($contents, $cnt, strlen($contents) - $cnt);
		}
		// Header --
		$data = "GIF"."87a";
		$data.= pack("v",ord($width)).pack("v",ord($height));
		$data.= pack("h","1");//Global Color Table Flag(1 Bit)
		$data.= pack("h","7").pack("h","7").pack("h","7");//Color Resolution(3 Bits)
		$data.= pack("h","0");//Sort Flag(1 Bit)
		$data.= pack("h","7").pack("h","7").pack("h","7");//Size of Global Color Table(3 Bits)
		$data.= pack("h",0);//Background Color Index(1 Byte)
		$data.= pack("h",0);//Pixel Aspect Ratio(1 Byte)
		$data.= $colorTable;
		// Image --
		$data.= pack("n",0x2C);//Image Separator(1B)
		$data.= pack("v",0).pack("v",0);//Image Left,Top Position(2B,2B)
		$data.= pack("v",ord($width)).pack("v",ord($height));//Image Width,Height(2B,2B)
		$data.= pack("h",0);//Local Color Table Flag(1b)
		$data.= pack("h",0);//Interlace Flag((1b)
		$data.= pack("h",0);//Sort Flag(1b)
		$data.= pack("v",0);//Reserved(2b)
		//$data.= pack("h",0).pack("h",0).pack("h",0);//Size of Local Color Table(3b)--
		$data.= pack("h",0);//LZW Minimum Code Side(1 Byte)
		//$data.= pack("h",strlen($pixeldata));//Block Size(1 Byte)
		$data.= pack("h",0);//Block Size(1 Byte)
		$data.= $pixeldata;
		//$data.= pack("n",0x00);//image-end
		//Tailer
		$data.= pack("n",0x3B);//end
		return $data;
	}
	// type:20 gif
	function data2gif_rgba($contents){
		return substr($contents, 6, strlen($contents)-6);
	}
	// type:21 jpeg2
	function data2jpeg($contents){
		$offset = 4;
		return substr($contents, $offset, strlen($contents)-$offset);
	}
	//type 20 -> png
	function data2png($contents){
		$image_id  = ord(substr($contents, 0, 2));
		$compossed = ord(substr($contents, 2, 1));
		$width     = substr($contents, 3, 2);
		$height    = substr($contents, 5, 2);
		$colortable_num = ord(substr($contents, 7, 1));
		$cnt = (($colortable_num+1)*3);
		$colorTable = substr($contents, 8, $cnt);
		$cnt+=9;
		$pixeldata = substr($contents, $cnt, strlen($contents) - $cnt);
		//--
		$data = pack("n",0x89).pack("n",0x50).pack("n",0x4E).pack("n",0x47);
		$data.= pack("n",0x0D).pack("n",0x0A).pack("n",0x1A).pack("n",0x0A);
		$dataPart = substr($contents, 8, strlen($contents)-8);
		$data.= pack("h",strlen($dataPart));
		$data.= pack("h","aaaa");
		$data.= $dataPart;
		$data.= "";
		return $data;
	}
} 
BitReader.php
今回もyoyaさんのソースを流用してバイナリ読み込みを行なってます。
 
<?php
class BitReader {
    var $_data; // input_data
    var $_byte_offset;
    var $_bit_offset;
    function input($data) {
        $this->_data = $data;
        $this->_byte_offset = 0;
        $this->_bit_offset = 0;
    }
    function byteAlign() {
        if ($this->_bit_offset > 0) {
            $this->_byte_offset ++;
            $this->_bit_offset = 0;
        }
    }
    function getData($length=1) {
        $this->byteAlign();
        $data = substr($this->_data, $this->_byte_offset, $length);
        $data_len = strlen($data);
        $this->_byte_offset += $data_len;
        return $data;
    }
    function getUI8() {
        $this->byteAlign();
        $value = @ord($this->_data{$this->_byte_offset});
        $this->_byte_offset += 1;
        return $value;
    }
    function getUI16LE() {
        $this->byteAlign();
        $ret = @unpack('v', substr($this->_data, $this->_byte_offset, 2));
        $this->_byte_offset += 2;
        return $ret[1];
    }
    function getUI32LE() {
        $this->byteAlign();
        $ret = @unpack('V', substr($this->_data, $this->_byte_offset, 4));
        $this->_byte_offset += 4;
        return $ret[1];
    }
    function getUIBit() {
        $value = @ord($this->_data{$this->_byte_offset});
        $value = 1 & ($value >> (7 - $this->_bit_offset));
        $this->_bit_offset ++;
        if (8 <= $this->_bit_offset) {
            $this->_byte_offset++;
            $this->_bit_offset = 0;
        }
        return $value;
    }
    function getUIBits($width) {
        $value = 0;
        for ($i = 0 ; $i < $width ; $i++) {
            $value <<= 1;
            $value |= $this->getUIBit();
        }
        return $value;
    }
    function getImage() {
        $this->byteAlign();
        $ret = @unpack('v', substr($this->_data, $this->_byte_offset, 2));
        //$this->_byte_offset += 2;
        //return $this->_data;
        //return join(",", $ret);
        return $this->_byte_offset;
        //return $ret[1];
    }
} 
実行
jpegデータの格納されているSWFファイルを利用してください。
※GIF、PNGは無視されます。
$ php getImage.php sample.swf
上記を実行すると「output」というフォルダを自動で作成して、実行IDフォルダ、対象SWFファイルのコピー
そして内部に格納されているJPEGデータが存在する分だけ作成されます。
output/
  20160505000000/
    sample.swf
    img_1.jpg
    img_2.jpg
とりあえず、Jpegは簡単だということはわかりましたが、GIF、とPNGはかなりの難題であることも同時に理解できました。
しばらくは画像フォーマットのSDKとにらめっこしなければいけないようです・・・orz
参考リンク
SWFバイナリ編集のススメ第三回 (JPEG)
 
0 件のコメント:
コメントを投稿