2015/12/28

WineでWOLF RPGエディター 2.10作品が3Dモード時に文字化けする問題と対処

WOLF RPGエディター (ウディタ)のバージョン2.10で作られた作品をWineで動かしたときに3Dモードで文字化けが発生する現象についてを扱う。

この問題の詳細については2014年3月に扱ったが、その後Wineにパッチを当てて設定することで “3Dモード” でも正常に動かせるようになり、その修正の要領やパッチについて2015年2月に扱った。

本記事ではこれらの記事を統合し、更に一部の内容を書き直したりWineのバージョン1.8向けのパッチを追加したりしている。

  1. お急ぎの方向け (対処のみがすぐ知りたい場合)
  2. 不具合の内容
  3. バグ報告とその後の流れ
  4. DXライブラリ関係の問題
  5. Wineにとっての問題点や対策
    1. 他の影響を受けるプログラムなど
    2. WOLF RPGエディター 2.10作品についての今後の対策
  6. ここまでのおさらい
  7. Wine側を邪道な形で修正する対処
    1. 考え方と修正の要領
    2. パッチ
    3. パッチ適用済みのバイナリ
    4. 設定

お急ぎの方向け (対処のみがすぐ知りたい場合)

  • Game.exeと同じディレクトリにある設定ツールConfig.exeを、これらの存在する階層を作業ディレクトリとして実行し、 “ソフトウェアモード” を選択して設定反映してからGame.exeを動かせば文字化けは起こらないが、GPUを活かせず(CPU資源を多く使用する)、環境や作品によっては動作が非常に遅くなる
  • 3Dモードで動かしたい場合は
    • 見出し:パッチ” の当たったWineを用意 (見出し:パッチ適用済みのバイナリも参照)
    • 見出し:設定” にあるレジストリ追加用コマンド(“wine reg add” から行末まで)を実行してWolfRpgGame.exeという名前の実行ファイルのみに対して文字化け回避用の動作モードが有効になるようにする
    • 文字化けする作品が確認できたら一度終了してGame.exeWolfRpgGame.exeに名前変更して実行し直す

不具合の内容

2013年12月頃、WOLF RPGエディター (ウディタ)のバージョン2.10で作られた作品をダウンロードしてWineで動かしてみたところ、文字の描画が所々おかしくなっているのに気付いた。

この現象は3Dモード(GPU描画モード)での描画時にのみ起こり、ソフトウェアモード(ソフトウェア描画モード)では起こらない。

サンプルゲームの場合、開始したあたりでは幾つかの文字だけが崩れている状態だが

サンプルゲーム冒頭・初期段階

しばらく色々なメッセージなどを表示している内に大部分が壊れた描画になってしまう。

しばらくプレイすると全く読めない

この現象を最初に確認したときには作品依存かとも思ったが、バージョン2.10に付属するサンプルゲームで試してみても同様の現象が起きたため、このバージョン(のGame.exe)に固有な不具合と判断した。

WinetricksでDirect3D関係の設定変更や一部DLLのネイティブ版への置き換えも試したが変化はなかった。

古いバージョンのWineを試したところ、バージョン1.3.5までのバージョンで問題が起こらないように見えたが、これは3Dモードが正しく初期化できずにソフトウェア描画になっているためで動作も遅かった。エディタからサンプルゲームのテストプレイを行うとWOLF RPGエディターが使用している “DXライブラリ” というライブラリのデバッグ情報出力ファイルLog.txt

[引用]ファイル名:Log.txt
629: IDirect3D9Ex オブジェクトを取得します.... 成功
758: IDirect3DDevice9Ex オブジェクトを取得します.... Direct3DDevice9Ex の作成に失敗しました
854: IDirect3DDevice9Ex オブジェクトを取得します.... Direct3DDevice9Ex の作成に失敗しました
951: IDirect3DDevice9Ex オブジェクトを取得します.... Direct3DDevice9Ex の作成に失敗しました
1055: IDirect3DDevice9Ex オブジェクトを取得します.... Direct3DDevice9Ex の作成に失敗しました

