VitaでZAVASやりたくて(7)
次はプロテクトの話だと豪語したものの、驚くほど何も覚えていなかったため、一からデバッグしなおしてかれこれ1週間。
やっとほぼ終わった。もうやりたくない。
プロテクトの中身の詳細に触れるとあまりよろしくないと思うので、プロテクトについてはぼんやりとだけ触れて、確認作業の流れとかそういうのを書いておきます。
使う道具
使うのはこちら。
X88000
実行アドレスのトレースログが簡単にとれるので大まかな動作を探るのに便利。QUASI88
デバッグ機能が強力なので細部を探るのにとても便利。標準でもとてつもなく便利ですが、デバッグログ機能を使うと最強に便利で今回はさらにちょっと改造して使用します。
大まかな流れ
X88000でプロテクトチェックルーチンにあたりをつける
QUASI88のデバッグログ強化
QUASI88による詳細デバッグ
X88000でプロテクトチェックルーチンにあたりをつける
X88000には標準のデバッグ機能に「実行ログ記録」というのがあって実行アドレス(PCレジスタの値)を記録することが出来ます。
こんな感じ。
Log Start(Main)
0A05CH 0A05DH 0A05BH 0A05CH 0A05DH 0A05BH 0A05CH 0A05DH
0A05BH 0A05CH 0A05DH 0A05BH 0A05CH 0A05DH 0A05BH 0A05CH
0A05DH 0A05BH 0A05CH 0A05DH 0A05BH 0A05CH 0A05DH 0A05BH
...
だからなんだという感じですけど、アドレスだけでも案外わかることがあります。
たいてい何かをチェックする場合はどこかサブルーチンを呼び出して結果を判定するという形をとるので、急にアドレスが飛んでまた戻るみたいな動きをしがちです。
まあそんな簡単な保証はないわけですけど、とりあえずそこを攻めます。(結果から言うと幸運にも当たり)
流石に全部を追うのは骨が折れすぎるのでゲームが止まるちょっと前から確認してみます。
X88000でZAVASを起動してメインCPUデバッグを指定&デバッグ実行。
オープニングをスキップしたところで、えいや!と実行アドレス記録し、例のファンファーレとともにゲームの進行がストップしたところでデバッグ実行をストップします。
Debug.logというファイルに結果が記録されているのでそれをしばらく眺めてみると…(諸事情によりここからしばらくぼんやりとした解説)
ところどころ妙にアドレスが飛んでる箇所を発見。
逆アセンブルして確認すると、何やら高位アドレスのルーチンを呼び出した後でAレジスタが特定の値かどうか(2種類の値を確認しているがお互いがビット反転した値になっててなんか意味深)チェックしている箇所が。
まだこの時点ではなんだろなーという感じですけど、一旦そこでブレークポイントをおいてAレジスタの値を確認してみます。
するとどうやらそのルーチンから帰ったときに判定処理の2つ目の値になってる様子です。
これは気になる。
1つ目の値の方を通したらどうなるのか?
ということで、実行アドレス付近をメモリダンプしてバイナリエディタでd88ファイルの該当箇所を検索し、比較値の順番を強引に入れ替えてみます。
入れ替えたd88ファイルを読み直して再実行してみると…
お?
なんか普通にゲーム始まった。
どうも当たりを引いた模様です。
普通はこんなに早く見つからないんですけどね。なんという幸運!
とはいえこれだといわゆるプロテクト解除なのでよろしくない上に美しくない。
フロッピーの吸出し結果から恐らくランダムに読めるあたりが絡んでいそうだというのはあたりが付いていて、そうであればエミュレータの機能改善によって法的に問題ない形で対応出来そうだと内心推測している(いた)私は、安直な書き換えではない方法を探るべく、どういうプロテクトなのか詳細を調べてみるのであった。
QUASI88のデバッグログ強化
X88000のデバッグ機能で詳細を追うのはちょっときついのでより詳細を確認できるQUASI88にここでバトンタッチ。
実は今回の検証で気が付いたんですけど、QUASI88って最新のVisual Studioでもビルドが通るんですね。以前はLinux上でビルドしてましたけどWindowsで扱える方が断然便利。
(ちなみに検証時のバージョンを見るとVisual Studio Community 2022 Version 17.1.1、Visual C++ 2022 00482-90000-00000-AA741と出てます)
ビルドできると何が嬉しいかというと、X88000で記録した実行ログの詳細版みたいなものを出せる版でビルド出来るところ。
こういう感じのが記録できます
[MAIN] BBDE 47 LD B,A
[MAIN] BBDF 3AA2BF LD A,(BFA2H)
[MAIN] BBE2 B7 OR A
[MAIN] BBE3 2005 JR NZ,BBEAH
ただちょっと惜しい。
詳細な動きを追うには、レジスタの内容が確認できないと何が起こっているかよく分からないのです。
できればFCEUX(有名なファミコンエミュレータ)でとれるログみたいのが嬉しい。
ということでちょっと改造を試みます。
注意点としてはデバッグ機能(MONITORモード)も使いたいのでSDL版のビルドが必要なのですが、ちょっと古いSDLを使う必要があるのと、それ用に小細工してやや強引にビルドを通す必要があります。
改造の経緯はどうでもいいと思うので結果だけ。
SDL1.2.15を準備
QUASI88のドキュメントにならってSDL-devel-1.2.15-VC.zipをsdl-develという名前で展開しておきます。(SDLの公式サイトから旧バージョンをダウンロード)
たぶんSDL Ver1系列ならビルド通るんだと思うけどドキュメントに合わせました。そのままではビルドが通らないので以下のおまじないを適当なソースファイルに追記
以下のコードを追記。僕はmain.cに追記しました。このテクニックはググって出てきたやつで、ちゃんと動くか知らんけどうまくいくという事で紹介されてたもの。害はなさそうなので採用。
FILE* __cdecl __iob_func(void)
{
FILE _iob[] = { *stdin, *stdout, *stderr };
return _iob;
}
デバッグログ強化計画
z80-debug.cの末尾あたりに以下のlog_reg関数を追記して、z80_loggingを以下のように変更。
なお、log_regはちょっと上にあるz80_debug関数からレジスタ出力部分をほぼそのまま流用しただけです。
void log_reg(z80arch* z80)
{
static const char flags[8] = "SZ.H.PNC";
char fbuf[10];
char fbuf1[10];
int i, j;
char reg[256];
for (j = 0, i = z80->AF.B.l; j < 8; j++, i <<= 1) fbuf[j] = i & 0x80 ? flags[j] : '.';
fbuf[8] = '\0';
for (j = 0, i = z80->AF1.B.l; j < 8; j++, i <<= 1) fbuf1[j] = i & 0x80 ? flags[j] : '.';
fbuf1[8] = '\0';
sprintf(reg, "AF:%04X[%s] BC:%04X DE:%04X HL:%04X IX:%04X"
" PC:%04X I:%02X IM:%d ",
z80->AF.W, fbuf, z80->BC.W, z80->DE.W, z80->HL.W, z80->IX.W,
z80->PC.W, z80->I, z80->IM);
logz80(reg);
sprintf(reg, "A':%04X[%s] B':%04X D':%04X H':%04X IY:%04X"
" SP:%04X IFF:%d (%5d) ",
z80->AF1.W, fbuf1, z80->BC1.W, z80->DE1.W, z80->HL1.W, z80->IY.W,
z80->SP.W, z80->IFF, z80->icount <= 99999 ? z80->icount : 99999);
logz80(reg);
}
void z80_logging(z80arch *z80)
{
extern z80arch z80main_cpu;
extern z80arch z80sub_cpu;
if (main_debug && z80 == &z80main_cpu) {
logz80_target(main_debug);
logz80("[MAIN] ");
log_reg(z80);
log_line_disasm(z80, z80->PC.W);
logz80("\n");
}
if (sub_debug && z80 == &z80sub_cpu) {
logz80_target(sub_debug);
logz80("[SUB] ");
log_reg(z80);
log_line_disasm(z80, z80->PC.W);
logz80("\n");
}
}
QUASI88_sdlプロジェクトを「DEBUGLOG」を定義してビルド
QUASI88_sdlプロジェクトのプロパティから、「C/C++」→「プリプロセッサ」→「プリプロセッサの定義」で編集を選択して「DEBUGLOG」を追加。QUASI88_sdlのMonitor版をビルド
出来上がったQUASI88_mon.exeと同じフォルダにSDL.dllを置いて起動出来たらたぶん成功。
ビルドが成功したらQUASI88.iniに以下の設定を追加してモニターモードを有効にしておきます。
-fdcdebugをつけるとディスクアクセス時にちょっとした情報が出ます。
-debugは無くても良いけどなんとなく。
-monitor
-debug
-fdcdebug
QUASI88を再起動してモニターモードで動いてることを確認したら準備完了!強化版でログをとるとこんなのが出るよ!
[MAIN] AF:7710[...H....] BC:0000 DE:0000 HL:BEA0 IX:B948 PC:BBDE I:00 IM:2 A':C742[.Z....N.] B':00C7 D':B910 H':BA91 IY:BDE0 SP:00F2 IFF:0 ( 3657) BBDE 47 LD B,A
[MAIN] AF:7710[...H....] BC:7700 DE:0000 HL:BEA0 IX:B948 PC:BBDF I:00 IM:2 A':C742[.Z....N.] B':00C7 D':B910 H':BA91 IY:BDE0 SP:00F2 IFF:0 ( 3653) BBDF 3AA2BF LD A,(BFA2H)
[MAIN] AF:0010[...H....] BC:7700 DE:0000 HL:BEA0 IX:B948 PC:BBE2 I:00 IM:2 A':C742[.Z....N.] B':00C7 D':B910 H':BA91 IY:BDE0 SP:00F2 IFF:0 ( 3640) BBE2 B7 OR A
[MAIN] AF:0044[.Z...P..] BC:7700 DE:0000 HL:BEA0 IX:B948 PC:BBE3 I:00 IM:2 A':C742[.Z....N.] B':00C7 D':B910 H':BA91 IY:BDE0 SP:00F2 IFF:0 ( 3636) BBE3 2005 JR NZ,BBEAH
QUASI88による詳細デバッグ
道具が揃ったのでいよいよ詳細を確認していきます。
まずは先ほどのチェックルーチン前後でブレークポイントを置いて(アドレスは伏せておきます)、その間に何かぱっと見で分かる情報がないか見てみると、
QUASI88> break 0xXXXX
set break point MAIN - #1 [ PC : XXXXH ]
QUASI88> break 0xYYYY #2
set break point MAIN - #2 [ PC : YYYYH ]
QUASI88> g
(省略)
*** Break at XXXX *** ( MAIN[#1] : PC )
[MAIN CPU]
(省略)
QUASI88> g
R D:1 T:2 S:6
R D:1 T:4 S:6*** Break at YYYY *** ( MAIN[#2] : PC )
[MAIN CPU]
(省略)
QUASI88>
チェックルーチンを呼び出して戻るまでの間にフロッピーを2か所読み込んでいるのが確認できます。(D:1 T:2 S:6とかいうのがそれ)
バイナリエディタでd88の該当箇所を確認してみると…
やはりエラーセクタ!ビンゴ!
かな?
まだ確定とは言えないまでも極めて怪しい。
とりあえずエラーセクタを読み込んで何かしているのは間違いなさそうなので、次は具体的な処理を追ってみます。
まずは実行ログの取得。
一旦リセットして先ほどのチェックルーチン呼び出し前でbreakさせます。
breakしたら実行ログを取得するよう変数を設定して再実行。
呼び出し後のところでbreakしたら「quasi88.log」を適当な名前でコピーしておきます。
(※相当膨大なログになるので起動直後から取るのはおすすめしません。容量の問題もあるし、データが多すぎて何を見たらいいか特定するのも困難、そもそもエディタで開くのが厳しいとか色々面倒になるので)
QUASI88> reset
Reset QUASI88...start
Reset QUASI88...done
QUASI88> g
(省略)
*** Break at XXXX *** ( MAIN[#1] : PC )
[MAIN CPU]
(省略)
QUASI88> set main_debug 1
QUASI88> set sub_debug 1
QUASI88> set fdc_debug 1
QUASI88> g
R D:1 T:2 S:6
R D:1 T:4 S:6*** Break at YYYY *** ( MAIN[#2] : PC )
[MAIN CPU]
(省略)
QUASI88>
ほんの一瞬の間のログだけどこれでも200MB超えるログがとれます。
100万行以上あるぞ!
さてどうしたものか。
実行ログにはフロッピーの読み込みが「READ DATA…」として記録されるのでそこをあたります。
一個目はこんな感じで確かにエラーセクタになってます。(実行ログでは読み込み位置がDTS表記じゃなくてCHRN表記になっているのでd88ファイル内を参照しながら読み替える必要あり)
READ DATA ------------- sk0 mf1 mt1 us0 hd0 eot=247 gpl=14 dtl=255
C:01 H:00 R:F7 N:02 [SUB] AF:FF44[.Z...P..] BC:0000 DE:01F7 HL:4200 IX:00FF PC:413C I:00 IM:0 A':FFFF[SZ.H.PNC] B':FFFF D':FFFF H':FFFF IY:FFFF SP:7FF4 IFF:0 (99999) 413C C9 RET
で、何やってるのかなーと下の方を見てみると(またもやぼんやりした説明になりますけど)、SUB CPU側での読み込み処理がずらずらと続き、その後にそこからマジックナンバーというか識別子というかをチェックしている処理発見。
ロジックからするとここは普通に読める箇所の整合性チェックです。
チェックが通ればとあるアドレスに目印らしきものを書き込んでいるらしく、ずっと後の方でここを読みだして値を確認する箇所がちらほらあります。
この識別子チェックが終わったら読み込んだバッファをクリアしているのでどうやら1個目はこれで終わりらしい。
そして2個目はこんな感じ。こちらも確かにエラーセクタです。
ログ全体をgrepしてみるとこの後は全部こちらと同じセクタを読み直していて、どれも同じ処理を通過してます。つまり何度もチェックしてるっぽい。これは本丸くさい。
READ DATA ------------- sk0 mf1 mt1 us0 hd0 eot=247 gpl=14 dtl=255
C:02 H:00 R:F7 N:02 [SUB] AF:FF44[.Z...P..] BC:0000 DE:02F7 HL:4200 IX:00FF PC:413C I:00 IM:0 A':0044[.Z...P..] B':FFFF D':FFFF H':FFFF IY:FFFF SP:7FF4 IFF:0 (99999) 413C C9 RET
こちらの処理を追ってみると、SUB側での読み込み処理が終わったあと読み込みバッファを特に参照せずに他の処理をずっとやってるようで、ずっと先に進むとバッファクリアしています。
そんなばかな。なんもしてないはずはない。もしかしてMAIN側?
ということで次はMAIN側で何かやってないか探るのですが、MAIN側ではIN A,(FCH)で読み込んでるように見える。
見えるとはなんだ曖昧な奴だなと思われるでしょうけどもPC88のアーキテクチャについてはど素人なので全部手探りなのである。(技術書を持ってたのは後々勉強したいと思ってまだ若かりし頃に買ってたためであって当時は中身を理解できてない)
ググってみると(キーボードでお世話になったサイトにまたもやお世話になります)FDC関係のデータを読むときのI/Oポートは確かにそうみたいなのでたぶんあってるみたい。
そこで、MAIN側でIN A,(FCH)してるところを追ってみると確かにエラーセクタの情報を読み込んでいます。が、先頭から0x80ずれてる。ん?
手前の処理を探ってみるとSUB側のレジスタが0x80ずれるタイミングがあるのでどうも読み込み位置をずらすコマンドか何か発行してると予想されるのだけども真偽は不明。なにせ技術書がない。
とにかく読めてるのは間違いないので気を取り直して先に進みます。
ここから先は無茶苦茶ぼかしますが、すごくざっくり言うとこういう確認をしている模様。
正常に読める箇所に規則通りのデータがあるか
ランダム箇所がランダムに読めるか
<細かすぎて誰にも伝わらない補足>
一か所ちょっとよくわかんないのがあって、SUB側の7F14番地に書かれてる値をMAIN側で取得して内容チェックしてる部分があるんですけど、7F14に値を書いてるのはDISK.ROMにあるロジックなので、BIOSかなにかがFDCのステータスみたいなのを記録してるとこなのかな?
恐らく普通にフロッピーを使ってる分には問題にならなそうなのでそこは見なかったことにしてます。
検証してみる
ざっくりまとめるとこういうプロテクトチェックをしているのでした。
とある識別子が読める
エラーセクタでも規則的な箇所がある
エラーセクタのランダム箇所はランダム
本当か確認してみます。
各処理の実行アドレスは分かっているのでデバッガ上でbreakしてごにょごにょしてみると…
識別子書き換え
→進行不能。そもそも2個目のエラーセクタすら読まない正常に読める箇所の規則性を壊す&ランダム箇所はランダムに
→進行不能ランダム箇所をランダムに
→普通にゲーム始まる!
つまり、(想定通りではありますが、)実機同様にランダムに読める箇所がランダムに読めればOKということになります。
(実際はもうちょっと詳しく検証してるけどいい加減飽きてきたと思うので省略)
これならエミュレータでフロッピーディスクのエミュレートを機能改善すれば吸い出したデータそのままで普通に遊べるはず。
実際にフロッピーディスクを読みだしてみて毎回読める内容が変わる場合があるのでそれを再現しただけ!という事であればたぶん法的にも問題なかろう。ほんとかな?
若干グレーな気はしないでもないですけどとにかくやってみよう。
ということで次はエミュレータ改造の冒険の旅。