#author("2023-05-24T01:54:56+09:00;2023-05-21T00:49:31+09:00","","")
#author("2024-02-14T20:14:37+09:00;2023-05-21T00:49:31+09:00","","")
* PCゲーム「雫」の移植方法1 [#a230f56e]
「雫」(しずく)とは、1996年にLeaf(現アクアプラス)が企画・販売していて、ノベルタイプの18才以上を対象としたゲームです。とある業界(汗)には大変有名な作品で、特に「雫」はリーフ・ビジュアルノベル・シリーズ三部作(雫、痕、To Heart)の1つと言われています。


#br


詳しい歴史については[[Wikipedia:http://ja.wikipedia.org/wiki/%E9%9B%AB]]を参照してください。昔のゲームなので今に比べて機能も随分劣っていると思います。今回、移植対象としているものは''リメイク前''なのでグラフィックは16色、メモリも最大で640KBしかないPCで動いていました。
詳しい歴史については[[Wikipedia:https://ja.wikipedia.org/wiki/%E9%9B%AB]]を参照してください。昔のゲームなので今に比べて機能も随分劣っていると思います。今回、移植対象としているものは''リメイク前''なのでグラフィックは16色、メモリも最大で640KBしかないPCで動いていました。


** GBAに移植したくなった理由 [#v61dbf8c]
インターネット上では一時期、ものすごい勢いでPCゲームを別プラットフォーム(りなざう、Linuxなど)に移植するブームが起きていました。そのときに詳細な解析資料がインターネットに公開され、当時は市販レベルのアドベンチャーゲームの中身が見れると思って大変驚いたものです。


#br


ただ残念なことに、そのブームは2001~2004年までを境にぱったり終わってしまい、2007年現在では解析資料の消失が目立ってきています。このまま消えていくのはあまりにもったいない。この資料を元に自分のつたない技術でもいいから移植してみようと思ったわけです。ほかにも以下のような理由がありました。


- アドベンチャーゲームの仕組みを知りたかった。
- ついでにスクリプトエンジンの勉強をしたかった。
- 解析資料が充実していて、内容も大変読みやすかった。
- UNIX系でオープンソースがあり、設計方法を学びたかった。
- GBAに移植してもスペックに十分耐えられそうだった。
- %%その方面のダメ人間に仲間入りをしたかった(ぉ。%%


#ref(1.png,nolink)


** GBAの移植例 [#t288cdb1]
移植に関しては、いろいろな方がすでに実践していらっしゃっています。どのようなGBAの機能を使っているか調べてみました。既にできあがっているものを見れるというのは、大変心強いです。


- [[細雪:http://www.tennodai.com/~sakura/?gba/gba.html]] (大賀 きゆき 様)
-- 移植対象は「痕」
-- Mode4(Frame x 2, 256色)
-- 文字表示にはSpriteを使用。画面には同時に128文字まで。
-- 色番号は、背景(0~127の128色) + キャラクター(128~255の128色)。
-- 背景だけフェードイン、フェードアウトすることが容易。
-- 音楽はDirectSoundで再生。


- [[掌でひぐらしがなく 体験版:http://www.inside-cap.com/download/hig_taiken.htm]] (帽子屋インサイド 様)
-- 移植対象は「ひぐらしのなく頃に 体験版」
-- Mode3(32768色)
-- 文字表示にはSpriteを使用。64x64サイズのスプライトを画面に数個配置。
-- スプライトバッファ領域に、文字フォントをドット単位でコピーしている様子。
-- とにもかくにも動作が機敏。なんでこんなに早いんでしょうか(^^;。
-- 音楽は独自企画。AXAKA SOUND。


-[[希望入りパン菓子 体験版:http://kaoriha.org/kip/]] (Moonlight 様)
-- 移植対象は「希望入りパン菓子 体験版」
-- Mode3(32768色)
-- 文字表示にはSpriteを使用。32x16サイズのスプライトを画面に数個配置。
-- エフェクト処理がとてもきれいなことが特徴。
-- 音楽はDirectSoundで再生。

~
調べていて、だいたいですが以下のようなことがわかりました。
- 画面は一番きれいなMode3が良い。エフェクト処理はフェードイン、アウトぐらいにする。
- ROMのサイズは最大で''32MBまで''なので、ゲームの容量を抑えなくてはいけない。
- GBA上で処理を軽くする為には、''コンバータ側で処理しやすいデータ構造''に変換しておかないとダメ。
- メッセージ表示はスプライトで行った方が速い。退避もラクらしい。


** 移植するに当たって必要なこと [#a529b647]
移植というはやったことがない人からみると随分漠然としたものです。自分もそうだったのでよくわかります。そこで肝心なのは、移植するための''視点''を持ってみることだと後で気がつきました。ある意味でゲームとは「小さな機能がよりあつまり、構成されているものだ」といえます。アドベンチャーゲームを思い浮かべてみると、背景画像がって、キャラクターがいて、メッセージウィンドウなどがあって・・・などと言うことができます。プログラマからみると、アドベンチャーゲームの構成している要素は次のように移るらしいです。

- 画像
-- 背景
-- キャラクター
-- エフェクト
- 音
-- 音楽
-- 効果音
- プログラム
-- メッセージの表示
-- メッセージの早送り
-- セーブ、ロード
-- オープニング、エンディングなど


小さな機能の構成がゲームを作っているように、移植するときも小さな単位ごとにしていった方が簡単です。ここでは''画像、音、プログラムを単位''としてGBAに移植していくことにします。


** 資料集め [#wfb53992]
ゲーム本体の.exeファイルを逆アセンブルしていって、全体像を把握する方法もありますがそれはあまりに無謀です。「雫」に関しては、既にその手にかけてエキスパートな職人さんが解析を終えていて、さらにwindowsユーザには馴染みのデータフォーマットに変換するプログラムも用意されていました。ちなみに当方は解析についてはサッパリなので、資料がそろわなかった場合移植することを断念していたと思います(^^;。


- 画像(背景)
- 画像(キャラクター)
-- [[Leaf マルチ画像コンバータ:http://www.vector.co.jp/soft/dos/art/se049761.html]] - LFGというリーフ独自の画像フォーマットをBMP形式に変換する。
-- [[Leaf マルチ画像コンバータ:https://www.vector.co.jp/soft/dos/art/se049761.html]] - LFGというリーフ独自の画像フォーマットをBMP形式に変換する。

- 音(音楽)
-- [[Sox:http://sox.sourceforge.net/]] - CD-DA形式なのでをWAVファイルに変換する。
-- [[Sox:https://sox.sourceforge.net/]] - CD-DA形式なのでをWAVファイルに変換する。

- 音(効果音)
-- リーフ独自のフォーマット?wav形式に近いらしいがよくわからないので保留。

- プログラム(スクリプト)
-- スクリプトとは「表示するメッセージや、背景の表示や音楽の再生などのタイミング」が書かれているものです。詳しいことは後述します。

- アーカイブ
-- [[Leafpak:http://hoshina.denpa.org/leafpak.html]] - .pakアーカイブの分割ツール。
-- アーカイブとは「背景、キャラクター、効果音、スクリプトデータ」を、1つのファイルにまとめているもののことです。上記の素材を個々に処理をする前に、アーカイブファイルの中身を分割する必要があります。

~
このような情報から、作業の流れが自然と浮かび上がってくると思います。
- BMP形式は、gritを使ってGBA上に表示する。
- wav形式は、soxを使ってGBA上に再生する。
- その他の形式は、そのままバイナリデータとしてROMにつける。


** データフォーマット [#de12e192]
以下にデータフォーマットを表します。すでに変換プログラムが用意されているので知る必要はないのですが、ここでは簡単に概要のみを説明します。


*** アーカイブファイル [#d93e7383]
| 8B   | マジックナンバー("LEAFPACK") |
| 1W   | ファイルの数                   |
| ?    | ファイルのデータ               |
| ?    | ファイルの一覧情報             |

- ファイルの一覧情報は、登録されているファイル数分存在します。1つのファイルにつき24バイトです。
 struct {
 	u8  name[12]; //ファイル名(8+4)
 	u16 pos;      //ファイルの先頭
 	u16 len;      //ファイルのサイズ
 	u16 next;     //次のファイル先頭位置
 } ST_FILE_INFO;


- ファイル情報については以下のように読み込みます。
 fseek(fd, -ファイル数*24, SEEK_END);
 fread(fd, buf, ファイル数*24)

- 各データは暗号化されているので、以下の位置の11バイトをkeyとして復号します。詳細が知りたい場合、[[Leafpak:http://hoshina.denpa.org/leafpak.html]]のソースコードを読んでみて下さい。

 ファイルの先頭 + ファイルサイズ - 24 * ファイルのデータ数


*** 画像ファイル(.lfg) [#ac58872f]
画像は16色で、24バイトのパレットを使用します。基本はリングバッファのLZ法です。バッファサイズを4096バイト、初期状態は全て0、バッファへの書き込み開始位置は0xfeeです。読み取る前にアーカイブのkeyで復号する必要があります。

|  8B | マジックナンバー("LEAFCODE") |
| 24B | パレット                       |
|  2W | 開始位置(x0,y0)              |
|  2W | 終了位置(x1,y1)              |
|  1B | 方向                           |
|  1B | 透明色の指定                   |
|  2B | ???                            |
|  1L | 展開後の長さ                   |
|  ?B | 圧縮データ                     |


- パレット - 1つの色要素毎に4bit(16階調)でRG BR GB ...といった感じで2色分を3Bに収めてます。
- 開始、終了位置 - 横は8ドットを1単位とした数です。よって横のドット数は、(x1 - x0 + 1) * 8となります。
- 方向 - 展開後の列がどの方向に進んでいくかの指定。0ならば縦、1ならば横。
- 透明色の指定 - 0xffならば透明色はなし。
- 詳細が知りたい場合、[[lfview(X11用リーフ画像ローダ):http://hoshina.denpa.org/lfview.html]]のソースコードを読んでみて下さい。

*** スクリプトファイル [#c06f0caf]
スクリプトファイルには大きく分けて、イベントと、メッセージという2つ構造に分かれています。データの最初の部分(イベントデータ、メッセージデータの位置)以外はリングバッファのLZ圧縮がされており、さらにbit反転されています。


| 1W | イベントデータの位置   |
| 1W | メッセージデータの位置 |
| 6W | ?                      |
| ?  | イベントデータ         |
| ?  | メッセージデータ       |


- イベントデータ、メッセージデータの位置には、0x10を掛けた値を使用します。


- 以下にイベントデータの詳細を表します。サイズが2バイト以上のコマンドは、パラメータがその後に存在します。


| コマンド | サイズ | 説明                    |
| 0x00     | 1      | ブロック終了            |
| 0x01     | 3      | 特殊効果(画面)        |
| 0x03     | 2      | 不明                    |
| 0x04     | 4      | 別スクリプトへジャンプ  |
| 0x05     | ?      | 選択肢                  |
| 0x06     | 1      | 不明                    |
| 0x07     | 1      | 前選択肢に戻る位置保存  |
| 0x0a     | 2      | 背景のみロード          |
| 0x14     | 2      | 画面クリア              |
| 0x16     | 2      | Hシーンロード           |
| 0x22     | 3      | キャラクタロード        |
| 0x0a     | 2      | 背景のみロード          |
| 0x14     | 2      | 画面クリア?             |
| 0x16     | 2      | Hシーンロード           |
| 0x22     | 3      | キャラクタロード        |
| 0x24     | 3      | キャラクタロード2?      |
| 0x28     | 1      | 選択肢に存在するデータ  |
| 0x38     | 2      | 表示処理(画面に反映)  |
| 0x3d     | 4      | if文                    |
| 0x3e     | 4      | if文(否定)            |
| 0x47     | 3      | フラグの値設定          |
| 0x48     | 3      | フラグの加算            |
| 0x54     | 2      | テキストメッセージ      |
| 0x5a     | 1      | 不明                    |
| 0x5c     | 2      | 不明                    |
| 0x60     | 1      | 不明                    |
| 0x61     | 3      | 不明                    |
| 0x62     | 2      | 不明                    |
| 0x63     | 1      | 不明                    |
| 0x64     | 1      | 不明                    |
| 0x65     | 1      | 不明                    |
| 0x66     | 1      | 不明                    |
| 0x6e     | 2      | BGM再生                 |
| 0x6f     | 1      | 不明                    |
| 0x73     | 1      | 不明                    |
| 0x7c     | 2      | エンディング関係(不明)|
| 0x7e     | 2      | エンディング番号設定    |
| 0x7d     | 2      | エンディングBGM&起動    |
| 0xff     | 1      | 本来アクセス不可        |


- 以下にメッセージデータの詳細を表します。サイズが2バイト以上のコマンドは、パラメータがその後に存在します。


| コマンド | サイズ | 説明                    |
| & 0x80   | ?      | 連続したメッセージ      |
| '$'      | 1      | メッセージ終了          |
| 'r'      | 1      | 改行                    |
| 'p'      | 1      | ページ更新              |
| 'K' 'k'  | 1      | キー入力待ち            |
| 'O'      | 1      | 不明                    |
| 'C'      | 4      | キャラクタ位置交換      |
| 'B'      | 7      | 背景ロード              |
| 'S'      | 10     | 背景付きキャラ表示      |
| 'D'      | 4      | キャラ全消去後表示      |
| 'A' 'a'  | 10     | キャラ3人表示           |
| 'Q'      | 1      | 画面を揺らす            |
| 'E'      | 7      | 背景ロード(2)?          |
| 'F'      | 1      | フラッシュ              |
| 'V'      | 7      | ビジュアル?             |
| 'H'      | 7      | Hシーン                 |
| 'M'      | 2      | BGM再生                 |
| 'P'      | 2-4    | PCM関連(効果音)         |
| 'X'      | 2      | 不明                    |
| 's'      | 2      | 表示速度指定?           |


- スクリプトファイルの処理の順番は、まずイベントデータの先頭を順番に読み込み、BGM, フラグ, 背景, キャラクタなどの設定を済ませます。準備がととのったところでテキストメッセージコマンド(0x54)を発行し、パラメータを使ってメッセージデータのオフセットを読み込みます。
- メッセージデータに読み込みが移っても、メッセージ終了('$')コマンドが来るまでメッセージデータを読み進めます。$が発行された場合、次のイベントデータ(テキストメッセージコマンド(0x54)発行後)のコマンドを読み込みます。
- メッセージとイベントの命令では重複しているものもあります。


** 移植手順 [#webee924]
移植の流れは以下のようになります。実際の作業はこんなに綺麗に分かれているわけではなく、色々作業を同時進行していました。


- スクリプトエンジン部分のプロトタイプを作る
- .gbaファイルのサイズを確定させる
- 画像ファイルを表示するプログラムの作成
- 音楽ファイルを再生するプログラムの作成
- スクリプトファイルを実行するプログラムの作成


*** スクリプトエンジン部分のプロトタイプを作る [#l086ef47]
途中で挫折しないようにする為、一番難しいことを最初にやってしまうことにしました。このプログラムは「DOSプロンプト上でスクリプトメッセージを表示する」使い捨てでしたが、アドベンチャーゲームの本質と思える部分で、一番の難関になると思います。


#br


プロトタイプのソースコードを以下に表します。見せるために用意したものではないので、ワーニング出しまくりのヒドイものですが必要ならダウンロードしてみてください(^^;。アーカイブファイル(MAX_DATA.PAK)からスクリプトファイル(SCN002.DAT)をデコードして、メッセージを表示しています。コンパイラはBorland C++ 5.5.1 for Win32(ダウンロード版)を使用しました。


#ref(scn3.c)


- 出力結果

 file(SCN002  DAT)
 event size   = 40
 message size = 2225
 
 ...(中略)
 
  unknown!![cmd:31 1]
  [22] 
  42 
  僕は黙々とシャープペンシルの先を走らせ、青い罫
  [59] 
  72 
 線が刻まれた真新しい大学ノートに、ひとつの円を描
 
 ...(以下略)


*** .gbaファイルのサイズを確定させる [#k57922fd]
次の問題は.gbaファイルの最大サイズ32MBに収まるように考慮することになります。この問題については(雫とゲームシステムがほぼ同じ)痕が移植を成功していることもあり、あまり大きな問題にはなりませんでした。ただし、痕を移植しました[[大賀 きゆき 様の日記:http://www.tennodai.com/~sakura/index.html?200206#20020602]]から伺いできるように、様々なトレードが絡んでいることが窺い知れます。たとえば、データのサイズを抑える為に圧縮してしまうと解凍に時間がかかり、無圧縮にした場合はデータのサイズが大きくなってしまいます。理想はPCゲームと同じ品質であることが望まれます。
*** 画像ファイルを表示するプログラムの作成 [#r77f5fed]
ここでのポイントは画像ファイルを表示するという目的以外にも、データ容量について調べてみることです。作業の流れはだいたい次のようなものです。


- アーカイブファイルを展開する
- その中の画像ファイル(*.lfg)だけをビットマップファイルに変換する
- GBAの画面用に240x160リサイズをする。無圧縮の場合、容量が大きくなるのでLZ圧縮をしておく


#ref(2.png,nolink)


以下にその結果を表します。


|データの形式 | 色    | サイズ   | 備考                          |
| lfg         | 8bpp  | 3.94MB   |                               |
| bmp         | 8bpp  | 15.1MB   |                               |
| bmp(resize) | 24bpp | 13.5MB   | 倍率約37%                     |
| img(GBA用)  | 15bpp | 3.98MB   | LZ圧縮あり。240x160サイズ固定 |


lfgファイルの数は195。サイズはLZ圧縮が掛っているとはいえ、無圧縮のbmpに比べて約5倍ほどの違いがあります。リサイズ時には縦横比(アスペクト)を無視して240x160のサイズに変更をしている為、若干横に太めに見えてしまうかもしれません。ここでわかったことは、オープニングアニメーション用の画像ファイルはスピードが気になるので無圧縮にして、他の画像は圧縮することで''約4.5MB''ぐらいになると想像できます。詳細が知りたい場合、旧サイトのNo.76を参照してください。ちなみに最終版では容量に問題がならなかった為、無圧縮にしています。


*** 音楽ファイルを再生するプログラムの作成 [#h915ad7e]
「雫」はCD-DA形式の為、ターゲットはWAVファイルということになります。全25ファイル(曲)で無圧縮の場合、517MB(16bit 44khz stereo)です。当初はsoxというツールを使って変換をかける予定でしたが、容量がかなりきびしいことがわかりました。そもそも圧縮していない点で場違いみたいです(^^;。


| soxのオプション  | サイズ |
| -r 11025 -c 1 -b | 29.8MB |
| -r 8192 -c 1 -b  | 22.2MB |


次にGBAで定評のあるライブラリを使用してみます。今回は[[GSM player for GBA:http://www.pineight.com/gba/gsm/]]と、[[8ad codec:http://www.pineight.com/gba/#8ad]]を調べてみることにしました。ビットレートの変更はGSMはできなかったのですが、8adはちょうど容量に収まりそうなパラメータに変更しています。


| 名前 | 容量   |
| GSM  | 10.1MB |
| 8ad  | 18MB   |


GSMはビットレートの低い特有のシャワシャワ感(?)が出てしまうので少し気になっていたのですが、8adはあまり気になりません。決定打となったは、8adの広告に「6 percent of the GBA's CPU time.」という記載があることと移植しやすいソースコードであったことです。詳細が知りたい場合、それぞれの公式サイトを見てみることをオススメします。サンプルプログラムは旧サイトのNo.77を参照してください。

** 履歴 [#r21bc3ba]
- 2007/10/22

トップ   差分 履歴 リロード   一覧 検索 最終更新   ヘルプ   最終更新のRSS