といった出力があることからもそれが分かる。

どのタイミングから3Dモードが実際に動作するかを(Gitを使いながら)調べたところ、バージョン1.3.5と1.3.6の間の “d3d9: Partially implement IDirect3D9Ex::CreateDeviceEx().” という(つじつまの合う)結果となり、結局 “3Dモードが実際に動作する最初のバージョン(のWine)からはずっと不具合が発生する” と分かっただけだった。

バグ報告とその後の流れ

上記の経緯により、この件についてのバグ報告を2013年の12月末に行った。その際

  • WOLF RPGエディターはDXライブラリを使用している
  • 同ライブラリのソースは公開されている

ということを添えて記述した。

これに対して2014年1月末にSagawa氏によって “DXライブラリを直接用いた小さなテストプログラムで現象を再現できた” という旨のコメントが出され、その数日後に突然

  • 不具合の原因はDXライブラリの中にあり、同ライブラリの最新バージョン(3.11d)で修正された
  • WOLF RPGエディターの将来のバージョンでDXライブラリのバージョン3.11d以上を用いたバージョンが利用可能になったらそれを用いて動作テストを行ってほしい

という旨のコメントが出された。

DXライブラリ関係の問題

詳細についてはそこでは語られなかったが、突然このような流れになったことからDXライブラリの作者と何かやりとりが行われていた可能性があると思い、同ライブラリの作者のサイトを探してみた。その結果 “DXライブラリ内部でのフォントキャッシュ利用法について” というスレッドが確認できた。

同氏によるとバージョン2.10のWOLF RPGエディター(のGame.exe)に使われているバージョン3.10fのDXライブラリ[1]について “Direct3Dの仕様上保証されていない動作に依存している” 部分があるということで、具体的には “IDirect3DDevice9::UpdateTexture()についてIDirect3DTexture9::LockRect()で作成したダーティ領域(と呼ばれる領域)のみをコピーすることをDXライブラリ側は期待しているが、仕様上ダーティ領域のみをコピーすることは保証されていない” として指摘・報告している。

この不具合は二人のやりとりの数日前に別の方によって報告された不具合の関係で作者が開発(暫定)バージョンで修正済みで、上記の “報告” の翌日に公開されたバージョン3.11dでも修正済みとのことだった。Sagawa氏によるとそのバージョン3.11dではテストプログラムで描画が正常に行われることが確認できており、また、同氏が問題点の対処として提案したIDirect3DDevice9::UpdateSurface()を用いた使用メモリ節約版でも正常な動作を確認したということだった。

なお、Sagawa氏はテストプログラムの内容を公開していないが、同氏の書き込み内容を参考にして文字化けを再現する以下のプログラムをWindows向けにコンパイルして、文字化けを起こすバージョン[2]のDXライブラリ(GCC向け)とリンクし、Wineで実行することで文字化けが再現できた。

該当するバージョンのDXライブラリで文字化けを再現するプログラム例 (Sagawa氏による再現シナリオ)
#include "DxLib.h"

int WINAPI
WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  ChangeWindowMode (TRUE);

  if (DxLib_Init () == -1)
    return -1;

  // 1
  int font_x = CreateFontToHandle ("MS UI Gothic", 24, 8, DX_FONTTYPE_ANTIALIASING);
  if (font_x == -1)
    return -1;

  // 2
  DrawStringToHandle (0, 0, "ABCDEFG", GetColor (191, 191, 191), font_x);

  // 3
  int font_y = CreateFontToHandle ("MS Sans Serif", 24, 8, DX_FONTTYPE_ANTIALIASING);
  if (font_y == -1)
    return -1;

  // 4
  DrawStringToHandle (0, 50, "HIJKLMNO", GetColor (31, 63, 191), font_y);

  // 5: キャッシュ破壊
  DrawStringToHandle (0, 100, "PQRST", GetColor (127, 127, 127), font_x);

  // 6: 文字化け発生
  DrawStringToHandle (0, 150, "FBCEDA", GetColor (191, 63, 63), font_x);

  WaitKey ();

  DxLib_End ();

  return 0;
}

