ようこそゲストさん

mitc - 日記

2008/10/26(日) 圧縮SWFファイル(CWSファイル)を展開する方法を調べてC#で試してみた

はてブ 2008/10/26 19:59 .NETmiff

圧縮SWFファイルとは何か

前の日記で色々なファイルをffmpegにかけていたところ、次のようなエラーで処理できないファイルがありました。
[swf @ 0034A5F0]Compressed SWF format not supported
in.swf: I/O error occurred
Usually that means that input file is truncated and/or corrupted.
検索してみたところ、swfファイルの中には圧縮されたものがあり、それは一旦展開しないとffmpegで扱えないとのこと。

参考:windows上のffmpegでCompressed SWFを変換する

さらに検索してみると色々情報とCWS->SWF展開の実装が出てきました。
(圧縮SWFファイルの呼び方がわからなかったので、便宜上CWSファイルと呼びます)
まとめると、次のようになります。
  1. CWSファイルは、先頭3バイトが0x43'C',0x57'W',0x53'S'になっている
  2. CWSファイルは、先頭8バイトがヘッダで、残りの部分が丸ごとzlibで圧縮されている
  3. CWSファイルの先頭の'C'を'F'に変更し、zlib圧縮されている部分を展開してくっつければSWFファイルに変換できる
単純に先頭3バイトを見て"CWS"と書いてあればそれは圧縮されているので、先頭8バイトを取り除いてzlibで展開し、最後に取り除いた8バイトの1バイト目を0x46'F"に書き換えzlibで展開したデータと結合して出力すればOKということに。

CWSファイルとSWFファイルをバイナリエディタで見たところ*1
binary_dump.png
一番手前がSWFファイルで、後ろ3枚がCWSファイル。
先頭3バイトがSWFファイルは"FWS"、CWSファイルは"CWS"となっているのがわかります。

*1 : 使用バイナリエディタはStirling

アーカイバライブラリzlib

ここで問題なのが、「zlibで展開」という部分。
サンプルではどれも純正のzlibを使っていたんですが、Windowsには普通はインストールされていないし、純正のzlib共有モジュールはマネージコードから直接使えないわけで……。
そもそも「zlib形式」で圧縮されているというのがよくわからず、zlibについて調べたところWikipediazlib入門サイトによるとgzip(tar.gzのgz)形式のことなのかな……と推測。
gzipを扱う方法を色々試してみました。
結果は次のようになるのですが、ZLIB.NET以外ではデータ形式が違うと門前払い。
zlib形式でありgzip形式とは違う……ということなのでしょうか?わかりません。

参考:オリジナルのzlib
http://www.zlib.net/

System.IO.Compression.GZipStream

byte[] body = 先頭8バイトを除去したCWSファイルのバイナリ列
MemoryStream bodyStream = new MemoryStream(body);

byte[] buffer = new byte[255];
int length = 0;
System.IO.Compression.GZipStream objGZipStream = new System.IO.Compression.GZipStream(bodyStream, System.IO.Compression.CompressionMode.Decompress);

List<byte> res = new List<byte>();
while (true)
{
	length = objGZipStream.Read(buffer, 0, buffer.Length);
	if (length <= 0) break;
	for (int idx = 0; idx < length; idx++) res.Add(buffer[idx]);
}
body = res.ToArray();
InvalidDataExceptionで次のようなメッセージが返ります
GZip ヘッダーのマジック ナンバーが適切ではありません。GZip ストリームを渡していることを確認してください。

SharpZipLib

byte[] body = 先頭8バイトを除去したCWSファイルのバイナリ列
MemoryStream bodyStream = new MemoryStream(body);

byte[] buffer = new byte[255];
int length = 0;
ICSharpCode.SharpZipLib.GZip.GZipInputStream objGZipInputStream = new ICSharpCode.SharpZipLib.GZip.GZipInputStream(bodyStream);

