DXRuby を Wine から使う

# 前説
## この記事は
[DXRuby Advent Calendar 2014](http://www.adventar.org/calendars/345)の7日目です。6日目の記事はみれいゆーさんの「Ruby初心者がふれーむわーく：チーム戦のあるサイドビューシステム」でした。その前の記事と合わせて読むことで、1キャラコマアニメ->複数キャラコマアニメ->チーム戦かつオブジェクト指向化、とプログラムが進化していくさまを見ることが出来ます。やりたいことの一部をまず実装して、そこからだんだんと本当にやりたいことに近づけていく、あと気がついたところは気がついた時にリファクタリングする、というプログラミングのお作法の実践例です。大事なことなんですがなかなか実践できません。少なくとも私は。

その他にも DXRuby Advent Calendar の公開済み記事を見ると、[8時間でゲーム作った](http://blog.aotak.me/post/103982856491/dxruby-stg-8-hours-time-trial)り[イベントで展示した](https://gist.github.com/shinokaro/63d2484ba7163f0cdefd)りとなんとも素敵な記事が並んでいます。

さて本日はそんな華のある雰囲気はなく、文字だらけでゲームのゲの字もない進行ではありますが、お付き合い頂ければありがたいです。

## はじめに
今回取り扱うのは、非 Windows な環境で DXRuby を使うには、という話です。Windows でハッピー DXRuby ライフをお送りの方には無縁の話題です。

そのような前提で第一候補に上がるのは、普通はまず [dxruby_sdl] (https://github.com/takaokouji/dxruby_sdl) ではないかと思います。dxruby_sdl は本家 DXRuby との互換性が高く、普通に使う分には問題ありません。
とはいえ移植ですから 100% 互換というわけにはいかず、最新の機能や少し尖った機能、例えばシェーダ機能などは流石に使えません。

そこで、Wine を使って最新の DXRuby を動かしてみよう、というのが本校の趣旨です。

## 要するに
Wine 1.7.31 から Ruby が動くよわーい、その上で DXRuby も動くよやったー、という、言ってしまえばそれだけです。あと、gem まで動かすにはレジストリも書き換える必要があったりとか、面倒なところを `rbenv install` でやってくれるようにしました。そんな内容です。

## 前提
rbenv + ruby-build と Wine の最新版を使います。
OS は Mac OS X 10.10 または Ubuntu 14.04 を想定しています。また、Mac では Homebrew を使います。
それ以外の環境では適宜読み替えてください。たぶんそんなに大きくは違わないと思います。

# 導入
ではここから、DXRuby の動作に必要な諸々のソフトウェアをインストールしていきます。

## Homebrew（Mac の場合）
Mac の方はまず最初に Homebrew を用意します。入れていない人は下記コマンドで導入しておいてください。

```
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```

## 7z
Ruby Installer のパッケージを展開するために、7z コマンドを使う用意をします。
### Mac の場合

```
$ brew install p7zip
```

### Ubuntu の場合

```
$ sudo apt-get install p7zip-full
```

## Wine
Wine の最近のアップデート(1.7.31)で、mingw32 版の Ruby が動くようになりました。それ以前の Wine では動作しませんので、最新版をインストールするように注意してください。

### Mac の場合

```
$ brew install --devel wine
$ brew install winetricks
```

Wine は巨大プロジェクトですので、ビルドにはかなり時間がかかると思います。ゆったりとお待ちください。

### Ubuntu の場合
launchpad から最新の Wine を持ってきます。[Wine 公式サイトの説明](https://www.winehq.org/download/ubuntu) を参考にして GUI で操作しても結構です。

```
$ sudo add-apt-repository ppa:ubuntu-wine/ppa
$ sudo apt-get install wine1.7 winetricks
```

## DirectX

```
$ winetricks d3dx9
$ winetricks dsound
$ winetricks directmusic
$ winetricks dsdmo
```

midi を鳴らしたい場合は gm.dls を用意する必要があるらしいですが、複雑になるのでここでは触れません。midi を鳴らさなくとも、dsdmo や directmusic を入れておかないと思わぬところで DLL や COM のロードエラーに泣かされることがありますので、とりあえず入れておいた方が無難です。

## rbenv
以下の方法では設定を .bash_profile に書き込んでいます。環境と好みに合わせて .profile や .bashrc や .zshrc などご自由に書き換えてください。

### Mac の場合
Homebrew でインストールする場合の方法です。Git 経由でインストールしたい場合は、「Ubuntu の場合」の方法を使ってください。

```
$ brew install rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ . ~/.bash_profile
```

### Ubuntu の場合

```
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ . ~/.bash_profile
```

## ruby-build
Windows 版 Ruby をインストール候補に含めるように、本家の ruby-build にパッチを当てます。

### Mac の場合
rbenv と同じく、Homebrew を使わずに Git 経由でインストールしたい場合は、「Ubuntu の場合」の方法を使ってください。

```
$ brew edit ruby-build # パッチが当たるように ruby-build の設定を編集する

### test do の行の直前あたりに以下の部分を追加する
### ここから
  def patches
    "https://github.com/wanabe/ruby-build/compare/wanabe:master...wine.patch"
  end
### ここまで

$ brew install ruby-build
```
### Ubuntu の場合
パッチ適用済みのレポジトリから clone してきます。本家の ruby-build にパッチを当てたりマージする方法でも問題ないと思います。

```
$ git clone https://github.com/wanabe/ruby-build.git ~/.rbenv/plugins/ruby-build
```

## Ruby
### mingw32 版（by rubyinstaller.org）を使う場合 <- おすすめ
rubyinstaller.org 製の Ruby を使うと、Devkit を組み合わせることが出来るため、のちのち便利です。
Ruby 2.1 または Ruby 2.0 を選んでインストールしてください。

```
### Ruby 2.1を入れる場合
$ rbenv install 2.1.5-i386-mingw32
### Ruby 2.0を入れる場合
$ rbenv install 2.0.0-p598-i386-mingw32
```

### mswin32_100 版（by artonx.org）を使う場合
拡張ライブラリを入れる予定がない人は、こちらの mswin32_100 版を使っても問題ないと思います。（または VisualStudio をセットアップする人も同様。ただ、Wine で動くのかは確認していませんが）
DXRuby が同梱されていますので、用途によってはこちらの方がかえって楽かもしれません。
```
$ rbenv install 2.1-i386-mswin32_100
```

## DXRuby
mswin32_100 版には DXRuby は同梱されているため、同バージョンを選んだ場合はこの工程は必要ありません。
### Ruby 2.1 の場合
DXRuby の 現行の gem（1.4.1）の中には Ruby 2.1 用のバイナリが含まれていないため、手動でインストールする必要があります。
[DXRuby の配布ページ](http://sourceforge.jp/projects/dxruby/releases/)から、ファイル名末尾が "ruby21.zip" となっているファイルをダウンロードして展開しておいてください。

```
$ cd (上記 zip ファイルを展開してできたディレクトリ)
$ rbenv local 2.1.5-i386-mingw32
$ ruby install.rb
```
### Ruby 2.0 の場合
使う Ruby のバージョンを指定するため、作業ディレクトリを作成します。

```
$ mkdir （作業ディレクトリ）
$ cd （作業ディレクトリ）
$ rbenv local 2.0.0-p598-i386-mingw32
$ gem install dxruby
```

# 動作
動作を確認します。
何を確認プログラムにするか迷っていたのですが、今回の Advent Calender で衝撃的だった 3日目 mieki256 さんの[多関節スプライト表示スクリプト](http://blawat2015.no-ip.com/~mieki256/diary/201412031.html)を動かしてみることにします。とても興味深い記事ですので、もし未見の方がいらっしゃいましたらぜひご覧になってください。

ページ中央辺りに spritersconparser_20141203.zip へのリンクがありますので、そこからダウンロードして展開しておいてください。展開できたら、

```
$ cd spritersconparser
$ rbenv local （rbenv install でインストールしたrubyのバージョン）
$ ruby main.rb
```
としますと、多関節スプライトのキャラクターが動いてくれます。

特にここで説明はしませんが、せっかくなので他にもいろいろと動かしてみてください。個人的には、DXRuby 付属のシェーダのサンプルなどを動かしてみるのが良いと思います。

# 詳細
中で何が起きているのか、何をやっているのか、についてです。知らなくても動くので特に問題はありません。そういうのに興味がある人向け。

## Wine
つい先日の 2014/11/14 に [Wine 1.7.31](https://www.winehq.org/news/2014111401) がリリースされました。このリリースで、[Ruby の 1.9 以降（の mingw32 版）が動かない](https://bugs.winehq.org/show_bug.cgi?id=34941)というバグが潰され、Ruby Installer の ruby.exe が動くようになりました。

### バグの詳細
簡単に言うと、ioinfo と呼ばれる構造体の構成が違ったことと、この構造体に含まれる変数の初期化が行われていなかったのが原因です。しかしこれもやむを得ない部分がありまして、実はこの ioinfo についてのドキュメントは一般に無償で公開されているわけではなく（確かお金出して買った人には読めるソースコード中に書かれていたとかそんなんだった気がします。うろ覚え）、従ってこの構造体の構成については Wine 陣営は手探りでどうにかするしかなかったのです。

この ioinfo、Visual C++（あるいは Visual Studio。以降はまとめて VC と略します）のランタイムライブラリのバージョンによって構成もサイズも異なっているという特徴があります。さてところで Wine のランタイムライブラリに目を向けると、VC6 以前のランタイムである msvcrt.dll と、VC7 以降の msvcrXX.dll（VC のバージョンによって、XX の部分に 71 だったり 90 だったり数字が入ります）が完全に分離されておらず、msvcrXX.dll は初期化程度のことはするものの基本的に msvcrt.dll をロードして使う構成になっていました。これでは、バージョンごとに ioinfo の構成を変えるというのはかなり面倒な仕事でした。
（以前無理やりな解決を試みたのが[こちら](https://gist.github.com/wanabe/5565338)です。今あらためて眺めると、ひどくひどい）

しかし最近 Wine のこの部分に変更があり、各ランタイムは別々にコンパイルされるようになりました。おかげで、`#ifdef` で簡単に構造体を変更できるようになり、正しいサイズで構造体の変数が確保されるようになりました。さらに変数の初期化（具体的に言うと CRITICAL_SECTION に対する InitializeCriticalSection() と、その初期化が済んだことを表すフラグのセット）もされるようになり、ようやく Wine で mingw32 版 ruby.exe が動くようになりました。

### mingw32 版と強調する理由
実は、以前のバージョンの Wine でも厳密にいえば ruby.exe が動かないわけではありません。MS 謹製のランタイムを使えば、Wine のバグとは無縁です。

mingw32 版 ruby は msvcrt.dll にリンクしています。msvcrt.dll はあくまで Windows 付属品でありコピーしたり配布したりはできないので、どうしても Wine 側での修正が必要でした。

しかし msvcrXX.dll は MS が配布していたり、あるいは再配布可能だったりするので、こちらを使えばいいわけです。例えば arton さんがビルド・配布している ruby-2.1 がこれに該当します。ただこの場合、使用する拡張ライブラリもすべて同じランタイムにリンクしないといけません。そのためには同じバージョンの VC が必要になります。

## ruby-build
パッチを書いたと言いましたが、そんなに大したことはしていません。基本的には tar ボールの取得・展開ルーチンを真似て 7z ファイルに対応させ、.exe と .bat をラップするスクリプトの生成ルーチンを書いただけです。あとそれに加えて gem が動いてくれるように、Wine で足りないレジストリを書き込む操作を追加し、rbenv rehash 時にまた .bat のラッピングを行っています。

### .exe と .bat のラッピング
wine を使っていることを意識しなくてもいいように、シェルスクリプトで実行ファイルをラッピングしてあります。といっても、.exe は Wine を呼ぶだけ、.bat はその中で読み込むスクリプトを ruby.exe で読み込むだけ、です。

### レジストリの書き込み
Windows では、環境独自の名前解決を行えるように hosts ファイルというファイルが用意されています。Linux や Mac でも /etc/hosts という同様のファイルがあると思います。Windows の ruby ではインターネット接続前にこのファイルを読み込もうとしますが、このときファイルの所在を示す値としてレジストリ `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath`（Win95/98 では `Software\Microsoft\Windows\CurrentVersion\SystemRoot`）の値を取得します。しかし Wine ではこの値の初期値は設定されていないため、ruby で予期せぬエラーが起こってしまいます。

これでは rubygems などのネット接続が前提のプログラムが使えないため、適当な値でレジストリを作成してやることでこれを回避しています。パッチでは ```Z:\etc``` を書き込んでいます。Mac やほとんどの Linux では /etc/hosts に名前解決情報があるため、これを読むようにしました。

/etc/hosts を使われると都合が悪い場合は、別のディレクトリ名や空文字を指定しておけばよいです。キーが存在しない時にはエラーになりますが、キーを読んだ結果存在しないファイル名だった時には特に何もせず独自名前解決部分はスキップしてくれます。

### rbenv rehash 時の .bat の再ラップ
`gem install` で入るパッケージの中には、実行スクリプトが含まれることがあります。これらもシェルスクリプト化してやります。
本来なら `gem install` のフックにしてやればいいのですが、gem は Wine 内部で動作しているため、ファイルに実行権限を付加する部分がうまくいきません（Windows には実行権限なんて概念がありませんから、できなくて当然といえます）。なので、Wine の外で動く rbenv で処理することになりました。

ただ、この部分は後から気づいて慌てて変更した部分なので不具合があるかもしれません。お気づきの方は教えていただけると助かります。ここに限らず、全体的に指摘や不具合報告はありがたいです。

# 雑感
技術的なところのないよもやま話です。暇な人向け。

## 動機
Wine で mingw32 の ruby が動くようになったのが嬉しくて、試しに DXRuby を入れてみたらさくさく動いて感動しました。ただ環境構築にはちょっと手間とコツが必要だったので、試してみたい人の手間が省ければと思い手順を作りました。

## シェーダ
文中でやたらとシェーダシェーダ連呼しているのは、個人的に興味があるからというのもありますが、それ以外の機能はだいたい dxruby_sdl で動くだろうからそこしか戦える部分がない、という理由が大きいです。

## Wine
もともと私は Windows と DirectX が好きで、MinGW + MSYS を開発環境として使っていました。環境の貧弱さに耐え切れず Ubuntu に逃げ込みましたが、潤沢なリソースがあるなら Windows を使っていたい派です。そのような個人的事情に Wine はとてもよくマッチしています。まだまだ発展途上なところもあるのであまりクリティカルな要件にはともかくとして、遊びで使うには Wine はとてもよいです。

## DXRuby
DXRuby は前から眺めていて「いいなー」と思っていました。いいですよね DXRuby。この記事を見ている方なら同意してくれるはずです。簡単なのがいい。速いのがいい。いろいろ揃っているのもいい。
なにより開発が活発なのがいい。バグが見つかれば直されますし、次に何が出てくるのかワクワク感があります。

## おわりに
常々ゲームを作りたいなーと考えたりしています。この記事にも、DXRuby を使う人・ひいてはゲームを作る人が増えたら楽しかろうなーというぼんやりした期待を込めています。
ここまでお付き合いいただきありがとうございました。

## 次は
DXRuby Advent Calendar 2014、8日目の明日は主催の aoitaku さんによる「DXRubyユーザーのためのRuby組み込みライブラリ紹介」です。Ruby はオブジェクト指向言語でありますので、プログラムのほとんどがオブジェクトの操作で占められるわけでして、組み込みライブラリを知り外部ライブラリを知れば百戦危うからず（と言えなくもない、かも）です。これは期待ですね。

# 追記
## Ruby バージョンの追加対応について(2015/5/16)
[要望をいただいた](https://twitter.com/noanoa07/status/593351961333796864)ので Ruby のバージョンの追加対応をしました。RubyInstaller の 2.0.0-p645, 2.1.6, 2.2.2 と Arton さんのところの mswin32 版 2.2 がインストール可能です。

## irb, pry 対応について(2015/5/16)
[質問をいただいた](https://twitter.com/noanoa07/status/593393177404387328)ので確認したところ、粉の記事の方法では irb や pry が動作しません。
これは、どうも RubyInstaller で添付している rb-readline に、Wine が対応していないのが原因のようです。rb-readline にというより、コンソール系の API の多くがちゃんと動いてくれてない気がします。
[自分でも少し弄って](https://github.com/wanabe/wine/compare/master...wanabe:enable-kbhit-getch)みましたが、カーソル移動の SetConsoleCursorPosition を筆頭にまだまだ書き換えなければいけなそうだったので挫折しました。（あと、Wine の流儀を調べて合わせたり、テストを作成・実行したり、というやる気も起きなかった）

なお、どう説明していいかわからなかったので Wine 側に報告はしていません。報告またはパッチを Wine 本家に送る人がいると事態は進むかもしれないので、irb, pry, rb-readline がどうしても使いたい！という人は挑戦してみてはいかがでしょうか。
あるいは、Wine でも動くように rb-readline もしくは irb を書き換えるのでもよいかもしれません。