Wineにとっての問題点や対策

後述のパッチを用いない限り、Wineは以下の問題を抱えることになる。

他の影響を受けるプログラムなど

DXライブラリはWOLF RPGエディターなどのこれを使用するアプリケーションからビルド時に静的リンクされる(ライブラリ部分がプログラム本体と分離されずに取り込まれる)ため、最新バージョンのライブラリで修正されたといってもこの不具合の影響を受けるバージョンのDXライブラリを用いて(取り込んで)作成されたプログラムはWineで動かすと不具合が出るのを避けられない(なのでSagawa氏は “修正された後の将来のバージョンでテストを” とコメントしている)。WOLF RPGエディターは不具合回避としてソフトウェア描画モードが利用できるのでまだよいかもしれないが、DXライブラリにはソフトウェア描画機能無し版も存在するので、そちらの該当バージョンを使って作られたものに当たった場合はどうしようもなくなる。

WOLF RPGエディター 2.10作品についての今後の対策

WOLF RPGエディターのバージョン2.10で作られた作品では、バージョン2.10の後にDXライブラリのバージョンを3.11d以上にして公開される最初のバージョンが将来公開されるまではソフトウェアモードを使用するしかない。それが公開されたら既存のGame.exeに上書きして動かすことで不具合の解消ができることを期待するが、そのバージョンに上がる際にWOLF RPGエディター内に仕様変更があった場合、バージョン2.10で作られた作品が正常に動かないかもしれない。

WOLF RPGエディターの公式サイトにある “バグ報告スレッド 11” には今回の件についてと “DXライブラリを最新にして、かつ仕様変更のないバグ修正バージョンのリリースをしてほしい” 旨を2014年2月上旬に書き込んではいるものの、WOLF RPGエディターの作者が目を通したかどうかは不明で、目を通していたとしてもそのようなバージョンが近い内に公開されるという保証はなく、2015年12月時点では新しいバージョンが出ていないどころか、作者によるコメントも2013年8月から後は見られない。

WOLF RPGエディターの過去の “DXライブラリのバージョンの更新” は慎重で、作者のTwitter上の発言によると、作者の環境でバージョンを試しに上げた結果不具合が出て新バージョンの採用が先送りになったこともある。

ここまでのおさらい

  • Sagawa氏曰く、IDirect3DDevice9::UpdateTexture()Wine(wined3d.dll.so)における実装では、(バージョン1.8系時点では) “ダーティ領域” として記録された領域だけでなくテクスチャの全体がコピーされる処理になっている
  • 同氏曰く、バージョン3.11dより古い幾つかのバージョンのDXライブラリには、IDirect3DTexture9::LockRect()で矩形範囲をロック処理[3]した後でIDirect3DDevice9::UpdateTexture()を呼び出した際に、“ダーティ領域(ここでは先ほどロックした領域)のみがコピーされる” という動作を期待した処理がフォントキャッシュの扱いの中で存在し、Wineの実装では上の挙動の関係で特定の流れによってキャッシュの破壊が確実に起こり、文字化けの原因となる
  • WOLF RPGエディターバージョン2.10に含まれるGame.exeはこの現象が発生するバージョン(3.10f)のDXライブラリを組み込んでいる(静的リンクしている)ため影響を受ける
  • 上記動作に依存した部分は同ライブラリのバージョン3.11d以上では改善されて文字化けは起こらないが、その新しいバージョンのDXライブラリを組み込んだWOLF RPGエディターの新しいバージョンは2015年12月時点では公開されておらず、それで既存のGame.exeを置き換えるという形の対処はまだ行えず、できるようになっても文字化けしない以外にバージョン2.10のGame.exeと完全に同じ動作になるかは不明
  • WineのBugzillaで2013年12月にバグ報告をしたが、その後DXライブラリ側が修正されたことは分かったものの、Wine側の変更による対処には前向きではなく、開発者によるパッチも提供されていない
  • 2014年2月にWOLF RPGエディターのバグ報告の掲示板にこの件についてを書いたものの、作者によるコメントも長い間見られない状態で状況は変わっていない
  • Wineのバージョン1.8時点では改善はされておらず、Macユーザによる文字化け事例も既に複数見られるようになっている

