Excel VBAでファイル解析(Directorファイル編)

2024-01-05

■Directorファイルの概要

DirectorファイルはAdobe Director(※1)で作成されたファイルで、ファイルの拡張子は「.dir」です。

(※1)Adobe Directorはオーサリングソフトで、タイムライン上に画像や音声を配置してアニメーションを作成可能です(作成したコンテンツを保存したものが、Directorファイルです)。
1980~1990年代にはゲーム・ソフト等に使用されていたようですが、2017年に販売を終了しています。
なお、Wikipediaによると、当初の製品名は「MacroMind Video Works」でしたが、後に「MacroMind Director」→「Macromedia Director」→「Adobe Director」と名称変更されています。

筆者はDirectorという製品を知らなかったんですが、たまたま入手したPippin@(※2)版のソフトがDirectorを使用していたため、Directorファイルの内部構造を調べたという経緯です。
(筆者はPippin@のハードを持っていないため、Excel VBAを駆使して何とか画像だけは見ることができました)

(※2)Pippin@は1990年代にAppleとバンダイが共同開発したゲーム機です。

Directorファイルの仕様を全て理解している訳ではないので、本稿では画像データを取扱うサンプル・プログラムを中心に説明します。
なお、動作確認に使用したDirectorファイルはMAC版のVersion 5で作成されたものです。
⇒Directorファイルの仕様について、公式に開示されている情報はほとんどありませんが、Anthony Kleine(Team Earthquake)のサイト(More Director Movie File Unofficial Documentation)とGitHubで公開されているAbraham Macias Paredesのプログラム(Macromedia Director 5 DRI and DRX files data extractor)が参考になります。

なお、Adobe Directorに固有の概念を予め把握しておいた方が理解し易いと思いますので、「ユーザガイド」の一部を以下に引用しておきます。

  • (Director のメタファーの理解)
  • Director のユーザインターフェイスのコンセプトは、映画制作のプロセスを模倣しています。
    Director で作成する各プロジェクトは、出演俳優(キャスト)、音楽(スコア)、アクションが行われる舞台(ステージ)、および監督(プロジェクトの作者 = ディレクタ)から成り立つ映画と考えることができます。
    サウンド、ビデオ、画像、テキスト、ボタンなどのムービーの各メディアエレメントも、映画のキャストのメンバと見なすことができます。
  • (中略)
  • 実際の映画と同様に、Director の各ムービーにはスコアがあります。
    しかし Director ムービーのスコアには音楽以外のものもあります。
    Director のスコアウィンドウ内には、各キャストメンバがステージ上にいつ、どの位置に表示されるかについての情報が含まれています。
    スコアは、ムービーのアクションを定義するものです。

Directorファイルの構成

Directorファイルの基本構造はRIFF(Resource Interchange File Format)フォーマットになっており、RIFFチャンクの中にDirector固有のサブチャンクが配置されています。

(Directorファイルの基本構造)