List<byte> res = new List<byte>();
while (true)
{
	length = objGZipInputStream.Read(buffer, 0, buffer.Length);
	if (length <= 0) break;
	for (int idx = 0; idx < length; idx++) res.Add(buffer[idx]);
}
body = res.ToArray();
GZipExceptionでこんなメッセージが返ります
Error GZIP header, first magic byte doesn't match

ZLIB.NET

byte[] body = 先頭8バイトを除去したCWSファイルのバイナリ列
MemoryStream bodyStream = new MemoryStream(body);

byte[] buffer = new byte[255];
int length = 0;
zlib.ZInputStream objZlibStream = new zlib.ZInputStream(bodyStream);

List<byte> res = new List<byte>();
while (true)
{
	// ReadだとBinaryReaderクラスのメソッドを読んでしまうので注意
	length = objZlibStream.read(buffer, 0, buffer.Length);
	if (length <= 0) break;
	for (int idx = 0; idx < length; idx++) res.Add(buffer[idx]);
}
body = res.ToArray();

C#でCWSファイルからSWFファイルへの変換を行う

というわけで、以上の情報を元にCWSファイルをSWFファイルに変換するプログラムは簡単に作れます。

サンプル

簡単なサンプルのソースコードは次のような感じ。
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using zlib;

namespace CwsTest
{
	class Program
	{
		static void Main(string[] args)
		{
			// 第一引数を入力ファイルパス
			string input = args[0];
			// 第二引数が出力ファイルパス
			string output = args[1];

			// CWSヘッダのサイズ
			int SIZE_HEADER = 8;
			// ファイルからバイト列に読み込み
			List<byte> src = new List<byte>(File.ReadAllBytes(input));
			// 先頭8バイト分をヘッダとして読み込み
			byte[] header = src.GetRange(0, SIZE_HEADER).ToArray();
			// 残りをzlib展開対象として取得
			byte[] body = src.GetRange(SIZE_HEADER, src.Count - SIZE_HEADER).ToArray();
			
			// ZLib.NETに渡すためにバイト列をストリームに接続
			using (MemoryStream bodyStream = new MemoryStream(body))
			{
				// zlib展開読み込みバッファのサイズ
				int BUFFER_SIZE = 255;

				// Zlib.NETの展開用クラスはZInputStream
				ZInputStream zis = new ZInputStream(bodyStream);
				// 通常のストリームと同様に末尾まで読み込んでいく
				List<byte> res = new List<byte>();
				byte[] buffer = new byte[BUFFER_SIZE];
				int len = 0;
				while (true)
				{
					len = zis.read(buffer, 0, buffer.Length);
					if (len == -1) break;
					for (int lci = 0; lci < len; lci++) res.Add(buffer[lci]);
				}
				body = res.ToArray();

				zis.Close();
				bodyStream.Close();
			}

			// ヘッダの一文字目をFにしてヘッダと本体を結合して書き出す
			List<byte> after = new List<byte>();
			header[0] = Convert.ToByte('F');
			after.AddRange(header);
			after.AddRange(body);
			File.WriteAllBytes(output, after.ToArray());
		}
	}
}
サンプルダウンロード:CwsTest.zip

次のように使います
CwsTest.exe 入力CWSファイル.swf 出力SWFファイル名.swf

ライブラリ

折角なので変換ツールとライブラリを作成してみました。
必要な環境は.NET Framework 2.0です。

ダウンロード:CsCws2Swf.zip
使い方
・"CsCws2FwsLib.dll"を参照に追加
・名前空間は"CsCws2FwsLib"
・参照できる場所に"zlib.dll"を置く
new CsCws2FwsEngine().Cws2Fws("入力CWSファイルパス", "出力SWFファイルパス");


名前:  非公開コメント   

E-Mail(任意/非公開):
URL(任意):
  • TB-URL  http://mitc.s279.xrea.com/diary/089/tb/