Wine側を邪道な形で修正する対処

レジストリ設定によって有効/無効が切り替え可能(他のプログラムの実行に影響しない形)な回避策がWine側の変更によって実現できないかと2015年1月に試していたのだが、最終的にそのような形で動作するものが作成できた。

考え方と修正の要領

Wineのバージョンが変わって一部の修正の仕方が変わることがあっても考え方は同じ。

  • IDirect3DTexture9::LockRect()Wine実装からはテクスチャのサブリソースであるサーフェイス[4]の処理としてIDirect3DSurface9::LockRect()Wine実装が中で呼ばれるが、この中において、ロックされた領域を示すRECT構造体(左/上/右/下の4つの座標の値から成る)を、サーフェイスの構造体内に別途追加した別のRECT型のメンバにコピーしておく[5]
  • IDirect3DDevice9::UpdateTexture()[6]Wine実装からはサブリソースの処理としてIDirect3DDevice9::UpdateSurface()Wine実装が中で呼ばれるが、2Dテクスチャを扱う場合の呼び出しについて、転送元サーフェイスの構造体のメンバを参照して、過去にロックされたことのあるものについては先ほどコピーした範囲の情報を利用して、その範囲のみが転送先サーフェイスの同一座標へコピーされるように引数を指定する[7]
  • 常にこの挙動で動作するようにすると他のプログラムで意図しない動作をする恐れがあるため、Wineにあるレジストリ設定の仕組みを利用して設定項目を追加(設定保持用構造体のメンバ追加と初期値指定,レジストリ設定取得処理の追加)[8]し、上2つの処理はこの独自動作(以下hack)の有効時にのみ行われるようにする

本来はサーフェイスの構造体内にダーティ領域を示す構造体のメンバを用意して、ダーティ領域を変化させる操作(ロック以外に複数存在する)それぞれについて扱いを正しく記述してIDirect3DDevice9::UpdateTexture()側でそれを用いてコピー範囲を決める必要がある[9]のだが、WineのDirect3Dでダーティ領域の情報が内部的にどのように管理されているのかがよく分からず、処理によっては関数から関数へと指定領域が引数で渡されているにも関わらず関数内でそれが一切使われていないところもあったため、結局は該当するバージョンのDXライブラリで正しく動くことだけを優先して “レジストリによって切り替え可能なhack” という形で実装した。

Wineの将来のバージョンでこのあたりの動作が改善されて特に何もせずに正しく動作するようになる可能性もなくはないが、あまり期待はできない。

パッチ

このパッチは間違いなくSagawa氏の調査がなければ作成できず、改めて感謝する。パッチはパブリックドメインとして公開する。

手動でビルド[10]する場合、ビルドするソースと同じバージョンのWineのディストリのバイナリパッケージ(32bit版)があれば、dlls/wined3d/以下のビルドが終わった後はビルドを中止して(32bitの)wined3d.dll.soのみを既存のファイル([32bitライブラリのディレクトリ]/wine/wined3d.dll.so)に対して管理者権限で上書きするだけで済む。

(2016/9/19)バージョン1.9系で以前のパッチが当たらなくなってしまっているため、wined3d_resource構造体を拡張する形で修正を行うようにしてパッチを作成した。対象バージョンは1.9.19だが、1.9系ではwined3d関係の修正が頻繁に入っているため、この後いつまでこのパッチが当たるか(や正しく動作するか)は分からない。

パッチ適用済みのバイナリ

PPAリポジトリ “ppa:kakurasan/winedxlibfonthack” で公開している。Ubuntu 15.04と15.10で提供しているバージョンは1.6.2となり、その後のディストリで1.8系が標準で提供されるようになってからは1.8系に切り替える予定。