識別子
('RIFX’)
サイズ
(ファイル長
-8)
フォーム
タイプ
('MV93’)
データ
(サブチャンクの集まり)

Directorファイルのチャンク

Directorファイルに含まれる主なチャンクは下表の通りですが、画像データにアクセスするために必要なチャンクは、括弧書きで簡単な説明を付けた前半の10種類になります。
この10種類のチャンクについて、次項で詳細を説明します。

(主なチャンクの種類)

識別子
(チャンクID)
説明
RIFX Archive Header(またはRIFX Header)
(Directorファイル全体)
imap Input Map
(先頭に配置され、mmapの位置を示すポインター)
mmap Memory Map
(各チャンクの位置とサイズを示すテーブル)
KEY* Key Mapping Pointers
(Cast MemberとResourceの対応関係を示すテーブル)
CAS* Cast Mapping Pointers
(Cast IDとResource IDの対応関係を示すテーブル)
VWCF Video Works Configuration
(Directorファイルの構成情報を保有)
VWSC Video Works Score
(コンテンツの詳細情報(Channel Data)を保有)
CASt Cast Member Pointers
(Cast Memberの位置、Resource IDと基本情報を保有)
CLUT Color Lookup Table
(カラーパレット)
BITD Bitmap Data
(ビットマップデータ)
VWFI Video Works File Information
VWFM VWFM
Sord Score Order
Lctx Lingo Context
Lscr Lingo Script
THUM Thumbnail

Archive Header(またはRIFX Header)のフォーマット

Archive Header(またはRIFX Header)のフォーマットは、下表の通りです。

後続の処理で必要となる情報は保有していません(ファイルの形式をチェックする際に使う位)ので、読み飛ばして大丈夫です。

(Archive Header(またはRIFX Header)のフォーマット)

オフセット 長さ 説明
0 4 Chunk ID('RIFX’)
4 4 Block Size(ファイルサイズ-8)
8 4 Format Name('MV93’)

imap(Input Map)のフォーマット

imap(Input Map)のフォーマットは、下表の通りです。

imapはArchive Header(またはRIFX Header)の直後に配置され、mmapの開始位置を示します。
多くのDirectorファイルでは、imapの直後にmmapが配置されていますが、mmapが複数存在するファイルもありますので、必ずimapが指している方のmmapを使用するようにして下さい。

(imap(Input Map)のフォーマット)

オフセット 長さ 説明
0 4 Chunk ID('imap’)
4 4 Data Size(X’00000018′)
8 4 Memory Map Count(X’00000001′)
12 4 Memory Map Offset(mmapの開始位置)
16 4 Memory Map File Version(X’00000000′)
20 2 Reserved
22 2 Unknown
24 4 Reserved

mmap(Memory Map)のフォーマット

mmap(Memory Map)のフォーマットは、下表の通りです。

mmapは「Properties(固定長)」と「Resources(可変長)」の2つの部分に分かれており、Propertiesの中にResourcesの1エントリーのサイズやエントリー数を保有し、Resourcesの各エントリーが全てのチャンクの開始位置とサイズを保有しています。
個々のチャンクにアクセスする際には、mmapで該当するチャンクの開始位置を取得する必要があります。
下表の中に「Resource ID」という単語が出て来ますが、これはDirectorファイル内のチャンクの連番で、Resourcesのエントリー番号と一致しています(0がRIFX、1がimap、2がmmap、・・・という形になっています)。

(mmap(Memory Map)のフォーマット)

オフセット 長さ 説明
Properties
0 4 Chunk ID('mmap’)
4 4 Data Size
8 2 Properties Size(X’0018′)
(Propertiesのサイズ)
10 2 Resource Size(X’0014′)
(Resources Arrayの1エントリーのサイズ)
12 4 Max Resource Count
(Resources Arrayの最大エントリー数)
16 4 Used Resource Count
(Resources Arrayの使用中のエントリー数)
20 4 First Junk Resource ID
24 4 Old Memory Map Resource ID
28 4 First Free Resource ID
Resources Array(各エントリのフォーマットは以下の通りです)
0 4 Chunk ID
4 4 Size
8 4 Offset
12 2 Flags
14 2 Unused
16 4 Next Resource ID

KEY*(Key Mapping Pointers)のフォーマット

KEY*(Key Mapping Pointers)のフォーマットは、下表の通りです。

KEY*は「Properties(固定長)」と「Mapping Pointers Array(可変長)」の2つの部分に分かれており、Propertiesの中にMapping Pointers Arrayの1エントリーのサイズやエントリー数を保有し、Mapping Pointers Arrayの各エントリーがCast MemberとResourceの対応関係を保有しています。
Cast Memberが画像の場合は、Mapping Pointers Arrayが2エントリー使用され、1つはBITD、もう1つはTHUMと対応付けられています。

(KEY*(Key Mapping Pointers)のフォーマット)

オフセット 長さ 説明
Properties
0 4 Chunk ID('KEY*’)
4 4 Data Size
8 2 Properties Size(X’000C’)
(Propertiesのサイズ)
10 2 Key Size(X’000C’)
(Mapping Pointers Arrayの1エントリーのサイズ)
12 4 Max Key Count
(Mapping Pointers Arrayの最大エントリー数)
16 4 Used Key Count
(Mapping Pointers Arrayの使用中のエントリー数)
Mapping Pointers Array(各エントリのフォーマットは以下の通りです)
0 4 Owned Resource ID
(所有されるResourceのResource ID)
4 4 Owner Resource ID
(所有するCast MemberのResource ID)
8 4 Owned Chunk ID
(所有されるResourceのChunk ID)

CAS*(Cast Table)のフォーマット

CAS*(Cast Table)のフォーマットは、下表の通りです。

CAS*はCast IDとResource IDの対応関係を示すテーブルです。
データとしてはResource IDしか保有しておらず、エントリー番号とCast IDが1対1に対応しています(※)。

(※)次項で説明するVWCFのCast Array Startの値が1エントリー目のCast IDになります。

(CAS*(Cast Table)のフォーマット)

オフセット 長さ 説明
0 4 Chunk ID('CAS*’)
4 4 Data Size
Cast Table Array(各エントリのフォーマットは以下の通りです)
0 4 Resource ID

VWCF(Video Works Configuration)のフォーマット

VWCF(Video Works Configuration)のフォーマットは、下表の通りです。

(VWCF(Video Works Configuration)のフォーマット)

オフセット 長さ 説明
0 4 Chunk ID('VWCF’)
4 4 Data Size(X’00000050′)
8 2 Length(Data sizeと同値)
10 2 Version Number
12 2 Stage Top
14 2 Stage Left
16 2 Stage Bottom
18 2 Stage Right
20 2 Cast Array Start
22 2 Cast Array End
24 説明省略(Frame Rate、Stage Color等)

VWSC(Video Works Score)のフォーマット

VWSC(Video Works Score)のフォーマットは、下表の通りです。

スコアは複数のチャネルから構成され、チャネルは複数のフレームから構成されています(Adobe Directorのスコア・ウィンドウでは、行がチャネル、列がフレームのイメージになっています)。
下表の最後に記載したChannel Dataは、重要なデータを保有しているうえ、特殊な方式で圧縮されていて難解なため、次項で詳細を説明します。

(VWSC(Video Works Score)のフォーマット)

オフセット 長さ 説明
0 4 Chunk ID('VWSC’)
4 4 Data Size
8 4 Frames End Offset(Data sizeと同値)
12 4 Unknown
16 4 Frames Size
20 2 Frames Type
22 2 Frame Size(X’0014′)
24 2 Channel Count
26 2 Last Channel
28 可変長 Channel Data

フレームとChannel Dataの詳細

スコアの最小単位であるフレームは20バイト(固定)ですが、チャネルの種類によって保有する項目が異なります。
基本チャネル、パレット・チャネル、スプライト・チャネルのフレーム構造は、下表の通りです。
(筆者が動作確認に使用したDirectorファイルの場合、1つ目のチャネルが基本チャネル、2つ目がパレット・チャネル、3つ目または4つ目にスプライト・チャネルが定義されていました)

(基本チャネルのフレーム構造)

オフセット 長さ 説明
0 2 Flags
2 1 Transition Duration
3 1 Transition Chunk Size
4 1 Frames Per Second
5 1 Transition ID
6 2 Sound1 Cast
8 2 Sound2 Cast
10 2 Sound Flags
12 2 Unknown
14 2 Unknown
16 2 Script
18 2 Unknown

(パレット・チャネルのフレーム構造)

オフセット 長さ 説明
0 2 Palette ID(CLUTのCast ID)
2 2 Unknown
4 1 Operation
5 1 FPS
6 2 Unknown
8 2 Cycles
10 10 Unknown

(スプライト・チャネルのフレーム構造)

オフセット 長さ 説明
0 2 Sprite Type
2 1 Foreground Color
3 1 Background Color
4 1 Flags
5 1 Ink Type
6 2 Cast Id
8 2 Y
10 2 X
12 2 Height
14 2 Width
16 2 Flag1
18 2 Flag2

各チャネルのフレーム・データはX’00’の部分が多いため、VWSCに保有しているChannel DataはX’00’以外の値のみを保有する方式で、データを圧縮しています。

(VWSCに保有しているChannel Dataの形式)

オフセット 長さ 説明
0 2 圧縮データのサイズ
2 2 展開後のオフセット
4 可変長 展開する値
  • (Channel Dataの例)
  • 00 02 00 0A FF FF
  • X’0002′:圧縮データのサイズ
  • X’000A’:展開後のオフセット
  • X’FFFF’:展開する値
  • (展開後のフレーム)
  • 00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00 00 00

CASt(Cast Member Pointers)のフォーマット

CASt(Cast Member Pointers)の内容はData Typeによって異なりますが、画像イメージ(Data Type=1)のフォーマットは、下表の通りです。

(CASt(Cast Member Pointers))

オフセット 長さ 説明
0 4 Chunk ID('CASt’)
4 4 Data Size
8 2 Header Size
10 4 Additional Size
14 1 Data Type
1:Image
3:Text Input
4:CLUT
6:SND
7:Push Button
8:Shape
11:LSCR
12:Text
14:Tran
15 1 Flags
16 1 BMP BPP Value
X’80’:8 bit per pixel image
X’81’:4 bit per pixel image
X’82’:8 bit per pixel image
X’84’:16 bit per pixel image
X’85’:16 bit per pixel image(MAC format)
X’8A’:24 bit per pixel image
17 1 Unknown
18 2 Height Padding
20 2 Width Padding
22 2 BMP Height
24 2 BMP Width
26 2 Top
28 2 Left
30 2 Bottom
32 2 Right
34 2 Center Y
36 2 Center X
38 2 Bit Depth
40 2 Palette
42 説明省略(Cast Memberの名称等)

CLUT(Color Lookup Table)のフォーマット

8ビット/ピクセル(256色)の場合、CLUT(Color Lookup Table)のフォーマットは、下表の通りです。

(CLUT(Color Lookup Table)のフォーマット)

オフセット 長さ 説明
0 4 Chunk ID('CLUT’)
4 4 Data Size(X’00000600′)
8 1536 Color Palette Data

Color Palette Dataは、BMPファイルのRGBTRIPLEと同じような形式ですが、R→G→Bの順で2バイトずつ保有しており、1バイト目と2バイト目に同じ値が入っています。
BMPファイルのカラーパレットはB→G→Rの順に格納する仕様のため、順番を入れ替える必要があります。

BITD(Bitmap Data)のフォーマット

BITD(Bitmap Data)のフォーマットは、下表の通りです。

(BITD(Bitmap Data)のフォーマット)

オフセット 長さ 説明
0 4 Chunk ID('BITD’)
4 4 Data Size
8 可変長 Bitmap Data

Bitmap Dataはランレングス圧縮(RLE:Run Length Encoding、連長圧縮)されており、レングス(1バイト)+データの形式で格納されています。
レングス≧X’80’の場合は次の1バイトを(256 – レングス + 1)バイトにセット、レングス<X’80’の場合は次のデータからレングス分のバイトを転送する方式で復元できます。

なお、ビットマップ・データは画像イメージの上→下の順に配置されているため、BMPファイルに出力する際は下→上に並べ替える必要があります。

レングス データ ・・・ データ

■Directorファイルから画像データを抽出する処理

Directorファイルを読み込み、画像データをファイルに出力するプログラムです。
⇒サンプル・プログラムで使用している、Binary Fileクラスの詳細については、Excel VBAでファイル解析(事前準備編)を参照して下さい。
⇒また、BMPファイルの詳細については、Excel VBAでファイル解析(BMPファイル編)を参照して下さい。

データ宣言部とメイン・プロシジャ

処理の流れは以下の通りです。
①mmap、KEY*、CAS*、VWSCをワーク・エリアに展開
②mmapからBITDを検索し、対応するCLUTとBITDをBMPファイルに出力

(※)今回使用したDirectorファイルはMAC版で作成されたファイルだったので、数値データ(サイズ、オフセット等)がビッグ・エンディアンで格納されていましたが、Windows版で作成されたDirectorファイルはリトル・エンディアンで格納されているため、計算に使用する場合はエンディアン変換が必要です。

  1. Dim bf As Object
  2. Dim OUTBMPpath As String
  3. Dim OUTBMPlen As Long
  4. Dim OUTBMPnum As Integer
  5. Dim OUTBMPbuf() As Byte
  6. Dim mmapChunkID() As String
  7. Dim mmapOffset() As Long
  8. Dim keyOwnedResourceID() As Long
  9. Dim keyOwnerResourceID() As Long
  10. Dim casResourceID() As Long
  11. Dim vwscHeight() As Integer
  12. Dim vwscWidth() As Integer
  13. Dim vwscPaletteID() As Integer
  14. Dim vwscOffset As Long
  15. Dim CastStart As Integer
  16. Dim CastEnd As Integer
  17. Dim CastID As Integer
  18. Dim ResourceID As Long
  19. Dim clut(0 To 1023) As Byte
  20. Dim bitd() As Byte
  21. Dim bitw() As Byte
  22. Dim bmpd() As Byte
  23. Dim wID As Variant
  24. Dim wLen As Variant
  25. Dim wOff As Variant
  26. Dim wTyp As Variant
  27. Dim wStr As String
  28. Dim fromBITD As Long
  29. Dim toBITD As Long
  30. Dim fromCLUT As Long
  31. Dim toCLUT As Long
  32. Dim EntNum As Long
  33. Dim EntLen As Long
  34. Dim FrameSize As Long
  35. Dim ChannelCount As Long
  36. Dim ChannelSize As Long
  37. Dim ChannelData() As Byte
  38. Dim DeltaSize As Long
  39. Dim DeltaOffset As Long
  40. Dim bmp_height As Long
  41. Dim wHeight As Variant
  42. Dim bmp_width As Long
  43. Dim bmp_wlimit As Long
  44. Dim wWidth As Variant
  45. Dim h_padding As Integer
  46. Dim w_padding As Integer
  47. Dim PaletteID As Integer
  48. Dim bmp_bpp As Integer
  49. Dim wSize As Variant
  50. Dim values As Long
  51. Dim previous_encoded As Boolean
  52. Dim previous_run_length As Long
  53. Dim wval As Byte
  54. Dim run_length As Long
  55. Dim run_value As Byte
  56. Dim si As Long
  57. Dim di As Long
  58. Dim i As Long
  59. Dim j As Long
  60. Dim k As Long
  61. Dim l As Long
  62. Dim w As Long
  63. Dim x As Long
  64. Dim y As Long
  65. Dim idx As Long
  66. Dim pos As Long
  67. Dim buf As String
  68. Dim wStrPath As String
  69. Dim wByte0, wByte1, wByte2, wByte3, wByte4, wByte5 As Byte
  70. Private Sub Sample1()
  71.     Set bf = New BinaryFile
  72.     bf.InputFile (“C:\work\xxx.DIR")
  73.     Call mmap展開
  74.     Call KEY展開
  75.     Call CAS展開
  76.     Call VWSC展開
  77.     For i = 0 To UBound(mmapChunkID)
  78.         If mmapChunkID(i) = “BITD" Then
  79.             For j = 0 To UBound(keyOwnedResourceID)
  80.                 If i = keyOwnedResourceID(j) Then
  81.                     Exit For
  82.                 End If
  83.             Next j
  84.             If j > UBound(keyOwnedResourceID) Then
  85.                 MsgBox “BITD ResourceID is not found!"
  86.                 Exit Sub
  87.             End If
  88.             Call CASt抽出
  89.             Call ヘッダ出力
  90.             Call CLUT出力
  91.             Call BITD出力
  92.         End If
  93.     Next i
  94.     Set bf = Nothing
  95. End Sub

mmapを展開するサブ・プロシジャ

行番号6でmmapの開始位置を取得し、行番号14~25の繰返し処理でmmapのデータをワーク・エリアに展開しています。

  1. Public Sub mmap展開()
  2. ' [Archive Header]
  3.     bf.SkipData (12)
  4. ' [Input Map]
  5.     bf.SkipData (12)
  6.     bf.CurrentPosition = bf.GetData(4)
  7. ' [Memory Map]
  8.     bf.SkipData (16)
  9.     EntNum = bf.GetData(4)
  10.     ReDim mmapChunkID(0 To EntNum – 1)
  11.     ReDim mmapOffset(0 To EntNum – 1)
  12.     bf.SkipData (12)
  13.     y = y + 1
  14.     For j = 1 To EntNum
  15.         wByte0 = bf.GetData(1)
  16.         bf.CurrentPosition = bf.CurrentPosition – 1
  17.         If (wByte0 >= Val(&H2A)) And (wByte0 <= Val(&H7A)) Then
  18.             wID = bf.GetDataChar(4)
  19.             wLen = bf.GetDataHex(4)
  20.             wOff = bf.GetDataHex(4)
  21.             mmapChunkID(j – 1) = wID
  22.             mmapOffset(j – 1) = CLng(“&H" & wOff)
  23.             bf.SkipData (8)
  24.         End If
  25.     Next j
  26. End Sub

KEY*を展開するサブ・プロシジャ

行番号11でKEY*の開始位置を取得し、行番号20~37の繰返し処理でKEY*のデータをワーク・エリアに展開しています。

  1. Public Sub KEY展開()
  2.     For j = 0 To UBound(mmapChunkID)
  3.         If mmapChunkID(j) = “KEY*" Then
  4.             Exit For
  5.         End If
  6.     Next j
  7.     If j > UBound(mmapChunkID) Then
  8.         MsgBox “KEY* is not found!"
  9.         Exit Sub
  10.     End If
  11.     bf.CurrentPosition = mmapOffset(j)
  12. ' [Key Mapping Pointers-Properties]
  13.     bf.SkipData (10)
  14.     EntLen = bf.GetData(2)
  15.     EntNum = bf.GetData(4)
  16.     bf.SkipData (4)
  17.     ReDim keyOwnedResourceID(0 To EntNum – 1)
  18.     ReDim keyOwnerResourceID(0 To EntNum – 1)
  19. ' [Key Mapping Pointers-Mapping Pointers Array]
  20.     For j = 1 To EntNum
  21.         wByte0 = bf.GetData(1)
  22.         bf.SkipData (3)
  23.         wByte1 = bf.GetData(1)
  24.         bf.SkipData (3)
  25.         wByte2 = bf.GetData(1)
  26.         wByte3 = bf.GetData(1)
  27.         wByte4 = bf.GetData(1)
  28.         wByte5 = bf.GetData(1)
  29.         bf.CurrentPosition = bf.CurrentPosition – 12
  30.         If (wByte0 < Val(&H80)) And (wByte1 < Val(&H70)) And (wByte2 >= Val(&H2A)) And (wByte2 <= Val(&H7A)) And (wByte3 >= Val(&H2A)) And (wByte3 <= Val(&H7A)) And (wByte4 >= Val(&H2A)) And (wByte4 <= Val(&H7A)) And (wByte5 >= Val(&H2A)) And (wByte5 <= Val(&H7A)) Then
  31.             keyOwnedResourceID(j – 1) = bf.GetData(4)
  32.             keyOwnerResourceID(j – 1) = bf.GetData(4)
  33.             bf.SkipData (4)
  34.         Else
  35.             bf.SkipData (EntLen)
  36.         End If
  37.     Next j
  38. End Sub

CAS*を展開するサブ・プロシジャ

行番号11でVWCFの開始位置を取得し、行番号14~15でCast IDの開始/終了を把握したうえ、行番号26でCAS*の開始位置を取得し、行番号31~33の繰返し処理でCAS*のデータをワーク・エリアに展開しています。

  1. Public Sub CAS展開()
  2.     For j = 0 To UBound(mmapChunkID)
  3.         If mmapChunkID(j) = “VWCF" Then
  4.             Exit For
  5.         End If
  6.     Next j
  7.     If j > UBound(mmapChunkID) Then
  8.         MsgBox “VWCF is not found!"
  9.         Exit Sub
  10.     End If
  11.     bf.CurrentPosition = mmapOffset(j)
  12. ' [Video Works Configuration]
  13.     bf.SkipData (20)
  14.     CastStart = bf.GetData(2)
  15.     CastEnd = bf.GetData(2)
  16.     ReDim casResourceID(0 To CastEnd)
  17.     For j = 0 To UBound(mmapChunkID)
  18.         If mmapChunkID(j) = “CAS*" Then
  19.             Exit For
  20.         End If
  21.     Next j
  22.     If j > UBound(mmapChunkID) Then
  23.         MsgBox “CAS* is not found!"
  24.         Exit Sub
  25.     End If
  26.     bf.CurrentPosition = mmapOffset(j)
  27. ' [Cast Mapping Pointers]
  28.     bf.SkipData (4)
  29.     wLen = bf.GetDataHex(4)
  30.     EntNum = (CLng(“&H" & wLen) – 1) / 4
  31.     For j = 1 To EntNum
  32.         casResourceID(CastStart + j – 1) = bf.GetData(4)
  33.     Next j
  34. End Sub

VWSCを展開するサブ・プロシジャ

行番号11でVWSCの開始位置を取得し、行番号32~68でChannel Dataを展開したうえ、Height、Width、Palette IDをワーク・エリアに退避しています。

  1. Public Sub VWSC展開()
  2.     For j = 0 To UBound(mmapChunkID)
  3.         If mmapChunkID(j) = “VWSC" Then
  4.             Exit For
  5.         End If
  6.     Next j
  7.     If j > UBound(mmapChunkID) Then
  8.         MsgBox “VWSC is not found!"
  9.         Exit Sub
  10.     End If
  11.     bf.CurrentPosition = mmapOffset(j)
  12.     vwscOffset = mmapOffset(j)
  13.     ReDim vwscHeight(0 To CastEnd)
  14.     ReDim vwscWidth(0 To CastEnd)
  15.     ReDim vwscPaletteID(0 To CastEnd)
  16.     For j = 0 To UBound(vwscHeight)
  17.         vwscHeight(j) = 0
  18.         vwscWidth(j) = 0
  19.         vwscPaletteID(j) = 0
  20.     Next j
  21. ' [Video Works Score]
  22.     bf.SkipData (4)
  23.     wLen = bf.GetDataHex(4)
  24.     bf.SkipData (14)
  25.     FrameSize = bf.GetData(2)
  26.     ChannelCount = bf.GetData(2)
  27.     bf.SkipData (2)
  28.     ReDim ChannelData(0 To ChannelCount * FrameSize – 1)
  29.     For j = 0 To UBound(ChannelData)
  30.         ChannelData(j) = 0
  31.     Next j
  32.     Do While bf.CurrentPosition < vwscOffset + CLng(“&H" & wLen) + 8
  33. ' [Channel Data]"
  34.         ChannelSize = bf.GetData(2) – 2
  35.         Do While ChannelSize > 0
  36.             DeltaSize = bf.GetData(2)
  37.             DeltaOffset = bf.GetData(2)
  38.             ChannelSize = ChannelSize – 4
  39.             For j = 1 To DeltaSize
  40.                 ChannelData(DeltaOffset + j – 1) = bf.GetData(1)
  41.                 ChannelSize = ChannelSize – 1
  42.             Next j
  43.         Loop
  44.         If (Right(“0" & Hex(ChannelData(46)), 2) & Right(“0" & Hex(ChannelData(47)), 2)) <> “0000" Then
  45.             CastID = CInt(“&H" & Right(“0" & Hex(ChannelData(46)), 2) & Right(“0" & Hex(ChannelData(47)), 2))
  46.             If vwscHeight(CastID) = 0 Then
  47.                 vwscHeight(CastID) = CInt(“&H" & Right(“0" & Hex(ChannelData(52)), 2) & Right(“0" & Hex(ChannelData(53)), 2))
  48.             End If
  49.             If vwscWidth(CastID) = 0 Then
  50.                 vwscWidth(CastID) = CInt(“&H" & Right(“0" & Hex(ChannelData(54)), 2) & Right(“0" & Hex(ChannelData(55)), 2))
  51.             End If
  52.             If vwscPaletteID(CastID) = 0 Then
  53.                 vwscPaletteID(CastID) = CInt(“&H" & Right(“0" & Hex(ChannelData(20)), 2) & Right(“0" & Hex(ChannelData(21)), 2))
  54.             End If
  55.         End If
  56.         If (Right(“0" & Hex(ChannelData(66)), 2) & Right(“0" & Hex(ChannelData(67)), 2)) <> “0000" Then
  57.             CastID = CInt(“&H" & Right(“0" & Hex(ChannelData(66)), 2) & Right(“0" & Hex(ChannelData(67)), 2))
  58.             If vwscHeight(CastID) = 0 Then
  59.                 vwscHeight(CastID) = CInt(“&H" & Right(“0" & Hex(ChannelData(72)), 2) & Right(“0" & Hex(ChannelData(73)), 2))
  60.             End If
  61.             If vwscWidth(CastID) = 0 Then
  62.                 vwscWidth(CastID) = CInt(“&H" & Right(“0" & Hex(ChannelData(74)), 2) & Right(“0" & Hex(ChannelData(75)), 2))
  63.             End If
  64.             If vwscPaletteID(CastID) = 0 Then
  65.                 vwscPaletteID(CastID) = CInt(“&H" & Right(“0" & Hex(ChannelData(20)), 2) & Right(“0" & Hex(ChannelData(21)), 2))
  66.             End If
  67.         End If
  68.     Loop
  69. End Sub

CAStを抽出するサブ・プロシジャ

行番号2~7でBITDのResource IDでKEY*を検索してCAStのResource IDを取得、行番号12~17でCAStのResource IDでCAS*を検索してCast IDを取得した後、行番号22でCAStの開始位置を取得し、行番号26~42でHeight、Width、Palette IDを取得しています。

BMPファイルに使用するHeight、Widthは、パディング分を差し引く必要があるため、行番号33、35で減算処理を行っています。
また、Height、Width、Palette IDは、CAStとVWSC(Channel Data)に格納可能で、通常は「どちらか一方に格納」または「両方に同じ値が格納」されていますが、中には両方に値を保有し、かつ値が異なっているケースがあり、その場合にはChannel Dataの方を優先する必要があります(行番号43~51でChannel Dataの値で上書きしています)。

  1. Public Sub CASt抽出()
  2.     For j = 0 To UBound(keyOwnedResourceID)
  3.         If keyOwnedResourceID(j) = i Then
  4.             ResourceID = keyOwnerResourceID(j)
  5.             Exit For
  6.         End If
  7.     Next j
  8.     If j > UBound(keyOwnedResourceID) Then
  9.         MsgBox “CASt(KEY*) is not found!"
  10.         Exit Sub
  11.     End If
  12.     For j = 0 To UBound(casResourceID)
  13.         If casResourceID(j) = ResourceID Then
  14.             CastID = j
  15.             Exit For
  16.         End If
  17.     Next j
  18.     If j > UBound(casResourceID) Then
  19.         MsgBox “CASt(CAS*) is not found!"
  20.         Exit Sub
  21.     End If
  22.     bf.CurrentPosition = mmapOffset(ResourceID)
  23. ' [Cast Member Pointers]
  24.     bf.SkipData (14)
  25.     wTyp = bf.GetDataHex(1)
  26.     If wTyp = “01" Then
  27.         bf.SkipData (3)
  28.         wStr = Right(“000" & bf.GetDataHex(2), 4)
  29.         h_padding = CInt(“&H" & wStr)
  30.         wStr = Right(“000" & bf.GetDataHex(2), 4)
  31.         w_padding = CInt(“&H" & wStr)
  32.         bmp_height = bf.GetData(2)
  33.         bmp_height = bmp_height – h_padding
  34.         bmp_width = bf.GetData(2)
  35.         bmp_width = bmp_width – w_padding
  36.         If (bmp_width Mod 2) <> 0 Then
  37.             bmp_width = bmp_width + 1
  38.         End If
  39.         bf.SkipData (14)
  40.         wStr = Right(“000" & bf.GetDataHex(2), 4)
  41.         PaletteID = CInt(“&H" & wStr)
  42.     End If
  43.     If (vwscHeight(CastID) <> 0) And (bmp_height = 0) Then
  44.         bmp_height = vwscHeight(CastID)
  45.     End If
  46.     If (vwscWidth(CastID) <> 0) And (bmp_width = 0) Then
  47.         bmp_width = vwscWidth(CastID)
  48.     End If
  49.     If vwscPaletteID(CastID) <> 0 Then
  50.         PaletteID = vwscPaletteID(CastID)
  51.     End If
  52. End Sub

BMPファイルのヘッダを作成するサブ・プロシジャ

ワーク・エリアに展開された情報を基にBMPファイルのヘッダを作成しています。

  1. Public Sub ヘッダ出力()
  2.     OUTBMPpath = “C:\work\xxx" & “-" & Format(CastID, “000") & “.bmp"
  3.     bmp_wlimit = bmp_width
  4.     If bmp_width Mod 4 <> 0 Then
  5.         bmp_width = bmp_width + 4 – (bmp_width Mod 4)
  6.     End If
  7.     If bmp_wlimit Mod 2 <> 0 Then
  8.         bmp_wlimit = bmp_wlimit + 1
  9.     End If
  10.     bmp_bpp = 8
  11.     values = 14 + 40 + (256 * 4) + bmp_width * bmp_height
  12.     If values Mod 2 <> 0 Then
  13.         values = values + 1
  14.     End If
  15.     wLen = Right(“0000000" & Hex(values), 8)
  16.     OUTBMPlen = values
  17.     If OUTBMPlen Mod 2 <> 0 Then
  18.         OUTBMPlen = OUTBMPlen + 1
  19.     End If
  20.     OUTBMPnum = FreeFile
  21.     Open OUTBMPpath For Binary Access Write As #OUTBMPnum
  22.     ReDim OUTBMPbuf(0 To OUTBMPlen – 1)
  23.     di = 0
  24.     OUTBMPbuf(di) = &H42
  25.     di = di + 1
  26.     OUTBMPbuf(di) = &H4D
  27.     wStr = bf.ConvertEndian(wLen)
  28.     For j = 1 To 7 Step 2
  29.         di = di + 1
  30.         OUTBMPbuf(di) = CByte(“&H" & Mid(wStr, j, 2))
  31.     Next j
  32.     For j = 1 To 4
  33.         di = di + 1
  34.         OUTBMPbuf(di) = &H0
  35.     Next j
  36.     di = di + 1
  37.     OUTBMPbuf(di) = &H36
  38.     di = di + 1
  39.     OUTBMPbuf(di) = &H4
  40.     For j = 1 To 2
  41.         di = di + 1
  42.         OUTBMPbuf(di) = &H0
  43.     Next j
  44.     di = di + 1
  45.     OUTBMPbuf(di) = &H28
  46.     For j = 1 To 3
  47.         di = di + 1
  48.         OUTBMPbuf(di) = &H0
  49.     Next j
  50.     wWidth = Right(“0000000" & Hex(bmp_width), 8)
  51.     wStr = bf.ConvertEndian(wWidth)
  52.     For j = 1 To 7 Step 2
  53.         di = di + 1
  54.         OUTBMPbuf(di) = CByte(“&H" & Mid(wStr, j, 2))
  55.     Next j
  56.     wHeight = Right(“0000000" & Hex(bmp_height), 8)
  57.     wStr = bf.ConvertEndian(wHeight)
  58.     For j = 1 To 7 Step 2
  59.         di = di + 1
  60.         OUTBMPbuf(di) = CByte(“&H" & Mid(wStr, j, 2))
  61.     Next j
  62.     di = di + 1
  63.     OUTBMPbuf(di) = &H1
  64.     di = di + 1
  65.     OUTBMPbuf(di) = &H0
  66.     di = di + 1
  67.     OUTBMPbuf(di) = &H8
  68.     di = di + 1
  69.     OUTBMPbuf(di) = &H0
  70.     For j = 1 To 4
  71.         di = di + 1
  72.         OUTBMPbuf(di) = &H0
  73.     Next j
  74.     wSize = Right(“0000000" & Hex(bmp_width * bmp_height), 8)
  75.     wStr = bf.ConvertEndian(wSize)
  76.     For j = 1 To 7 Step 2
  77.         di = di + 1
  78.         OUTBMPbuf(di) = CByte(“&H" & Mid(wStr, j, 2))
  79.     Next j
  80.     For j = 1 To 16
  81.         di = di + 1
  82.         OUTBMPbuf(di) = &H0
  83.     Next j
  84. End Sub

BMPファイルのカラー・パレットを作成するサブ・プロシジャ

ワーク・エリアに展開された情報を基にBMPファイルのカラー・パレットを作成しています。

行番号30でCALLしているシステム・パレットの処理については、次項で説明します。

  1. Public Sub CLUT出力()
  2.     If PaletteID > 0 Then
  3.         For j = 0 To UBound(keyOwnerResourceID)
  4.             If keyOwnerResourceID(j) = casResourceID(PaletteID) Then
  5.                 ResourceID = keyOwnedResourceID(j)
  6.                 Exit For
  7.             End If
  8.         Next j
  9.         If j > UBound(keyOwnerResourceID) Then
  10.             MsgBox “CLUT(KEY*) is not found!"
  11.             Exit Sub
  12.         End If
  13.         bf.CurrentPosition = mmapOffset(ResourceID)
  14.         fromCLUT = mmapOffset(ResourceID) + 8
  15.         bf.SkipData (4)
  16.         wLen = bf.GetDataHex(4)
  17.         toCLUT = fromCLUT + CLng(“&H" & wLen) – 1
  18.         k = 0
  19.         For j = fromCLUT To toCLUT Step 6
  20.             clut(k + 2) = bf.GetData(1)
  21.             bf.SkipData (1)
  22.             clut(k + 1) = bf.GetData(1)
  23.             bf.SkipData (1)
  24.             clut(k) = bf.GetData(1)
  25.             bf.SkipData (1)
  26.             clut(k + 3) = &H0
  27.             k = k + 4
  28.         Next j
  29.     Else
  30.         Call SystemPalette取得
  31.     End If
  32.     For j = 0 To 1023
  33.         di = di + 1
  34.         OUTBMPbuf(di) = clut(j)
  35.     Next j
  36. End Sub

システム・パレットを作成するサブ・プロシジャ

Palette IDが負の値の場合はシステム・パレットが指定されており、CLUTが存在しないため、CLUTに相当する値を返す関数を作成しました(この関数の部分は煩雑になるため、テキスト・ファイルを添付しておきます)。

  1. Public Sub SystemPalette取得()
  2.     Select Case PaletteID
  3. ' systemMac
  4.     Case -1
  5.         Call SystemPalette_systemMac
  6. ' rainbow
  7.     Case -2
  8.         Call SystemPalette_rainbow
  9. ' grayscale
  10.     Case -3
  11.         Call SystemPalette_grayscale
  12. ' pastels
  13.     Case -4
  14.         Call SystemPalette_pastels
  15. ' vivid
  16.     Case -5
  17.         Call SystemPalette_vivid
  18. ' ntsc
  19.     Case -6
  20.         Call SystemPalette_ntsc
  21. ' metallic
  22.     Case -7
  23.         Call SystemPalette_metallic
  24. ' systemWinDir4
  25.     Case -101
  26.         Call SystemPalette_systemWinDir4
  27. ' systemWin
  28.     Case -102
  29.         Call SystemPalette_systemWin
  30. ' systemMac
  31.     Case Else
  32.         Call SystemPalette_systemMac
  33.     End Select
  34. End Sub

↓System Paletteの値を返す関数
SystemPalette.txt

BMPファイルのビットマップ・データを作成し、BMPファイルを出力するサブ・プロシジャ

行番号6でBITDの開始位置を取得し、行番号30~61の繰り返し処理でランレングス圧縮されたデータを復元し、行番号68~79の繰り返し処理でBMPファイル用に画像データを下から上に向って並べ替えたうえ、行番号85でBMPファイルを出力しています。

  1. Public Sub BITD出力()
  2.     ReDim bmpd(0 To bmp_width * bmp_height – 1)
  3.     For k = 0 To UBound(bmpd)
  4.         bmpd(k) = 0
  5.     Next k
  6.     bf.CurrentPosition = mmapOffset(i)
  7.     fromBITD = mmapOffset(i) + 8
  8.     bf.SkipData (4)
  9.     wLen = bf.GetDataHex(4)
  10.     toBITD = fromBITD + CLng(“&H" & wLen) – 1
  11.     ReDim bitd(0 To toBITD – fromBITD)
  12.     bf.CurrentPosition = fromBITD
  13.     For j = 0 To UBound(bitd)
  14.         bitd(j) = bf.GetData(1)
  15.     Next j
  16.     ReDim bitw(0 To bmp_width * bmp_height – 1)
  17.     For j = 0 To UBound(bitw)
  18.         bitw(j) = 0
  19.     Next j
  20.     idx = 0
  21.     si = 0
  22.     If (bmp_height * bmp_wlimit) <= (UBound(bitd) + 1) Then
  23.         Do While idx < UBound(bitw)
  24.             bitw(idx) = bitd(idx)
  25.             idx = idx + 1
  26.             si = si + 1
  27.         Loop
  28.     Else
  29.         Do While (idx < UBound(bitd)) And (si < UBound(bitw))
  30.             wval = bitd(idx)
  31.             If wval >= 128 Then
  32.                 run_length = 256 – wval + 1
  33.                 If idx + 1 >= UBound(bitd) Then
  34.                     Exit Do
  35.                 End If
  36.                 run_value = bitd(idx + 1)
  37.                 idx = idx + 2
  38.                 For k = 1 To run_length
  39.                     bitw(si) = run_value
  40.                     si = si + 1
  41.                     If si > UBound(bitw) Then
  42.                         Exit Do
  43.                     End If
  44.                 Next k
  45.             Else
  46.                 run_length = wval + 1
  47.                 idx = idx + 1
  48.                 If idx >= UBound(bitd) Then
  49.                     Exit Do
  50.                 End If
  51.                 For k = 1 To run_length
  52.                     bitw(si) = bitd(idx)
  53.                     idx = idx + 1
  54.                     si = si + 1
  55.                     If si > UBound(bitw) Then
  56.                         Exit Do
  57.                     End If
  58.                 Next k
  59.             End If
  60.         Loop
  61.     End If
  62.     x = 0
  63.     y = bmp_height – 1
  64.     w = bmp_width
  65.     idx = 0
  66.     Do While (idx < UBound(bitw)) And (y >= 0)
  67.         bmpd(y * w + x) = bitw(idx)
  68.         idx = idx + 1
  69.         x = x + 1
  70.         If x >= bmp_wlimit Then
  71.             x = 0
  72.             y = y – 1
  73.             If y < 0 Then
  74.                 Exit Do
  75.             End If
  76.         End If
  77.     Loop
  78.     For k = 0 To UBound(bmpd)
  79.         OUTBMPbuf(di) = bmpd(k)
  80.         di = di + 1
  81.     Next k
  82.     Put #OUTBMPnum, , OUTBMPbuf
  83.     Close #OUTBMPnum
  84. End Sub

(おまけ)Adobe Directorに関する書籍について

Adobe Directorに関する書籍を探したところ、インドネシアのサイトで以下の書籍が紹介されているのを発見。
さらにネットで検索しまくった結果、イギリスのAmazonで取扱っていたので購入しました。

  • 著者:Klemens Hübner
  • 書名:Macromedia Director Multimediaprogrammierung mit Lingo
  • 出版社:Springer-Verlag
  • 発行日:2013/10/3
  • 補足:Softcover reprint of the original 1st ed. 2004版

先程、改めて調べたところ、日本のAmazonでも購入できるようなので、興味のある方はチェックしてみて下さい。
(ドイツ語で書かれているため、正確に理解するのは困難ですが、図版が多いので大体は理解できると思います)

Macromedia Director Multimediaprogrammierung mit Lingo

国本温子(著),緑川吉行(著),できるシリーズ編集部(著)
出版社:インプレス
発売日:2022/3/23
単行本(ソフトカバー):A5判/912ページ

大村あつし(著),古川順平(著)
出版社:技術評論社
発売日:2021/1/9
単行本(ソフトカバー):A5判/800ページ

高橋宣成(著)
出版社:技術評論社
発売日:2019/11/25
単行本(ソフトカバー):B5変形判/576ページ

ファイル解析

Posted by hides