他のディストリなどではPPAで公開しているのと同じバージョンのWineのディストリのパッケージ(32bit版)をインストールした上でwine[バージョン]-i386_[バージョン]_i386.debのみパッケージ一覧ページから手動でダウンロードして展開し、wined3d.dll.soのみを既存のファイルに対して管理者権限で上書きする方法もあるが、正しく動作する保証はない。

設定

文字化け回避のモードで動かす(hackを有効にする)にはレジストリの設定が必要。

全てのプログラムに対してこのhackを有効にする場合はレジストリのキー(フォルダ) “HKEYCURRENTUSER\Software\Wine\Direct3D” の中に “DxLibFontHack” という名前の文字列を作成して値を “enabled” とすればよいが、他のプログラムでの意図しない動作を防ぐには特定のプログラムでのみこれが有効になるようにするようにするのが好ましく、 “HKEYCURRENTUSER\Software\Wine\AppDefaults[実行ファイルの名前]\Direct3D” の中に同様の項目を作成することになる。

下はGame.exeというファイル名でのみ有効になるような設定をレジストリファイル化したもの。

[任意]ファイル名:enable-dxlib-font-hack-game.reg
REGEDIT4

[HKEY_CURRENT_USER\Software\Wine\AppDefaults\Game.exe\Direct3D]
"DxLibFontHack"="enabled"

これはWOLF RPGエディター作品の動作に便利だが、実行ファイル名が他のプログラム(例:RPGツクール作品)とかぶるため、より確実にWOLF RPGエディター作品だけをhackの対象にするために上記設定のファイル名部分と実際のファイル名の両方をWolfRpgGame.exeなど他とかぶりにくい(ユニークな)名前にして使うとよい。

下は “WolfRpgGame.exe” に名前変更したWOLF RPGエディター作品に対してのみhackを有効化する設定をコマンドで行う作業例。

(WolfRpgGame.exeに名前変更したGame.exeでのみ自動的にhackを有効にする)
$ (WINEPREFIX=[Wine環境の場所]) wine reg add "HKEY_CURRENT_USER\Software\Wine\AppDefaults\WolfRpgGame.exe\Direct3D" /v "DxLibFontHack" /t REG_SZ /d "enabled"
使用したバージョン:
  • Wine 1.6.2, 1.8
[1]: DXライブラリのバージョンについては先述のLog.txtの冒頭付近に出力されるので確認が可能
[2]: “3.06e” から “3.11b” にかけてのバージョンであることが後に分かった
[3]: 仕様上はここで同領域が “ダーティ領域” として記録される
[4]: 少なくともWineのDirect3Dではテクスチャという型はサーフェイス(画像データを保持するメモリ領域)という型を中に含む形になり、テクスチャからサーフェイスはサブリソースとして参照する・IDirect3DDevice9::UpdateTexture()についても、中でサブリソースのサーフェイスに対するIDirect3DDevice9::UpdateSurface()に相当する内部処理が呼ばれる形の実装になっている
[5]: ロックされた領域を示す情報は既存のメンバに格納されるが、これはロックが解除されるとき(IDirect3DDevice9::UpdateTexture()の呼び出しよりも早い段階で行われる)に消されてしまうため、後で読み込めるようにするために新しいメンバの中にコピーして保持しておく必要がある
[6]: 説明として “テクスチャのダーティー部分を更新する。” とあり、 “転送元テクスチャにダーティー領域がある場合は、コピーをその領域だけに限定することで、コピー処理を最適化できる。” とも書かれているが “ダーティーとしてマークされているバイトだけがコピーされるという保証はない。” ともある
[7]: 座標は左上基準・つまりコピー元サーフェイスにおけるロック範囲を示すRECT構造体の左と上の座標値をPOINT構造体の形で指定する
[8]: これらは既存の設定項目に関係した記述を参考にしてコードを追加することで簡単に実装できる
[9]: 本家のソースツリーに対する修正としてはその形が望ましく、今回のような形の修正は取り込まれるべきではないと考える
[10]: 32bit向け・基本的には32bit環境や32bitのchroot環境でビルドする