SSブログ
  PC-98x1(補完計画) ブログトップ
前の30件 | 次の30件

孤独へ向って突っ走れ (34) [  PC-98x1(補完計画)]

ちょっと気力が低下して疲れを感じた私は、今までのように最初から順に見てゆくことに退屈を感じた。それで、アドレスで最初のcall 441c(パレット実現)から調べるのでなく、試しに最後のcall 441cから始めてみた。

CS:BECCにある最後のcall 441cの、ほんの少し後でretしている。やはり、こんなアドレスにあるのはメインルーチンでなくサブルーチンだ。ではこのサブルーチンの始まりはどこだ。普通は、このサブルーチンの直前にも別のサブルーチンが記述されている。そのサブルーチンもretかiretで終わっている。私は直前のretを探した。そのすぐ次から、このサブルーチンは始まっている可能性が高い。

これで、いま私が見ているサブルーチンがどこからどこまでかがわかった。

次に私は、その中にあるcall 6fec(ファイル読み込み)とcall ef08(pic展開)を探した。ちょっと困ったことになった。パレット実現はあるのに、パレットファイル読み込みはなかった。pic展開はあるのに、展開元のデータを読み込む処理がなかった。

picもpalも、サブルーチンの呼び出し元で読み込んでいるに違いない。

私は自作プログラムでEXE内を検索した。call bdd8(このサブルーチンの呼び出し)は5か所で行われていた。(手前味噌になるが、このEXE内call検索用自作プログラムは解析に役立つ!)

CS:ABA5 call bdd8
CS:ACDA call bdd8
CS:AEA5 call bdd8
CS:BD10 call bdd8
CS:DCC2 call bdd8

私はこれらをひとつずつ調べることにした。

私がいま、とんでもなく膨大な量の作業にとりかかっていることがわかるように、やっていることの全体像をはっきりさせよう。EXE内のcall 441c(パレット実現)をひとつずつ調べ、その前後にpicファイルの読み込みを見つけ、どのpicにどのパレットが必要かを知るのが目的だ。で、EXE内の未調査のcall 441cがいくつあるかというと、17箇所ある。で、そのうちの1つめを現在調査中だ。調査したところcall 441cを含むサブルーチン内にはファイルの読み込みがなかったから、サブルーチンの呼び出し元5か所をこれから調べる。

つまり私はこれから5か所の呼び出し元をひとつひとつ調べ、それが終わったらようやく17箇所あるcall 441cの次の箇所にとりかかる。この時点で残り16箇所だ。残り16個でも、もしもサブルーチン内にpicやpalのファイル読み込みがなかったら、呼び出し元を調べる。一体いつになったら全部終わるのかは、謎だ。



とはいえ、呼び出し元調査を始めると、いくつかのpicに対応するpalがわかってきた。

2つめの呼び出し元を調べる前に、私は今回わかったpalをpicに適用してみた。自分のやっている作業が正しいと確かめてから次へ行くべきだからだ。その結果がこれだ。
tt29.gif

絵が黒くなった。あなたは、これが何かを間違った結果だと思うだろうか。調べなければわからないが、これで正しい可能性もある。いつかの記事に私が書いた、フェードイン前のパレットは黒いという、その可能性だ。黒いけれども何やらうっすらと絵が見える。ものすごく暗いが、決してサイケな色使いではなく、赤であるべき所は赤、緑であるべき所は緑、青であるべき所は青のように見える。でも全体的に明度と彩度がめっちゃ低い。果たして、これはフェードイン前の暗さなのだろうか。それとも私のミスなのだろうか。

そこで私は98エミュでゲームを起動して、このシーンを確認し、フェードインしているかを見た。フェードインしていなかった!

また私のミスによるバグなのか。それとも何か事情があって絵が暗いのか。私のプログラムのバグは今の所みつからない。

私はこの絵のパレット実現している所の先をさらに解析し、パレットをいじっているならそれを知り、いじっていないならいないことをはっきりさせ、そして何とかしてこの絵の色を正しくするべきだ。

そこまで出来て初めて、私は「17箇所あるcallの1つめの、それを含むサブルーチンの呼び出し元が5か所あるうちの『2つめ』」に取りかかってよい。



また、やることが増えた。
先が、長すぎる。

アセンブラコードの解析は、基本的には簡単だ。限られた種類の命令が連続しているだけだ。それにもかかわらず、アセンブラコードの解析は大変だ。誰でもできる作業じゃない。なぜか。やっても、やっても、まだやっても、とことんやっても、なかなか先へ進まないからだ。体力と気力と健康は確実に少しずつ奪われてゆくのに、やるべき作業はまるで大海原のように先の先まで続き、いつ終わるかわからない。これが、アセンブラの恐さだ。

コメント(0) 

孤独へ向って突っ走れ (33) [  PC-98x1(補完計画)]

前回の記事で、picファイルのヘッダ部分の中で唯一未解析の2バイトは、残念ながらパレットを意味すると思えない、という結果となった。

それで私は(31)の記事の「結論」の2番目、EXEファイル内を探して各picファイルに対応するパレットファイルを知る、という大変な作業にとりかかった。「結論」の3番目はやらないと明言しているから、これが最後の作業だ。成功しても失敗しても最後の作業だ。

まず私はプログラムを組み、EXEファイルの中に出てくる
call 6fec 何かのファイルを読み込むルーチン
call 441c パレット実現ルーチン
call ef08 picファイル展開ルーチン
を全部書き出した。とくに注目するのはcall 441cだ。特定のpicファイルをどの時点で読み込むかは、サブルーチンに渡されるファイル名のアドレスが即値でないから検索できないが、とにかくpicファイルを展開する直前(または直後)にはそれにふさわしいパレットを実現するに違いない。だからパレット実現のコードの前後を見れば、picファイルを読み込む場所が突き止められるかもしれない。

冗長になるが、書き出した結果を載せよう。これを見れば、私がこれからやる作業がどれだけ大変か、けっしてチョチョイのチョイとできる作業ではないことがわかってもらえるだろう。

CS:0014 call 6fec 何かのファイルを読み込んだ
CS:00BE call 441c パレット実現
CS:0126 call 6fec 何かのファイルを読み込んだ
CS:014E call 6fec 何かのファイルを読み込んだ
CS:0163 call 6fec 何かのファイルを読み込んだ
CS:017F call 6fec 何かのファイルを読み込んだ
CS:019E call 6fec 何かのファイルを読み込んだ
CS:01AF call 6fec 何かのファイルを読み込んだ
CS:01D1 call 441c パレット実現
CS:01E9 call 6fec 何かのファイルを読み込んだ
CS:0292 call 6fec 何かのファイルを読み込んだ
CS:02C9 call 6fec 何かのファイルを読み込んだ
CS:02EB call 441c パレット実現
CS:02F6 call 6fec 何かのファイルを読み込んだ
CS:04E2 call 6fec 何かのファイルを読み込んだ
CS:095D call 6fec 何かのファイルを読み込んだ
CS:4064 call 6fec 何かのファイルを読み込んだ
CS:4097 call 6fec 何かのファイルを読み込んだ
CS:40C6 call 6fec 何かのファイルを読み込んだ
CS:4182 call 441c パレット実現
CS:4360 call 441c パレット実現
CS:44CF call 441c パレット実現
CS:5D25 call 6fec 何かのファイルを読み込んだ
CS:5D9B call 6fec 何かのファイルを読み込んだ
CS:5DBC call 6fec 何かのファイルを読み込んだ
CS:5EA5 call 6fec 何かのファイルを読み込んだ
CS:5EF4 call 441c パレット実現
CS:5F16 call 6fec 何かのファイルを読み込んだ
CS:5F30 call 6fec 何かのファイルを読み込んだ
CS:6011 call 6fec 何かのファイルを読み込んだ
CS:602E call 6fec 何かのファイルを読み込んだ
CS:604B call 6fec 何かのファイルを読み込んだ
CS:72AD call 6fec 何かのファイルを読み込んだ
CS:72CE call 441c パレット実現
CS:72E5 call 6fec 何かのファイルを読み込んだ
CS:72F2 call ef08 picファイル展開
CS:730F call 6fec 何かのファイルを読み込んだ
CS:731B call ef08 picファイル展開
CS:7742 call 6fec 何かのファイルを読み込んだ
CS:7763 call 441c パレット実現
CS:776A call 6fec 何かのファイルを読み込んだ
CS:7777 call ef08 picファイル展開
CS:778D call ef08 picファイル展開
CS:7D41 call 6fec 何かのファイルを読み込んだ
CS:7DEF call 6fec 何かのファイルを読み込んだ
CS:7E42 call 6fec 何かのファイルを読み込んだ
CS:7E4E call ef08 picファイル展開
CS:888F call 6fec 何かのファイルを読み込んだ
CS:88B0 call 441c パレット実現
CS:88B7 call 6fec 何かのファイルを読み込んだ
CS:88C3 call ef08 picファイル展開
CS:88D8 call ef08 picファイル展開
CS:88F1 call 6fec 何かのファイルを読み込んだ
CS:8936 call 6fec 何かのファイルを読み込んだ
CS:A52C call 6fec 何かのファイルを読み込んだ
CS:A548 call 6fec 何かのファイルを読み込んだ
CS:A556 call 6fec 何かのファイルを読み込んだ
CS:A564 call ef08 picファイル展開
CS:A582 call 6fec 何かのファイルを読み込んだ
CS:A590 call 6fec 何かのファイルを読み込んだ
CS:A59E call ef08 picファイル展開
CS:A5D9 call 6fec 何かのファイルを読み込んだ
CS:A5E7 call 6fec 何かのファイルを読み込んだ
CS:A5F5 call ef08 picファイル展開
CS:A630 call 6fec 何かのファイルを読み込んだ
CS:A63E call 6fec 何かのファイルを読み込んだ
CS:A64C call ef08 picファイル展開
CS:A687 call 6fec 何かのファイルを読み込んだ
CS:A695 call 6fec 何かのファイルを読み込んだ
CS:A6A3 call ef08 picファイル展開
CS:A766 call 6fec 何かのファイルを読み込んだ
CS:A782 call 6fec 何かのファイルを読み込んだ
CS:A790 call 6fec 何かのファイルを読み込んだ
CS:A79E call ef08 picファイル展開
CS:A7F9 call 6fec 何かのファイルを読み込んだ
CS:A807 call 6fec 何かのファイルを読み込んだ
CS:A815 call ef08 picファイル展開
CS:A870 call 6fec 何かのファイルを読み込んだ
CS:A87E call 6fec 何かのファイルを読み込んだ
CS:A88C call ef08 picファイル展開
CS:A94B call 6fec 何かのファイルを読み込んだ
CS:A9A2 call 441c パレット実現
CS:A9AA call 6fec 何かのファイルを読み込んだ
CS:A9B1 call 6fec 何かのファイルを読み込んだ
CS:A9B8 call 6fec 何かのファイルを読み込んだ
CS:A9D6 call 6fec 何かのファイルを読み込んだ
CS:A9F8 call 441c パレット実現
CS:AA02 call 6fec 何かのファイルを読み込んだ
CS:AA13 call ef08 picファイル展開
CS:AA29 call ef08 picファイル展開
CS:AA83 call 6fec 何かのファイルを読み込んだ
CS:AAA5 call 441c パレット実現
CS:AAAF call 6fec 何かのファイルを読み込んだ
CS:AAC0 call ef08 picファイル展開
CS:AAD6 call ef08 picファイル展開
CS:AB2E call 6fec 何かのファイルを読み込んだ
CS:AB3F call ef08 picファイル展開
CS:AB55 call ef08 picファイル展開
CS:AB69 call 6fec 何かのファイルを読み込んだ
CS:AF13 call 6fec 何かのファイルを読み込んだ
CS:AF26 call ef08 picファイル展開
CS:B0EF call 6fec 何かのファイルを読み込んだ
CS:B106 call ef08 picファイル展開
CS:B11E call 441c パレット実現
CS:B19B call 6fec 何かのファイルを読み込んだ
CS:B1B2 call ef08 picファイル展開
CS:B1CA call 441c パレット実現
CS:B246 call 6fec 何かのファイルを読み込んだ
CS:B25D call ef08 picファイル展開
CS:B275 call 441c パレット実現
CS:B2ED call 6fec 何かのファイルを読み込んだ
CS:B304 call ef08 picファイル展開
CS:B314 call 6fec 何かのファイルを読み込んだ
CS:B322 call ef08 picファイル展開
CS:B33A call 441c パレット実現
CS:B398 call 6fec 何かのファイルを読み込んだ
CS:B3AF call ef08 picファイル展開
CS:B3C7 call 441c パレット実現
CS:B5CC call 6fec 何かのファイルを読み込んだ
CS:B5E3 call ef08 picファイル展開
CS:B5FB call 441c パレット実現
CS:B7DC call 6fec 何かのファイルを読み込んだ
CS:B7F3 call ef08 picファイル展開
CS:B80B call 441c パレット実現
CS:BBDB call 6fec 何かのファイルを読み込んだ
CS:BBE8 call ef08 picファイル展開
CS:BDBC call 6fec 何かのファイルを読み込んだ
CS:BE75 call 6fec 何かのファイルを読み込んだ
CS:BE87 call ef08 picファイル展開
CS:BE9E call ef08 picファイル展開
CS:BECC call 441c パレット実現
CS:BF72 call 6fec 何かのファイルを読み込んだ
CS:BF84 call ef08 picファイル展開
CS:C070 call 6fec 何かのファイルを読み込んだ
CS:C556 call 6fec 何かのファイルを読み込んだ
CS:C563 call ef08 picファイル展開
CS:C64C call 6fec 何かのファイルを読み込んだ
CS:C659 call ef08 picファイル展開
CS:D156 call 6fec 何かのファイルを読み込んだ
CS:D163 call ef08 picファイル展開
CS:D7DA call 6fec 何かのファイルを読み込んだ
CS:D7E7 call ef08 picファイル展開
CS:D8CA call 6fec 何かのファイルを読み込んだ
CS:D913 call 6fec 何かのファイルを読み込んだ
CS:D920 call ef08 picファイル展開
CS:DC20 call 6fec 何かのファイルを読み込んだ
CS:DC82 call 6fec 何かのファイルを読み込んだ
CS:DCE4 call 6fec 何かのファイルを読み込んだ
CS:E41A call 6fec 何かのファイルを読み込んだ
CS:E427 call ef08 picファイル展開
CS:E453 call 6fec 何かのファイルを読み込んだ
CS:E460 call ef08 picファイル展開
CS:E49D call 6fec 何かのファイルを読み込んだ
CS:E4AA call ef08 picファイル展開
CS:E976 call 6fec 何かのファイルを読み込んだ
CS:E983 call ef08 picファイル展開

次に私がしたのは、今までの解析の中に出てきたcall 441cを調べることだ。すでに解析済みのcall 441cはもう調べる必要がないし、解析済みならそれに関連するpicファイルもわかっているだろう。その結果を私はMicrosoft Word文書に書き込んだ。こんな感じだ(一部分)。
tt28.gif

picとpalの関係は、まだ、これしか判明していない。この時点で私はちょっと気力が低下して休んだ。気づけばもう1か月以上もこの作業をしている。これから先の作業が長いとわかって、さすがに疲れを感じたらしい。でも気力が回復したらすぐにまた作業を再開する。

コメント(0) 

孤独へ向って突っ走れ (32) [  PC-98x1(補完計画)]

いま私がやるべきことは、個々のpicファイルに必要なパレットファイルを突き止めることだ。
前回の記事で私は次のように書いた。

結論。私がこれからやることは、
1.まずpicファイル内の不明な2バイトが何かを突き止める。
(以下略)

この不明な2バイトがもしもパレットファイルを意味する値ならば、私はパレットファイルを突き止められることになる。私は行動を開始した。



私はすべてのpicファイルの先頭からオフセット6,7の2バイトを読んで書き出すプログラムを組んだ。

A01.PIC 0B61
A02.PIC 10ED
A03.PIC 1142
A04.PIC 0CB1
A05.PIC 12BA
A06.PIC 1057
A07.PIC 07E5
B01.PIC 30C0
B02.PIC 46C2
C01.PIC 6200
C02.PIC 5C62
C03.PIC 4DF1
C04.PIC 4D91
C05.PIC 4B19
C06.PIC 549C
C07.PIC 4752
C08.PIC 4ADB
C09.PIC 472E
C10.PIC 4F29
C11.PIC 4C37
C12.PIC 501E
C13.PIC 4242
C14.PIC 47EA
C15.PIC 530D
C16.PIC 50E3
C17.PIC 5C35
CCC.PIC 064C
COT1.PIC 2BF6
COT2.PIC 22D5
COT3.PIC 1990
E01.PIC 34C3
E02.PIC 6BB8
E03.PIC 4790
E04.PIC 4B2E
E05.PIC 73E2
E06.PIC 7539
E07.PIC 4C87
E08.PIC 4110
KAS1.PIC 0DEC
LL01.PIC 2CBF
LL02.PIC 403B
LL03.PIC 3930
LL04.PIC 39F0
LL05.PIC 3737
LL06.PIC 3227
LL07.PIC 2E39
LL08.PIC 343A
LL09.PIC 3101
LL10.PIC 33EE
LL11.PIC 37CB
LL12.PIC 2E6D
LL13.PIC 3019
LL14.PIC 347A
LL15.PIC 373F
LL16.PIC 3DD1
MAII.PIC 03A3
MAIN.PIC 1BD2
MAS1.PIC 0A2F
MAS2.PIC 0237
MAS3.PIC 088E
MENU1.PIC 06FF
OVER1.PIC 22C6
P011.PIC 2D79
P012.PIC 2FB6
P021.PIC 2D49
P022.PIC 308E
P031.PIC 2E5F
P032.PIC 311E
P041.PIC 2D9F
P042.PIC 2F21
P051.PIC 2DF0
P052.PIC 315D
P061.PIC 2E6C
P062.PIC 31A6
P071.PIC 2D4A
P072.PIC 2F8D
P081.PIC 2CED
P082.PIC 2F7D
P091.PIC 2D73
P092.PIC 30F1
P101.PIC 2D32
P102.PIC 2FFC
P111.PIC 2EBF
P112.PIC 3004
P121.PIC 2CA8
P122.PIC 2FCE
P131.PIC 2F60
P132.PIC 30DC
P141.PIC 2E1C
P142.PIC 3049
P151.PIC 2C82
P152.PIC 308C
P161.PIC 2E3F
P162.PIC 3075
P2.PIC 2C78
P3.PIC 38D0
POT.PIC 27F1
PTS1.PIC 3BAC
SAS1.PIC 0D31
SIM01.PIC 60F2
SK01.PIC 0BA0
SK02.PIC 0874
T01.PIC 65C1
T02.PIC 4750
T03.PIC FA13
T05.PIC 3E70
T06.PIC 53D9
T07.PIC 55AB
T08.PIC 5B47
T10.PIC 355D
T12.PIC 6C4A
TAS1.PIC 0D8F
TAS2.PIC 0434
TITLE1.PIC 9FB7
TITLE2.PIC 338F
VICB.PIC 672E
WW01.PIC 3906
WW02.PIC 4FCC
WW03.PIC 3D8E
WW04.PIC 392D
WW05.PIC 3F70
WW06.PIC 3BA6
WW07.PIC 3339
WW08.PIC 3AEC
WW09.PIC 32C1
WW10.PIC 3AC8
WW11.PIC 30E0
WW12.PIC 3885
WW13.PIC 3B7E
WW14.PIC 366A
WW15.PIC 3101
WW16.PIC 34E5
Z01.PIC E00F
Z02.PIC F711
Z03.PIC F1B8
Z04.PIC E822
Z05.PIC 81DA
Z06.PIC DFA9
Z07.PIC E6E3
Z08.PIC 7F4D

うーん、わからない。この2バイトは何に使うんだろう。たとえばT03.PICの読み込み前にはa:\pal\pa27.palというパレットファイルを読み込んでパレット実現している。これは今までの解析でわかった数少ない「picとpalの関連」のひとつだ。では、T03.PICのオフセット6から格納されているFA13Hと関連している数値が、a:\pal\pa27.palの周辺にあるだろうか。ファイル名にある27(10進数)=1BHとは関連がない。データセグメント内にあるa:\pal\pa27.palの情報はオフセット22B9Hから始まるが、これも関連がない。

話が逸れるが、パレットファイルa:\pal\pa27.palの情報をファイル読み込み用サブルーチンに渡す時、

LEA SI,[2049]
ADD SI,0270

としている。これが意味するものは、
パレットファイルのうちpa??.palというファイル名をもつ一群のファイルのための"ファイル情報群"はデータセグメントのオフセット2049Hから始まる。ファイル名の??の部分には1以上の10進数が連番で入る。ひとつのファイルの情報が24バイトと決まっているので、(??-1)*24がそのpa??.palのファイル情報の"ファイル情報群"先頭からのオフセットになる。pa27ならば(27-1)*24=270Hになる。

ここまではわかるが、依然としてpicファイルとそれに必要なpalファイルの関連にはたどり着けない。

コメント(0) 

孤独へ向って突っ走れ (31) [  PC-98x1(補完計画)]

EXEファイル内で特定のpicファイルが読み込まれる場所を探すのは大変だ。なにしろデータセグメント内の「読み込むファイルの情報を格納した領域」がファイル読み込み用サブルーチンに渡される時、アドレスが即値で渡されていないらしい。だから特定のpicファイルがどの時点で読み込まれるかをEXEファイルを検索して知るのが難しい。だから、その直前のパレット実現場所を見つけてそのパレットファイル読み込みを突き止め、パレットファイル名を取得するのはさらに難しい。

これは大変すぎるので後回しにする。先に、他の可能性をあたる。

私は思うのだが、たくさんある画像ファイルにどのパレットが適用されるべきかを記録し管理するのはソフト製作者さんにとって大変なことだったはず。だから、データのどこかにpicとパレットファイルの関連付けが記録されていても不思議ではない。その可能性があるのは4つの場所。

可能性その1。データセグメント内の「読み込むファイルの情報を格納した領域」にはひょっとして補足情報を格納する領域がないか。調べたがなかった。領域の先頭をデータセグメントのオフセットSIとすると、
WORD PTR[SI+0] ファイルを読み込むバッファのセグメント
WORD PTR[SI+2] ファイルを読み込むバッファのオフセット(実際には0000Hが格納される)
WORD PTR[SI+4] 読み込むバイト数(実際にはFFFFHが格納される)
WORD PTR[SI+6] ファイルが存在するフロッピーディスクドライブ
WORD PTR[SI+8] これ以降に、読み込むファイルの絶対パスがある(末尾は00H)
情報はここまでで、その次のアドレスからは別のファイルの情報が始まる。

可能性その2。picファイルの中に、適用すべきパレットファイルを意味する値が存在しないか。調べたところ、たったひとつ不明な領域がある。それはこれから調査する。picファイル先頭からのオフセットと各2バイトが意味するものは、
00H - 01H グラフィックの横バイト数
02H - 03H グラフィックの縦ライン数
04H - 05H 常に0000H
06H - 07H 何かの数値が入っているが内容不明
08H 1行目データ先頭アドレス(最初の1バイトはサブルーチンEF80H内で条件分岐に使う)
1行分のデータは縦ライン数分続き、それが4プレーン分続く。透明データは存在しない。
オフセット06Hからの2バイトだけが内容不明だ。これから調査しなければ。

可能性その3。フロッピーディスク内のどこかに別ファイルとして記録されている。これはないだろう。当時のソフトは、面倒なデータ圧縮をしていることからわかる通り、フロッピーという小容量メディアにギリギリ詰め込む努力をしていた。だから保守する人間だけが知っていればいい情報を製品内に別ファイルとして残さないだろう。残すとすれば、

可能性その4。製品内でなく、このソフトを作った会社のオフィスの机の引き出しまたはPCのハードディスク内にあった。これは私にはどうしようもない。

結論。私がこれからやることは、
1.まずpicファイル内の不明な2バイトが何かを突き止める。もしそれがパレットファイルを意味していないならば、
2.大変だけれどもEXEファイル内を探して、各picファイルに対応するパレットファイルを知る方法を考える。それが駄目ならば
3.数十個あるパレットファイルをpicファイルの一つ一つに適用してみて、いちばんそれらしい色を採用する。
でも最後の3は大変な手間がかかるのにプログラミングとかデータ解析という作業とは無縁だ。私はプログラマーだからこの作業をやっている。プログラミングにもデータ解析にも関係しない大変な作業は、私はやらないだろう。つまり2.が駄目ならその時点で私はやめるだろう。

コメント(0) 

孤独へ向って突っ走れ (30) [  PC-98x1(補完計画)]

展開ルーチンのデバッグができた。やっぱり私のミスだった。仕事でチームを組んでいるプログラマーと違って、趣味の私はプログラミングを自分ひとりでしているから、私にとってデバッグというのは自分が冒したどこかにあるミスを自分自身で追い詰めて突き止める行為だ。いわば自分がやった馬鹿を自分でどつきまわす作業だ。

もしも恋愛でもこれだけ注意深く詰将棋のように追い込んで行けば、ゴールインできたような気がする。でも恋愛は心が先走るから馬鹿ばかりやったなあ。

この時点で、このゲームの全部のpicを(Windowsエクスプローラのアイコン表示だけれども)UPしておく。なぜこの時点かというと、仮に将来パレットのほうもうまく行って正しい色で表示できたとしたら、いかに大昔のソフトといえども他人様のソフトの中身を全部出しまくるのは気が引ける。今ならまだ不完全な状態だから、出しまくるなら今だ。

それに、仮に今でもこのゲームをプレイする人が日本中にひとりくらい残っているとしたら、その人は「こういう形で絵を見るのは嫌」という可能性が大きいと思う。その人は「自分でプレイして初めて見るのが正しいんだ」と思うだろう。その人のためにも、色はサイケなままがいい。これでゲーム内にどんな画像があるかはわかった。後は自分でプレイして完全制覇を目指し、正しい色合いの絵は制覇した時に初めて見るというわけだ。

絵を担当したのはたしかVOGUEさんだった。納期までの限られた時間で、これだけの数のキャラクターをしっかり性格付けして描き分けるのは、さぞかし大変だっただろう。
tt15.jpg

tt16.jpg

tt17.jpg

tt18.jpg

tt19.jpg

tt20.jpg

tt21.jpg

tt22.jpg

tt23.jpg

tt24.jpg

tt25.jpg

tt26.jpg

tt27.jpg

私は、ひとまず休もうと思う。しっかり休んでから、きっとパレットのほうに取りかかるだろう。パレットファイルのフォーマットと実現方法はもうわかっている。まだ知らないのは、どのpicにどのパレットを適用したらいいかということだ。それがわからないと、「全部試して一番それらしいパレットにする」という情けない方法になってしまう。でもその情けないやり方はしたくない。

コメント(0) 

孤独へ向って突っ走れ (29) [  PC-98x1(補完計画)]

私が昔作ったアプリを雑誌で取り上げたいという有難いメールを2件いただいた。でも私はもう5年くらい心を押し潰されて心の病だ。アプリの開発を続ける気力も、人前に積極的に出て行って押し負けない気力も何もない。今の私には、アプリにたいする自分の抱負を書くというのはとても無理だ。ただ不安の中でいっしょうけんめい生きている。それでもこうやって昔のアセンブラを思い出して元気になろうとしている。これで精いっぱいだ。残念ではあるが雑誌掲載は辞退した。

画像展開は、昨日よりも少しだけましになった。でもまだ五里霧中だ。不気味なことに、同じ展開プログラムを使っても、滅茶苦茶な結果の画像と完全に正しいと思われる画像がある。
tt13.jpg

滅茶苦茶な結果の画像はどこから手を付けていいかわからないし、正しい画像からはデバッグのしようがないので、少しだけ変な画像をひとつ選んでデバッグを試みている。C02.picだ。

自作プログラムは、特定のpicだけを展開し、どのGVRMプレーンの何ライン目で処理を止めるか指定できるように変更した。ログも出力している。下のようなログだ。この場合、GVRAMプレーン1の最初のラインまでで処理を止めている。



C02.pic
plane0: 2444445666666666666666666666666
6666666666666666666666666666666666666
6666666666666666666666666666666666666
6666666666666666666666666666666666666
6666666666666666666666666666666666666
6666666666666666666666666666666666666
6666666666666666666666666666666666666
6666666666666666666666666666666666666
6666666666666666666666666666555566666
666622222
plane1: 2
DEBUG MODE[stopped at: plane=1,y=0]



plane0の最後に22222とあるのが気になる。これは画像の最下にある5ラインだが、出力された画像を見ると、ちょうどそこだけ乱れている。この近くに何かある。
tt14.png

でも、この先、どうやってバグを見つければいいのだろう。6つの展開ルーチン(このブログ記事では5つと表現しているが同じものだ)のどれにバグがあるのか。バグがあっても画像によってはそのバグが影響せずに正常な表示になるのはなぜか。

コメント(0) 

孤独へ向って突っ走れ (28) [  PC-98x1(補完計画)]

道はまだはるかに遠く続く。私は自分の頭の悪さが残念で仕方がない。すべての条件分岐を解析し終え、C++で展開プログラムを書いた。だがどこかに複数の間違いがある。最初は「初期化してないメモリを画像にした」みたいな延々と続くノイズ状の画像から始まった。まる一日かけて自作展開プログラムをデバッグして、ようやく人の顔らしきものが少し見え始めた。しかしこれも、各グラフィックプレーンの書き込み位置がずれてご覧の有様だ。
tt12.jpg

書き込み位置がずれるということは、データの読み取り位置も間違っているということで、展開ルーチンが(C言語でいうところの)ポインタを操作するタイプのコードだと、たまにとんでもないアドレスにアクセスしてMicrosoft Visual Studioに怒られる。もう今日はここまでにしなければ。逆アセンブル結果とC++のコードをじっくり比較して、地道にバグを見つけなければ駄目だと思う。

私はこんな状態のまま終わりにしたくない。一晩でも続けたい。でもこれ以上続ければ能率は非常に悪く、体も壊す。それで、自分を納得させるために、こんな中途半端な段階ですまないけれども記事を出させてほしい。

なお、画像に適したパレットデータはまだ使っていない。まだまだそんな段階ではないから。サイケな色になっているのは、わざとだ。どんな画像を表示しても物の形がわかるように、わざと極端なパレット配色にしている。

みなさん、おやすみなさい。明日が幸せな日でありますように。
コメント(0) 

孤独へ向って突っ走れ (27) [  PC-98x1(補完計画)]

前回の続き。
下の分岐の③と④。

LODSB
CMP AL,6F
JB EFB0②
JZ EFB4(②)
CMP AL,80
JB EFEB
CMP AL,90
JB EFD7③
CMP AL,C0
JB EFB0②
CMP AL,D0
JB EFAD④
JZ EFCE①
JMP Short EFB0②




③条件分岐用のバイトが80Hから8FHまでの場合。この分岐先では、picファイル内のビットマップデータをグラフィックVRAMに書き込まない。その代わりに、グラフィックVRAMのオフセットDIに、それよりも1ないし数ラインだけ画面表示で上のデータをコピーする。

訂正
この後に私が長々と書いていたことは間違っていた。この③はスクロールに使われるのではない。縦方向の画像圧縮だった。私はこれがわかるまでPCの前に座り続けたので足が変だ。(前から記事に書いているが、私は座り続けると足を悪くする。)続きは数日後でないと書けないだろうが、きっと良い結果を出す。



EFD7: ;JBのラベルはここ
AND AL,0F
XOR AH,AH
サブルーチンEF80Hの冒頭で条件分岐に使ったALは、この分岐先では下位4ビットがパラメータ(00H - 0FH)になっている。

MOV BX,CS:[EF06]
CS:[EF06]には呼び出し元のサブルーチンEF08Hで50H(=80) が入れてある。これはグラフィックVRAMの1ラインのバイト数。

MUL BL
1ラインのバイト数に先ほどのパラメータを掛ける。

MOV BX,DI
SUB BX,AX
その結果をDIから引くと、グラフィックVRAMのDIよりも(パラメータの値)ライン分画面表示で上の場所になる。これをBXに入れる。

CALL F029
グラフィックVRAMのオフセットBXからオフセットDIへ指定横バイト数コピーする。
サブルーチンF029Hの解析結果はこの下に。

JMP Short EFA3




サブルーチンF029H
ES(=グラフィックVRAM)のオフセットBXから同じくES(=グラフィックVRAM)のオフセットDIへCS:[EF00](=指定横バイト数)バイトをコピーする。

PUSH DI
XCHG SI,BX

MOV CX,CS:[EF00]
REP MOVS Byte Ptr [DI],ES:[SI]
本来MOVSはDSからESへ転送するが、第2オペランドをESにオーバーライドしているのでESからESへの転送になる。

XCHG SI,BX
POP DI
RET




④条件分岐用のバイトがC0HからCFHまでの場合。この分岐先では、まず③とまったく同じコードでグラフィックVRAMのオフセットDIに、それよりも1ないし数ラインだけ画面表示で上のデータをコピーする。それに続けて、DIから指定オフセットの場所に指定の1バイトを書き込む。条件分岐用の1バイトの後は、(書き込む場所(DIからのオフセット))と(書き込むデータ1バイト)との組み合わせが続く。
私はこのルーチンの使い途が想像できないが、グラフィックVRAMの指定矩形領域内のすべてを更新するのでなく、既存データとの差分だけを書き込んでいるようだ。それだけならデータ量節約として理解できるが、その前に③の処理が付いている。

追記
わかった。やはり差分だった。縦方向の画像圧縮だった。



EFAD: ;JBのラベルはここ
JMP F038
JBのジャンプ先が遠すぎたのか、続けてJMPする。

F038: ;JMPのラベルはここ
AND AL,0F
XOR AH,AH
MOV BX,CS:[EF06]
MUL BL
MOV BX,DI
SUB BX,AX
CALL F029
グラフィックVRAMのオフセットDIに、それよりも1ないし数ラインだけ画面表示で上のデータをコピーする。ここまで③と同じ。

F04A: ;JNBのラベルはここ
LODSB
MOV BL,AL
MOV AH,AL
picファイルのオフセット9(初回)から1バイト取り出し、BLとAHに入れる。

AND BL,7F
XOR BH,BH
BL(picファイルのオフセット9(初回))の最上位ビットはデータ終了のフラグだが、これをマスクして0とし、その値をBXとする。

LODSB
MOV ES:[BX+DI],AL
picファイルのオフセット0AH(初回)から1バイト取り出し、グラフィックVRAMのオフセットBX+DIに書き込む。

SHL AH,1
JNB F04A
AH(picファイルのオフセット9(初回))の最上位ビットが0ならばF04AHへ戻って繰り返す。1ならばループから抜ける。

2C70:F05C E944FF JMP EFA3

コメント(0) 

孤独へ向って突っ走れ (26)  [  PC-98x1(補完計画)]

ビットマップデータの展開ルーチンに到達した。しかし例の「ANDをとってORをとる」をやっていない。つまり、このままでは背景との重ね合わせができないはずだ。ということは、まだ先があるのかもしれない。現在解析進行中。

picファイルのヘッダ情報により、5つの条件分岐先へ振り分けられ、そこに展開ルーチンがある。簡単そうなルーチンから解析を始めたので、まだ日高総帥の著書にあったような実践的な圧縮展開ルーチンは出てきていない。

私が日高総帥の著書を処分してしまってから10年か20年が経っているはずだが、アセンブラに接していると本の内容が部分的に頭に蘇ってくる。良い本は人の血肉となって生き続けるという好例だ。たとえ本そのものは消滅してしまっても、その本は人の頭の中に在り続ける。




サブルーチンEF80H

この構造化されていないがゆえに複雑なルーチンは、巨大なLOOPでできている。
このLOOPは次回繰り返しの直前にDIに50H(=80) を足すことから考えて、グラフィックVRAMへの縦方向の描画ライン数と思われる。

MOV CX,CS:[EF02]
まずCXにコードセグメントのオフセットEF02Hからの2バイトを入れる。これが巨大なLOOPの回数となる。
(CS:[EF02]には呼び出し元のサブルーチンEF08Hで先ほどpicファイルを読み込んだ場所からオフセット2以降の2バイトを入れてある。グラフィックの縦ライン数と思われる。)

EF85: ;LOOPのラベルはここ
PUSH CX
PUSH DI
ここから巨大なLOOPが始まる。

LODSB
CMP AL,6F
JB EFB0②
JZ EFB4(②)
CMP AL,80
JB EFEB
CMP AL,90
JB EFD7
CMP AL,C0
JB EFB0②
CMP AL,D0
JB EFAD
JZ EFCE①
JMP Short EFB0②
LOOP内ではまずDS:SI(先ほどpicファイルを読み込んだ場所のオフセット8)の1バイトの値を調べ、5通りの条件分岐をする。

条件分岐先の各処理は先の方のアドレスに存在し、その処理が終わるとEFA3Hに飛んでくる。

EFA3: ;JMP Shortのラベルはここ
POP DI
POP CX
ADD DI,CS:[EF06]
CS:[EF06]には呼び出し元のサブルーチンEF08Hで50H(=80) が入れてあり、DIをグラフィックVRAM内の1ライン下に変更する。
LOOP EF85
巨大なLOOPをCX回繰り返したら、ループから抜けてサブルーチンが終わる。

RET




①条件分岐用のバイトがD0Hの場合。5つの分岐先の中でいちばん単純で、素直に指定横バイト数だけVRAMに書き込む。

EFCE: ;JZのラベルはここ
MOV CX,CS:[EF00]
REP MOVSB
JMP Short EFA3




②条件分岐用のバイトが6EH以下、90HからBFHまで、D1H以上の場合。この分岐へ来る条件は、値が小さかったり中間だったり大きかったり、色々な値に散在している。
この分岐先に来るデータには、同じ値のバイトが連続する場合に効果的なきわめて基本的な圧縮がかけられている。条件分岐用の1バイトの後は、(書き込むデータ1バイト)と(書き込む回数1バイト)の組み合わせが続く。ただし、同じ値のバイトが続かないデータだと、最悪で2倍のデータ量になる。圧縮の効果がある時だけ使うのだろう。
1行の最初の(書き込むデータ1バイト)が条件分岐用の1バイトでもあるので、他の条件分岐へ行くための値にはできない。

EFB0: ;JBふたつとJMP Shortのラベルはここ
DEC SI
JMP Short EFB7
SIが条件分岐用のバイトを指すように戻す。

EFB7: ;JMP Shortのラベルはここ
MOV DX,CS:[EF00]
指定横バイト数をDXに入れ、ループ開始。ただしこのループは単純なループではない。全体のDX回のループはJMP Short EFBCで構成している。その中で、(書き込むデータ1バイト)を(書き込む回数1バイト)の回数だけ書き込むのにLOOP EFC4を使う。

EFBC: ;JMP Shortのラベルはここ
LODSB
MOV CL,AL
LODSB
XCHG AL,CL
XOR CH,CH
(書き込むデータ1バイト)をALに入れる。
(書き込む回数1バイト)をCXに入れる。

EFC4: ;LOOPのラベルはここ
STOSB
DEC DX
JZ EFCC
LOOP EFC4
JMP Short EFBC
ALの値をCX回続けてグラフィックVRAMに書き込む。全体の書き込みバイト数がDXに達したらループを抜ける。

EFCC: ;JZのラベルはここ
JMP Short EFA3




追記
条件分岐用のバイトが6FHの場合。この分岐先は、ほとんど②の処理そのものだ。ただ冒頭の、「SIが条件分岐用のバイトを指すように戻す」処理だけを行わない。つまりこの分岐先に来た時は、1行の最初の(書き込むデータ1バイト)は条件分岐用のバイトの次。このフォーマットがあれば、最初の(書き込むデータ1バイト)に値の制約がある②は必要ないのだが、下位バージョン互換のためにコードを残したのか。それとも、すでに②のフォーマットで作ってしまったpicファイルをこのフォーマットに作り直す暇がなかったのか。それとも、②が使える時にこのフォーマットを使うと生じる1バイトの無駄を作りたくなかったのか。

EFB4: ;JZのラベルはここ
JMP Short EFB7

コメント(0) 

孤独へ向って突っ走れ (25) [  PC-98x1(補完計画)]

私はプログラムを組んだ。t.exeを読み込み、コードセグメントにロードされる部分以降でCALL 6FECに相当するコードを見つけ、そのオフセットを書き出すプログラムだ。near callの場合、アセンブラで記述する時にラベルを使うと絶対オフセットではなくIPからの相対オフセットで機械語になるのをうまく計算してCALL 6FEC相当の3バイトを見つけ出すのがミソだ。途中、出力先のファイルに何も書き出されないという問題に頭を悩ませながら、Unicodeを使ったのにsetlocale()をすっかり忘れていた自分を情けなく思いつつ、なんとか目的は達成した。以下の行が、プログラムによって書き出された結果だ。



探すコードは CALL 6FEC

該当するコードを発見 CS:0014
該当するコードを発見 CS:0126
該当するコードを発見 CS:014E
該当するコードを発見 CS:0163
該当するコードを発見 CS:017F
該当するコードを発見 CS:019E
該当するコードを発見 CS:01AF
該当するコードを発見 CS:01E9
該当するコードを発見 CS:0292
該当するコードを発見 CS:02C9
該当するコードを発見 CS:02F6
該当するコードを発見 CS:04E2
該当するコードを発見 CS:095D
該当するコードを発見 CS:4064
該当するコードを発見 CS:4097
該当するコードを発見 CS:40C6
該当するコードを発見 CS:5D25
該当するコードを発見 CS:5D9B
該当するコードを発見 CS:5DBC
該当するコードを発見 CS:5EA5
該当するコードを発見 CS:5F16
該当するコードを発見 CS:5F30
該当するコードを発見 CS:6011
該当するコードを発見 CS:602E
該当するコードを発見 CS:604B
該当するコードを発見 CS:72AD
該当するコードを発見 CS:72E5
該当するコードを発見 CS:730F
該当するコードを発見 CS:7742
該当するコードを発見 CS:776A
該当するコードを発見 CS:7D41
該当するコードを発見 CS:7DEF
該当するコードを発見 CS:7E42
該当するコードを発見 CS:888F
該当するコードを発見 CS:88B7
該当するコードを発見 CS:88F1
該当するコードを発見 CS:8936
該当するコードを発見 CS:A52C
該当するコードを発見 CS:A548
該当するコードを発見 CS:A556
該当するコードを発見 CS:A582
該当するコードを発見 CS:A590
該当するコードを発見 CS:A5D9
該当するコードを発見 CS:A5E7
該当するコードを発見 CS:A630
該当するコードを発見 CS:A63E
該当するコードを発見 CS:A687
該当するコードを発見 CS:A695
該当するコードを発見 CS:A766
該当するコードを発見 CS:A782
該当するコードを発見 CS:A790
該当するコードを発見 CS:A7F9
該当するコードを発見 CS:A807
該当するコードを発見 CS:A870
該当するコードを発見 CS:A87E
該当するコードを発見 CS:A94B
該当するコードを発見 CS:A9AA
該当するコードを発見 CS:A9B1
該当するコードを発見 CS:A9B8
該当するコードを発見 CS:A9D6
該当するコードを発見 CS:AA02
該当するコードを発見 CS:AA83
該当するコードを発見 CS:AAAF
該当するコードを発見 CS:AB2E
該当するコードを発見 CS:AB69
該当するコードを発見 CS:AF13
該当するコードを発見 CS:B0EF
該当するコードを発見 CS:B19B
該当するコードを発見 CS:B246
該当するコードを発見 CS:B2ED
該当するコードを発見 CS:B314
該当するコードを発見 CS:B398
該当するコードを発見 CS:B5CC
該当するコードを発見 CS:B7DC
該当するコードを発見 CS:BBDB
該当するコードを発見 CS:BDBC
該当するコードを発見 CS:BE75
該当するコードを発見 CS:BF72
該当するコードを発見 CS:C070
該当するコードを発見 CS:C556
該当するコードを発見 CS:C64C
該当するコードを発見 CS:D156
該当するコードを発見 CS:D7DA
該当するコードを発見 CS:D8CA
該当するコードを発見 CS:D913
該当するコードを発見 CS:DC20
該当するコードを発見 CS:DC82
該当するコードを発見 CS:DCE4
該当するコードを発見 CS:E41A
該当するコードを発見 CS:E453
該当するコードを発見 CS:E49D
該当するコードを発見 CS:E976

わあーい、たくさん出てきたぞ。この結果からわかることは、最初からコードを辿って解析していたら、私はとてもじゃないが目的にたどり着けなかったということだ。そんなことをしたら何年かかっていたことか。そしてついには止めてしまっただろう。

上のCALLから後のコードをしらみ潰しに解析するのだって、果たして気力がもつかどうか。

PC-98全盛時代に、ゲームプログラムを解析してズルをして先へ行くのを毛嫌いしている人がいた。確かに一般的な感覚ではそれはズルだ。でも私には、自分のプログラミングレベルでは出来ない羨望の対象だった。ズルを毛嫌いする人は、そのズルのためにこうやって何ヵ月かかるかわからない解析を来る日も来る日も続けなきゃならないとは夢にも思っていないだろう。きっと、チョチョイのチョイと片手間にやっていると思っていただろう。そりゃあ結果だけをパッチとかいう形で使う人はチョチョイのチョイだろうけど、解析する側はそんなんじゃない。少なくとも私のレベルでは、先に成功が待っているか失敗が待っているかわからない作業だ。でも私より出来るプログラマーの背中を見つめて羨ましく思いながら、自分もそのレベルに追いつきたいと頑張る。学習する価値のある学問だと信じる。じつは今回、ニーモニックではなく機械語のバイトを扱って、初めて私はホッとした。やっと自分が学びたかった領域に手が届いた。もしもゲームなら、私個人はそれをここまで情熱的にプレイすることはできない。私はゲーマーではなくプログラマーとしてしか生きられない。

コメント(0) 

孤独へ向って突っ走れ (24) [  PC-98x1(補完計画)]

今までかかってゲーム本編にたどり着くどころか、いつになったらたどり着けるかわからない。もしも絵を読み込む部分のコードを探し当てられれば、そこから解析を始めることによってビットマップデータのフォーマット解析に早くたどり着ける。

フロッピーディスク内の拡張子picのファイルがビットマップデータだというのは想像できる。もっとも、ファイル名からはどんな絵か見当がつかない。

試しにどれかひとつを選び、それをディスクから読み込む部分のCALL 6FECを特定できるか試す。b:\tennis\pic4\z08.picを選んだ。

ファイルを読み込むサブルーチン6FECHは、データセグメントの[SI+8]からDOSファイル名を取得する。SIに値を入れるLEAを見つけられるか。文字列b:\tennis\pic4\z08.picが格納されているのはEXEファイル先頭からのオフセットで3C6DH。これはデータセグメント先頭からのオフセットで3C6DH - 0C00H = 306DH。これから8を引くと3065H。

この値65 30(念のために30 65も)をバイナリエディタで検索したが、見つからなかった。世の中そんなに甘くないか。

では仕方がない、CALL 6FECを意味するE8 C3 6Eを検索しよう。おかしい。E8 C3 6EがEXE内にひとつしかない。このサブルーチンは何度もCALLされているのに。

さっきは2回目のCALL 6FECを調べた。試しに最初のCALL 6FECを調べてみると、E8 D5 6Fだった。

これは参った。CALLという命令は飛び先のアドレスを直接指定しないのか。サブルーチンを呼び出すたびにコードが違うらしい。

今の私が功を焦っているのは確かだ。最初に戻ろう。すでに解析済みの部分に出てきたa:\pic\w.gを使って私の計算が正しいかどうか調べた。文字列a:\pic\w.gが格納されているのはEXEファイル先頭からのオフセットで2B72H。これからヘッダのサイズを引くと2B72H - 0C00H = 1F72H。さらに8を引くと1F6AHで、これは逆アセンブル結果で確認したSIの値だ。計算方法は合っている。

a:\pic\w.gの場合はLEAを使ってSIに即値を入れている。LEAを使うと即値はコード内に下位バイト上位バイトの順にそのまま現れる。先ほどEXEファイル内を検索して見つからなかった値は、即値以外の形でSIに入れられているに違いない。

うーん、私には、解析済み部分の先を地道に少しずつ解析してゆく方法しか残されていないのだろうか。




そして一晩が過ぎ、翌日となった。人間誰でも心に恐れているものがあると私は信じる。怒り・憎しみ・恐れ、それらは実は別個のものでなく、相互に関連しあっている。今日は選挙だ。私は恐れと戦いつつ、もうじき選挙に行こうとしながら、これを書いている。

CALL。最初の1バイトはどうやら常にE8だ。次の2バイトは飛び先のアドレスではない。となると現在IPと飛び先との相対的な位置関係かもしれない。試してみよう。

相対的ということは、マイナスもありうるということだ。

CS:0014にあるCALL 6FECは、E8 D5 6F。
CS:0126にあるCALL 6FECは、E8 C3 6E。

0014H + 6FD5H = 6FE9H
0126H + 6EC3H = 6FE9H

惜しいな。CALL命令が実行された後のIPか。3バイトを足すと

6FE9H + 3 = 6FECH

よし、NEAR CALLはわかった。

なにしろ、初めは読み込むファイル名のアドレスからLEA SI,[...]を探し出そうとしていたが、それが無理だったので、そうなるとファイルを読み込むサブルーチンを呼び出すCALL 6FECの場所を突き止めるしかないだろう。そうでなければこれから何か月・何年とかけてコードセグメントを全部解析することになってしまう。

さあ、選挙に行く準備だ。

コメント(0) 

孤独へ向って突っ走れ (23) [  PC-98x1(補完計画)]

ああ、ようやくサウンド関係のファイルを読み込んだぞ。不正ディスクを除外したので、これでやっとBGMを鳴らそうという事らしい。しかし、ゲーム本体への道のりはまだあまりに遠い。

あまりに遠そうだから、どれだけ遠いか、ゲームを起動して確認してみた。ああ、まだプレーヤーの名前を決定していない。私にとって不要なルーチンが山のように出てくるらしいぞ。BGMだって、それ自体は興味深い学習対象だが私の目的とは関係しない。

これは、もう、ちょっと辛すぎるかも。

DOSのファイル名を使ってディスクからデータをバッファに読み込むルーチンはすでに出てきているし、その時にデータセグメントのどういうアドレスを指定したら良いかもわかっている。ということは、データセグメント内にあるファイル名からサブルーチンに渡すアドレスがわかるので、プログラムを組んでEXEファイル内を検索して、そのコードを見つけ出せば、不要な山のようにあるルーチンを飛び越えて本当に解析するべき部分にたどり着けないだろうか。

この試みを始めた頃はMS-DOS時代のEXEファイルの構造がわからないでいたが、実はあれから情報収集したので少しだけわかった。

昔のEXEファイルは、まずヘッダがあり、その後にロードモジュールがあった。ヘッダのオフセット08Hからの2バイトに、ヘッダのサイズが入っている。これは同時にヘッダの直後から始まるロードモジュールの開始点でもある。ただしサイズは、16バイトパラグラフ単位で入っている。ヘッダ内のオフセット16Hからの2バイトに、ロードモジュール内のコードセグメントのオフセットが入っている。ただしこれも、16バイトパラグラフ単位で入っている。

これだけではまだ足りない。データセグメントという言葉がどこにも出てこない。ロードモジュール内のデータセグメントのオフセットはどこだ。データセグメントやコードセグメントの情報は何バイトロードされるのか。そのへんの事が、必ずわかるようになっているはずなのだが。

とにかく試してみよう。

私はt.exeをバイナリエディタ(ビューアーとして使う)に読み込んだ。ファイル先頭からオフセット08H以降の2バイトは、00C0H。これに10Hを掛けると、0C00H。バイナリエディタでその場所を見ると、前も後も00Hだらけだ。本当にここがロードモジュールの先頭アドレスなのか。

00Hばっかりだから、見るからにコードセグメントにロードされる情報ではない。データセグメントにロードされるのかもしれない。PC-98エミュ内でdias.exeを使い、データセグメントの内容を表示して比べよう。今までの解析で、データセグメントが1D7CHだということはわかっている。それ以降00Hがずっと続き、初めて00H以外が出てくるのが1D7C:1134だ。
TT08.png

次に、EXEファイル内を見ているバイナリエディタのほうで0C00H + 1134H = 1D34Hを見てみた。(ドキドキ。)
TT09.png

よっしゃぁッ!データセグメントにロードされる内容は、EXEファイル先頭からオフセット08H以降に格納されている2バイトの値に10Hを掛けた値をEXEファイル先頭からのオフセットとする場所から始まっている。(どこまで続くかは知らないが。)ロードモジュール先頭にはまずデータセグメント用の情報があるのだ。

今度はコードセグメント用の情報の場所を確認する。EXEファイル内を見ているバイナリエディタのほうでファイル先頭からオフセット16H以降の2バイトを見ると0EF4H。これに10Hを掛け、先ほど調べた0C00H(ロードモジュール先頭をEXEファイル先頭からのオフセットで表した値)に足すと、FB40H。
tt10.png

これがdias.exeにt.exeを読み込んでuとだけコマンド入力してEnter押した時(コードセグメント先頭からの逆アセンブル)と同じならば全部オッケーだ。
tt11.png

はうう!なんか違っ・・・!なんかまたドキドキしてきたッ!B8 7C 1Dでなきゃいけないのに、B8 00 00じゃないか。

それから私は、ちょっとパニックになり、使っているバイナリエディタの機能でB8 7C 1Dを検索した。検索のしかたがわからなくてヘルプを参照しようと思ったら昔のhlpファイルだから開けなくて、hlpファイルを開くソフトをマイクロソフトが提供しているようだからダウンロードしたらどれもこれもWindows10に対応していなくて、ネット上に有難いパッチを見つけて、それだけの苦労をして検索した結果、B8 7C 1DはEXEファイル内に存在しないことがわかった!なんだとぉぉぉぉ!だって逆アセンブル結果にあるじゃん。B8 7C 1Dって。そう考える間も私の頭の片隅で常に何か声がしていた。前から思っていた疑問。EXEファイルの中身がPCのメモリにロードされる前に、どのアドレスにロードされるかわかってない気がするんだけど。でも逆アセンブルしたコードにはいろんな場所のアドレスが書いてある。オフセットはいいとしてセグメント値はメモリにロードされる時点ではじめて決まるんじゃないかなあ。それともEXEファイルにあらかじめ書かれたセグメントにプログラムがきっちりロードされるのかなあ。だって、EXEファイル内のコードに大量に書かれたセグメントのデータを、プログラムをPCのメモリにロードする時点で瞬時にバババッと書き換えるなんていう大変なことを、しているわけがないから。・・・と、思っていたけど、しているわけなんですね!ああ、ひょっとして、それがあの、私が理解不能で読み飛ばしたリロケーションテーブルとかいうものの機能なんでしょうか。

ここまででわかったこと。上記の方法でコードセグメントにロードされる情報の先頭がEXEファイル内のどこに相当するかがわかる。ただしコードセグメントの情報だろうとデータセグメントの情報だろうと、その中にある(グラフィックVRAMのように固定した値を除いて)セグメントの値は、PCのメモリにロードされる時点ではじめて決定されるので、EXEファイル内のデータとメモリにロードされたデータが異なる。もしもdias.exeで逆アセンブルした結果を元にEXEファイル内のバイナリデータを検索しようとするならば、その検索キーにセグメント値が含まれていると失敗する。

コメント(0) 

孤独へ向って突っ走れ (22) [  PC-98x1(補完計画)]

個人用メモ

アセンブラでは高級言語と違い、コードの読みやすさを追求しない。その代わりに処理の速さとサイズ、つまり命令に要するクロック数とバイト数を気にして、時に独特のコーディングをする。ただし使用するプロセッサが改善されると同じ命令に要するクロック数が少なくなったと記憶している(参考:ワイヤードロジック)。それに、時代と共にクロック周波数が格段に高くなり、処理速度が格段に速くなったので、アセンブラで記述してまでも高速化を求める場面は昔に比べると非常に少なくなったと思われる。

趣味でプログラミングをしている私にとっては、アセンブラを使うメリットはもうない。ただ、過去に私がアセンブラを学んだ直後にMASMが売られなくなるという時代的不運があり、充分に学んだり楽しんだりする時間がなく終わってしまったのが悔やまれる。そこで、昔を懐かしむ意味でアセンブラらしいコーディングを見つけたらメモしたいと考えた。

そういう事情なので、ここではプロセッサはいまだに昔懐かしい16ビットが想定される。EAXなどというレジスタは存在しない。32ビットだとデータセグメントをいじらずにプログラミングできるとネットで読んだが、16ビットプログラミングだからDSの値は時として変わる。

私が昔使っていたのがNEC PC-9801シリーズなので、まるで当たり前のようにプロセッサは8086系、アセンブラはMASMの文法になる。

以下は、「テニステニス2」の逆アセンブル結果解析で現在までに私が出会ったアセンブラらしいコーディングだ。

XOR AX,AX
MOV AX,0の代わりに使う。これのメリットは何だったのか、クロック数かバイト数か。今どきネット検索しても命令に必要なクロック数/バイト数の体系的なデータがなかなか見つからず、そもそも上記のようにプロセッサの進化によってそのメリットが無意味になってしまったであろうことは、昔を懐かしむ私としてはちょっと悲しい。むしろ、MOVではフラグレジスタが変化しないがXORではサインフラグ、ゼロフラグ等が影響を受けるということのほうが大事といえるだろう。

SHL AX,1
AXを2倍する。同様に4倍、8倍、16倍、2で割る、4で割る、8で割る、16で割ることができる。8086では乗除算に多くのクロック数を費やしたそうなので、特定の数による乗除算はこうしたほうが良かった。だから昔のプログラムでは普通こうしていると思われる。しかしこれも、上記ワイヤードロジックやクロック周波数の向上により、今ではそれほどのメリットがないのかもしれない。

ADD AX,AX
AXを2倍している。MULを使わずに高速化しているのだろう。しかしこれも(以下同文)

OR AL,AL
一見無意味なコードのように見えるが、これはALの値を変えずに、次に来る条件分岐命令でサインフラグ等を参照して分岐するのに使う。

PUSH DS
POP ES
DSの値をESに入れる。これはコードのバイト数を少なくするために使う。もしもMOVを使うなら、MOV ES,DSは不可なので、一度MOV AX,DSとしてからMOV ES,AXとしなければならない。PUSHとPOPはどちらも1バイトなので、全部で2バイトで済む。ただし処理に必要なクロック数は節約できない。

コメント(0) 

孤独へ向って突っ走れ(21) [  PC-98x1(補完計画)]

逆アセンブル結果の解析は、ようやくコピーディスク判別ルーチンに到達した。昔そういう本を読んだことのある人ならピクピクッと反応するINT 1Bなるものが出てきた。DOSのファイル名によらない、BIOSによる低レベルのディスクアクセスだ。このアクセスに成功してしまうと、それはコピーディスクということになる。

このプログラムではアクセスに成功するとキャリーフラグを立てる。BIOSの仕様では処理に失敗するとキャリーフラグが立つのだが、それをわざわざ反転して使っているのは、ひょっとすると「ディスクアクセス成功こそが失敗」という意味なのかもしれない。これをサブルーチンの呼び出し元で調べて条件分岐する。そして最後はデッド・エンド(行きどまり)となる。せっかくだからデッド・エンドの所を出しておこう。ここに到達して嬉しいのはコードの解析をする人だけだな。普通ユーザーは途方に暮れる。



MOV AX,0005
CALL 98D0
98D0Hへ行ってこい
(サブルーチン98D0Hは、AXの値を元にデータセグメントから文字列を取得し、グラフィックVRAMに書き込む。今回の文字列は" 本ディスクはコピーディスクの可能性があります。")

0174: ;JMP Shortのラベルはここ
NOP
JMP Short 0174
無限ループ突入



さて、これはまだ私の最終目的地ではない。私の目的は2つある。まず、私は大昔、若いころにコピーガード外しの本を読んであまりに難しく、自分にはできないと負けを認めた。それが今まで、趣味プログラマーとしての私の限界だった。今回その自分の限界を超えることが1つめの目的だ。それは今日、超えた。プロテクト外し自体はしていないが、その場所と条件分岐を理解できればプロテクト外しをしたも同然だから。2つめの目的は、普通のゲームプレイヤーならば何十回とゲームをプレイすることで達成する全シーンの制覇を、まったく別の方法で達成することだ。具体的には、コードをたどってゆき、ディスクからビットマップデータ(Windows Bitmapという意味ではない)とパレットデータをどのように読み込むかを解析し、その方法に従ってディスク内の画像をすべてGIFまたはPNGファイルにする。この2つめの目的は、達成可能かどうかがまだ不明だ。なぜなら当時の画像はビットマップデータとパレットデータが別々に存在し、たとえビットマップデータのフォーマットが解析できてもそれに正しいパレットを適用できなければとんでもなくサイケな色合いになってしまったり、真っ黒になってしまったり(フェードインを目的として初期パレットが設定されている場合)する。果たして、ビットマップデータ読み込み時にパレットがいつも同じ手順で読み込まれているだろうか。もしそうならばプログラムを組んでEXEファイル内を検索し、自動的にパレット付きで画像を読むことができそうだが。もしも画像ごとに異なるタイミングでパレットを実現していたら、パレット実現は難しくなる。まさかプログラムを全部最後まで追いかけて行きビットマップごとの正しいパレットデータをメモするなんていう酔狂な真似はできない。そういうわけでこの解析はまだ始まったばかりという所だ。先に成功があるか失敗があるかはまったく不明だが、今はとにかく一歩を踏み出すしかない。

コメント(0) 

孤独へ向って突っ走れ(20) [  PC-98x1(補完計画)]

孫悟空はいまだに釈迦の掌上を飛んでいる。その情けないご報告。

前回の記事(19)では表まで出して、それを解析しなければならないと書いた。そして私は家事とか仕事とか外国人への手紙とかを片付けるために逆アセンブラ結果の解析を中断した。

それからどうなったかというと、とても情けない状態になった。まず、前回の記事に出した表は、解析を進めるとひとまず条件分岐の中で一番単純な処理が使われていることがわかり、気負っていた私は気が抜けたというか、前回の記事で大見得を切った手前、どうしたもんかと悩んだ。それから、外国人への手紙を書くのにのめり込み、それに数日を費やした。気がつくと、脳ミソの中身が外国語のほうに染まっていて、なんとアセンブラのニーモニックに興味が湧かなくなっていた!あらどうしようと思っていたところにここ数日の蒸し暑さ。(大雨で大変な地方もあるが、私がいるここは耐え難い蒸し暑さになった。)信じられない不快指数のせいで仕事も家事もアセンブラも何もやる気が起きない!

それでもほんの少しやった。コードセグメントの98D0Hから始まるサブルーチンがあって、ここで画面に文字を書いている。最初に呼び出された時は"カラーディスプレー"という文字を書いていた。それから2回も呼び出されたから、その後どんな文字を画面に書いているんだろうと調べた。そうしたら"ハードディスクのドライブ番号"。しまったぁぁぁッ!私はいまフロッピーディスクでプレイしているつもりなんだ。どこかで条件分岐を間違えてしまったらしい。また、ゲーム本編への道のりが遠くなった。いつになったらゲーム本編にたどり着けるのか。

蒸し暑さと落胆のせいで、私はもう何もできない。馬鹿と知りつつお酒を飲んでしまった。

安酒を飲んで、さらに馬鹿な状態になりつつ、私はアニメのうしとらの最終回録画を見た。九印が崩れてゆく悲しい場面がなかった。私はあの悲しい場面が見たくないから最終回を今まで見なかったのだが。で、見終わってからまた酔いを抱えつつもPCに向かった。少し前にネット上にふしぎの海のナディアPC-9801版ゲームの意欲溢れる動画を見つけて、このソフトでは私の出る幕はないなと思っていた。他に何かないかと酩酊状態で探していた時、偶然に「見るはずのない」名前を見つけた。筋肉少女帯。だって私が今見ているのは、昔のPC-9801用の雑誌やマニュアルなんだ。筋肉少女帯の名前が出てくるなんて、誰が思うだろう。でも確かに出てきた。
kinniku.jpg

筋肉少女帯と闘神都市。またもや不思議な取り合わせだ。その日はそこまでで、私は記事の締めくくりをどうするか思いつけないまま寝てしまった。そして今は翌日。このままだと記事は下書きのまま永遠に出さないで終わりそうなので、もうこのまま出してしまうことにした。だから今回は、アセンブラ関係の話は少しだけで、昔の筋肉少女帯を見つけたというほうがメインになった。筋肉少女帯といえばうしとらの主題歌。少し遅れたが、アニメ版うしとらの終了記念だ。



数日後の追伸
私は残念ながらこのところ自分の頭がボケたことを実感している。今日になってこの記事の画像をよく見てみたら、私が以前から黒塗りにすると言っていた種類の画像がそのまま出ていた。急いで修正したら、黒塗りにしたつもりが白塗りだ。年をとって私はボケたかなぁ、それは辛いなぁと思いつつ、とにかく修正した。

コメント(0) 

孤独へ向って突っ走れ(19) [  PC-98x1(補完計画)]

このところ逆アセンブル結果の解析ばかりをやり過ぎた。言うまでもなく、生きて行くためには他の色々なこともしなければならないが、それをないがしろにした。残念だがこれから数日の間はアセンブラに関わるのをやめて、他の色々なことをしなければならない。アセンブラの続きをやるのはその後だ。

今日は、いま頭を悩ませていることを予告編として出しておきたい。最近の記事で、Shift JIS文字コードをJIS文字コードに変換したり、キャラクタジェネレータから文字フォントを読み出したりした。だから次は、その文字を画面に表示するルーチンだ。でも、フォントをそのまま表示するのではない。周りに縁取りを付けてから表示する。問題はこの縁取りだ。私がまず思いつくのは、文字を上下左右に1ドットずらしたデータを黒色で重ね合わせ、その真ん中に元の文字を白色で置いたら黒い縁取りになるというもの。このゲームでもきっと似たようなことをやっているのだろうが、なにしろアセンブラだから、私が上に書いたような人間の頭にとってわかりやすいコードではない。正直に白状すると何だかわからん。

個々の部分は単純な記述だが、それが何を意味するか、結果的にどういう動作をするかとなると、アセンブラに詳しくない私には荷が重い。

下の表は、同じような記述が条件分岐先にいくつもあるから比較できるように表にしたものだ。私が解析すべきものだ。何日か後に、家事や仕事や外国人への手紙を片付けてから、私はこれの動作を解析しなければならない。
tt07.gif
コメント(0) 

孤独へ向って突っ走れ(18) [  PC-98x1(補完計画)]

前回の記事で、「次回は心機一転、逆アセンブルしたコードの解析で個性豊かな記事を書いてやる」と書いてしまったが、あれは格好をつけすぎて自分の首を締めたきらいがある。自分で記事を書きにくくしてしまった。

今回は、そろりそろりと参ろう。

そろりそろりと「テニステニス2」を解析していたら、あるサブルーチンを発見した。小粒なルーチンだが、当時のPC-98プログラミングではしばしば必要になり、そのくせコーディングが意外と面倒なルーチンだ。Shift JIS文字コードをJIS文字コードに変換するルーチン。

なお、(今までの記事も含めて)ラベルの代わりにアドレスをそのまま書いているのをお許しいただきたい。先頭文字が数字でラベルとして通用しない場合もある。それはわかっているが、いつ解析が終わるともわからない膨大な量の逆アセンブル結果を前にして、いちいちラベル名を作って書いている余裕がなく、処理がわかればいいということにしている。


CMP AH,80
JZ A0D5
AHが80Hなら何もせず戻る

CMP AH,A0
JNB A0BE
そうでなくてAHがA0H以上ならA0BEHへ飛べ

SUB AH,70
JMP Short A0C1
AHがA0H未満ならば70Hを引き、A0C1へ飛んで、AHがA0H以上の場合と合流

A0BE: ;JNBのラベルはここ
SUB AH,B0
AHがA0H以上ならB0Hを引き、この下でAHがA0H未満の場合と合流

A0C1: ;JMP Shortのラベルはここ
OR AL,AL
JNS A0C7
ALのビット7が0ならA0C7Hへ飛べ

DEC AL
ALのビット7が1なら1を引き、この下でALのビット7が0の場合と合流

A0C7: ;JNSのラベルはここ
ADD AH,AH
AHを2倍する

CMP AL,9E
JB A0D1
ALが9EH未満ならばA0D1Hへ飛べ

SUB AL,5E
JMP Short A0D3
ALが9EH以上ならば5EHを引き、A0D3Hへ飛んでALが9EH未満の場合と合流せよ

A0D1: ;JBのラベルはここ
DEC AH
ALが9EH未満ならばAHから1を引き、この下でALが9EH以上の場合と合流

A0D3: ;JMP Shortのラベルはここ
SUB AL,1F
ALから1FHを引く

A0D5: ;JZのラベルはここ
RET



どんなプログラミング言語で記述しても結果としての動作は同じだが、いくつかの場所でアセンブラらしいコーディングをしているのが私は嬉しい。私が若い頃にMASMを買ったすぐ後で、アセンブラは廃れてしまった。だから私はアセンブラを学んだ直後に使わなくなったようなもので、アセンブラらしい記述を十分に学ぶ暇もなく終わってしまった。それだけに、今アセンブラらしい記述に出会えたことがなおさら嬉しいのだ。

OR AL,AL
JNS A0C7

読みやすさを追求する高級言語ではこんな記述はあり得ない。アセンブラならではの醍醐味だと私は感じる。

コメント(0) 

誰もが忘れた太古の昔、ポプコムという雑誌があった [  PC-98x1(補完計画)]

(今回はお酒を飲んでしまいまして、大トラ状態のアホ記事で失礼します)

今ちょうど世界はイギリスのEU離脱決定で大騒ぎだ。私もイギリスの反EU感情の根深さを甘く見ていた自分を反省し、今後の株価の動向を注意深く観察しなきゃと思っている。

でも、社会の動向と私個人の動向はまた別個で、今日は「飲み」に突入してしまった。残念ながら逆アセンブラ結果のまともな解析もできない大トラ状態になってしまった。

世界はいま大変だけれども、すまない、今回の私の記事はアホ記事を許してほしい。画像にオッパイがあったら今までどおりに黒塗りにする。


今は昔、ポプコムありけり。昔すぎて私は何年ごろだったか忘れた。もうほとんどの人は、日常の忙しかったり辛かったりする毎日の中で、ポプコムという名を忘れていたのではないだろうか。

私はこの雑誌で日高総帥という方を知った。その方が雑誌や著書に書かれた言葉から、私は「僕にもプログラミングはできる」という力強い「はじめの一歩」を学ばせていただいた。数学、いや算数にトラウマに等しい引け目のある私は、日高総帥の著書がなければ自分がコンピュータをいじれるとは初めから思いもしなかっただろう。それがなんと、その後の私はコンピュータプログラミングだけが唯一他人に「目を伏せないで」面と向かって言える「自分ができること」だ。

だから当然、私はこの雑誌の日高総帥のページを永久保存しておくべきだった。ところが、今残っているページの中にそれがない。その理由は、当時のプログラミングはプラットフォームがPC-9801で、その後、このマシンは発売中止となり、それ用のソフトも作られなくなったから。でも、それでも、私は保存するべきだった。今にして思えば、日高総帥のページを処分した時の私の心は、ある意味「中二病」にも似た過ちだった。つまり、増えすぎた本の整理をするさいに、「PC-98の時代は終わったんだからそれ関連の書物はもう永遠に使えないんだよ」という、「自分の中でわかったような気持ち」に支配されて、自分が正しいと思って捨てた。でも正しくなかった。その後、PC-98関係ではエミュレータというものが出た。PC-98は、過去にそれを所持して、それと共に人生の喜怒哀楽を歩んできた人がいる限り、本当は不滅だった。でも私は本を捨ててしまった。

昔私は、日高総帥のマシン語の本を4冊買った。大事に読んで、その後は大事に天袋に仕舞っておいたが、これも「中二病」的になった整理整頓の日、捨ててしまった。ひたすら悔やまれる。でも後悔は先に立たない・・・。


私の部屋の天袋には、雑誌から破り取ったページの束が保存されている。その中には週刊少年ジャンプのような雑誌もあれば、ポプコムのようなPC関係もある。何年も前だが、私はこのPC関係の雑誌束を久しぶりに開ける時、過去の思い出にあるいろんなものが出てきてほしいと期待していた。たとえば「時空の竜騎兵リバーサー」。・・・なかった。何もなかった。

では、あったものは何か。
オッパイ。
オッパイ。
オッパイ。
オッパイ。
オッパイ。
オッパイ。
オッパイ。
オッパイ。
・・・・・・・・。

・・・昔の私は若かったからなぁ。自分が今のようになるとは想像もしなかっただろうしなぁ。

力石のように「終わった何もかも」とまでは言わないが、オッパイ以外は終わった。あの大事だった日高総帥の記事も、時空の竜騎兵リバーサーも、みんな終わった。


認めたくないものだな・・・自分自身の・・・若さゆえの・・・過ちというものを!(号泣)


後に残ったのがオッパイで、しかもオッパイが出てきたら黒塗りにすると約束しているのだから、この記事には一体何が残るのだろう。

私は今までにもポプコムからどこかのページを出した記憶がある。それと重複するのもいけないし、今までの膨大な量のブログ記事を調べることもできない。それで今回は、私が「その他」と名付けて別個にしておいたページだけを見る。今までにブログに出したものは、私が昔プレイして思い入れのあるゲームのページのはず。「その他」はまだブログに出していないに違いない。そのかわりに、私自身もプレイしていないゲームがほとんどだ。


pop01.jpg
あ、逆立ちしてる。スキャナでスキャンした時に原稿を置く向きの関係でこうなったらしい。

私はこれを画像処理ソフトに読み込んで、回転させて上下を正しくしてから再保存した。そうしたらなんと、ファイルサイズが大きくなって、このブログの画像ファイルのサイズ制限を超えてしまった。JPEGの圧縮率の都合だと思うが、私は画像処理ソフトのJPEG画像圧縮率といった細かい所をいじってしまうと後で元に戻すのを忘れて次回使用時に後悔する「しょうもないおじさん」なので、圧縮率をいじりたくない。

それで私は圧縮率を変える代わりにサイズを縮小してから再保存しようとしたが、そこでふと気づいた。仮にだ、仮にこのブログを見に来た人がこの画像を「本当に心から手に入れたい」と仮定するならば、「逆さだけれどもスキャナーでスキャンした時点の大きな画像」と、「上下が正しいが縮小された画像」と、どちらをダウンロードしたいだろう。・・・いや、わかっているんだ。そんな人はいないっていうことは。お酒のせいなんだ。私はアルコールが入ると、細かい所が(わからないくせに)妙に気になってしまうから。・・・結局、画像は逆さのままにした。

でもとにかく、記事の中にオッパイが出てこなかったのはホッとした。このページで紹介されているロードモナークはまともなゲームで、しかも私でも名前を知っている有名なゲームだと思う。

でもオッパイはどうせこの後で出てきてこのブログの品位を落とすのはわかっている。しかもオッパイが出てきたら私はブログの方針に従って黒塗りしなければならない。


pop02.jpg
無意識のうちにオッパイのないページを探してしまう私。今回の記事はもう駄目かもしれん。でもここで終わると「なんじゃいワレ、画像2枚出して終わりかワレ!」とかいう結果になるのが怖い。もう少し頑張って続ける。


pop03.jpg
今回の私の辛さは、自分がプレイしていないゲームのページから何か出そうとしている所にある。自分がプレイしていればその関係でテキストも書けるし、やがて蘇る思い出が後押しをして記事を先へ進めてくれる。私は今回、道を間違えたらしい。


pop04.jpg
たのむ、とにかくオッパイの出ないページがあってくれ。しかも本当にアホなことに、この記事を書くちょっと前から飲み始めていた私はなんかもう寝そうだ。どうやってこの記事のオチをつけようか、いやそもそもその前に私はPCの前に倒れて寝るかもしれん。あ、CALみたいな画像が。あ、そうか、バーディーワールドの記事だから私は「その他」に入れたのか。


pop05.jpg
ここまで来て私は自分の間違いを完全に認めた。ただ画像を出すんじゃ駄目なんだ。仮にオッパイが出ていたとしても、オッパイ目当てで画像を取りに来てもらうのもまた空しいだろう。そう、私は人生ナメちゃあかんのだ。テキストは大事だ。なぜなら私がテキストを書くという事は、私の思いが噴き出すという事だから。それなくして、人を引きつける記事が書けるわけがない。

今回は私はもうお酒を飲んでしまったので、とにかく酔い潰れる前に出せるものを出す。ちょうど、美少女メガバトルというのが出てきた。オッパイが少ない!(どうして私はオッパイ検閲に振り回されなきゃならんのだと思えてきた。まあ、こんな記事を書かなきゃいいというのが結論かもしれない。)

当時の懐かしいゲームの画面が少し出てくる。プリメとか。今日のところはそれで勘弁してほしい。次回は心機一転、逆アセンブルしたコードの解析で個性豊かな記事を書いてやる・・・それも、アクセス件数は増えないとわかっているけど。

たぶんあなたもお察しのとおり、下の画像でオッパイが出たら黒塗りにする。その過程でどうしても画像編集ソフトに読み込まなければならず、だからオッパイを黒塗りにした画像はブログのファイルサイズ制限に収まるように縮小しなければならない。

pop06.jpg

pop07.jpg

pop08.jpg

pop09.jpg

最後の画像だけは見ないほうがいい。執筆者が「出来レース」って、ちゃんと書いてるとおりに、そういう結果だから。出来レースというものは、たとえ不利益が自分に及ばなくても、なんか微妙に腹が立つというか。
pop010.jpg

私は泥酔してよくここまで書けたと思う。読んでくださったあなたが私に「しっかり書け」とおっしゃるのは当然。この続きのポプコム記事もあるが、上に書いたとおりに記事というのは書いた人の情熱が大事なもの。「こんなもの持ってるぞ、出すぞ」というのは私が求める立場とは違うと思う。残りを出すかどうかはよく考えてから行動する。

コメント(0) 

孤独へ向って突っ走れ(17) [  PC-98x1(補完計画)]

懐かしのCGウィンドウ

今夜はPC-98のキャラクタジェネレータからCGウィンドウを介して文字フォントを読み取る件だ。でもその前に私の足の怪我の報告をさせてほしい。

私の歳になると怪我の治りが遅い。毎日、前日よりもほんのわずかに良いような気がする。薄紙をはぐようにとは、よく言ったものだ。いまの状態がどんなかは、頭で思い巡らすよりも自分の無意識の行動を観察したほうがよくわかる。足の第1指が腫れている私は、相撲の蹲踞のような姿勢は怖くてできなかった。それが今朝、何も気にせずに蹲踞の形に爪先を曲げている自分がいた。ああ、昨日より良くなったんだなと私は思った。人間は、体が悪いうちは意識するまでもなく動きたくない。横になっていたい。ところが体が良くなると今度は無意識に動き回ろうとする。今日は久しぶりにPCの前に座った。そうしたら、今は蹲踞の形に爪先を曲げるのが怖くてできない。ああ、また悪くしたな、と私は思っている。

それでは本題、懐かしのCGウィンドウの話だ。この言葉を聞いて懐かしいと感じる方は、昔PC-9801でプログラミングをした人に違いない。私はすっかり忘れていたが、例の逆アセンブル結果の解析を進めていたら、今日それが出てきた。思い出して懐かしかった。CGウィンドウなどの情報は、BIOSの情報とは違ってあまり細かく公式公開されなかったと記憶している。だから存在は知っていても使い方はよくわからないというのが第一段階だった。でも世の中にはすごい人がいて、公式の公開データが足りない部分は自分で実験を重ねて補い、さらに、有り難いことにその結果を本にして出してくださった。私のような、後からゆっくりと追いかける人間は、その本を買って学び理解した。これが第二段階だった。

今日は私が解析中のテニステニス2から、CGウィンドウを介して文字フォントを取得する部分を出したい。ニーモニックを追いかけて行けばCGウィンドウの利用のしかたもしっかりわかる。(もはやPC-9801用のプログラミングをする機会はないので、実用目的でなくただ懐かしむのが目的という所が少し寂しいが。)




PUSH AX
MOV AL,0B
OUT 68,AL
POP AX
漢字アクセスモードをドットアクセスに変更(常時フォント読み出し可能、そのかわりにテキスト画面漢字表示不可;テキスト画面を使用せず、フォントを読み出してグラフィック画面に描画するためのモードと思われる)




この先はAHの値による条件分岐

PUSH AX
CMP AH,2C
JZ 9BA8
CMP AH,2D
JZ 9BA8
CMP AH,2E
JZ 9BA8
CMP AH,2F
JZ 9BA8
CMP AH,76
JZ 9BA8
CMP AH,79
JZ 9BA8
CMP AH,7A
JZ 9BA8
CMP AH,7B
JZ 9BA8
CMP AH,7C
JZ 9BA8
AHが2CH, 2DH, 2EH, 2FH, 76H, 79H, 7AH, 7BH, 7CHなら9BA8Hへ行け
(JISコードの第1バイトが2CHから2FHまでと76H以上の文字のフォントは、CGウィンドウの奇数アドレス からしか読み出せない。つまり全角文字の右半分と左半分を別々に読み出さなければならない。)

JMP Short 9C00
AHがそれ以外の値なら9C00Hへ行け




2つの条件分岐先が順に出てくる。それぞれの行数が多い

AHが2CH, 2DH, 2EH, 2FH, 76H, 79H, 7AH, 7BH, 7CHの場合はここへ来る

NOP
9BA8: ;たくさんのJZのラベルはここ

MOV AL,00
OUT A5,AL
全角文字フォントの右半分(のライン0)を読み出す指定
(もうひとつの条件分岐先にはこの部分はない)

POP AX
PUSH AX
SUB AX,2000
全角文字フォントを読み出す時はJISコードの第1バイトから20Hを引くことになっている

OUT A1,AL
読み出す全角文字フォントの文字コードの第2バイトを指定

XCHG AH,AL
今度は第1バイトがAL

OUT A3,AL
読み出す全角文字フォントの文字コードの第1バイトを指定

MOV AX,A400
MOV ES,AX
ESはA400H(このセグメントのメモリはCGウィンドウと呼ばれ、I/Oポートによる読み出しを省いてこのメモリから直接文字フォントを読み取ることができる)

MOV SI,0001
LEA DI,[48A1]
ここがもうひとつの条件分岐先と違う
SIは0001H、DIは48A1H

MOV CX,0010
9BC6: ;LOOPのラベルはここ
MOV AL,ES:[SI]
MOV [DI],AL
ADD SI,+02
ADD DI,+02
LOOP 9BC6
CGウィンドウからデータセグメントのバッファへフォントの右半分を転送する
(もうひとつの条件分岐先では左右2バイトを一度に転送する。)

ここからJMP Short 9C26でもうひとつの条件分岐先と合流するまでの処理は、もうひとつの条件分岐先にはない

MOV AL,20
OUT A5,AL
全角文字フォントの左半分(のライン0)を読み出す指定

POP AX
SUB AX,2000
全角文字フォントを読み出す時はJISコードの第1バイトから20Hを引くことになっている

OUT A1,AL
読み出す全角文字フォントの文字コードの第2バイトを指定

XCHG AH,AL
今度は第1バイトがAL

OUT A3,AL
読み出す全角文字フォントの文字コードの第1バイトを指定

MOV AX,A400
MOV ES,AX
ESはA400H(上記の通り、このアドレスはCGウィンドウ)

MOV SI,0001
LEA DI,[48A0]
SIは0001H、DIは48A0H

MOV CX,0010
9BF0: ;LOOPのラベルはここ
MOV AL,ES:[SI]
MOV [DI],AL
ADD SI,+02
ADD DI,+02
LOOP 9BF0
CGウィンドウからデータセグメントのバッファへフォントの左半分を転送する

JMP Short 9C26
NOP
もうひとつの条件分岐先と合流せよ




9C00: ;JMP Shortのラベルはここ
AHがそれ以外の値ならここへ来る

POP AX
SUB AX,2000
全角文字フォントを読み出す時はJISコードの第1バイトから20Hを引くことになっている

OUT A1,AL
読み出すフォントの文字コードの第2バイトを指定

XCHG AH,AL
今度は第1バイトがAL

OUT A3,AL
読み出すフォントの文字コードの第1バイトを指定

MOV AX,A400
MOV ES,AX
ESはA400H(このセグメントのメモリはCGウィンドウと呼ばれ、I/Oポートによる読み出しを省いてこのメモリから直接文字フォントを読み取ることができる)

MOV SI,0000
LEA DI,[48A0]
ここがもうひとつの条件分岐先と違う
SIは0000H、DIは48A0H

MOV CX,0010
9C19: ;LOOPのラベルはここ
MOV AX,ES:[SI]
MOV [DI],AX
ADD SI,+02
ADD DI,+02
LOOP 9C19
CGウィンドウからデータセグメントのバッファへ2*16バイトのフォントデータを転送する




9C26: ;JMP Shortのラベルはここ
ここで、2つの条件分岐先が合流する




ところで、PC-98についての貴重な情報
http://www.webtech.co.jp/company/doc/undocumented_mem/io_disp.txt
の中で、
I/Oポート 00A1h の所の解説に、

漢字フォントの場合はJISコードの第2バイトから 20hを減じたものを設定する。

となっている。でも上に挙げたコードでは第1バイトだけ20Hを引いているようだ。それに、私が若い頃に作ったCライブラリでも

#define jiscodeset(jiscode) (outp(0xa1,(unsigned)(jiscode)&0xff),\
(void)outp(0xa3,((unsigned)(jiscode)>>8)-0x20))

となっている。

コメント(0) 

孤独へ向って突っ走れ(16) [  PC-98x1(補完計画)]

孫悟空は釈迦の掌上を飛ぶ
Cプログラマーはマシン語の中で四苦八苦する

まずは本題とは関係しないがご報告。私は昨日一日中革靴を履いて外にいたが、足は腫れ上がらなかった。病み上がりだから完全とはいえず、帰宅後に足をちょっとぶつけただけで痛くて飛び上がったが、その程度の悪化で済んだのはとても有り難い。一日歩き回ってよく腫れ上がらなかったと思っている。

さて、アセンブラプログラム解析の件だ。今回は自粛してコードを出さないが、ご想像の通りズラズラと長い解析結果が出来上がった。そしてやっと、自分がいまゲームプログラムのどこを解析しているかが判明した。

このゲームは、起動するとDOS起動時の文字が数行表示された後、まずディスプレイがカラーか白黒かをマウスで選択する。その時選択肢を囲っている枠がある。その枠がやっと描画できた。つまり、いつになったらゲーム本編までたどり着けるのかわからない。


この画像の全部が出来たのではない。まだ枠だけだ。文字は表示されていない。たぶん、これから文字を表示して、ループの中でマウスクリックを取得して、ユーザーによる画面モード選択に従って初期設定をして、それからようやくゲーム本編が始まるのだろう。

アセンブラでプログラミングができるというのは、ニーモニックの意味を知っているということではない。その先に要求される「ものすごいプログラミングパワー」が大事だ。やってもやってもなかなか先へ進まない中で、日夜努力と根性を出し、時間・気力・体力を消費し続けるパワフルプログラマーでなければアセンブラは使いこなせない。

コメント(0) 

孤独へ向って突っ走れ(15) [  PC-98x1(補完計画)]

私の足が悪くなってから1週間以上が過ぎた。ほんの少しずつ治り、昨日は爪先を曲げるのも力を加えなければ痛くないまでになった。ところが今朝起きて、爪先を曲げてみたらちょっと痛い。昨日も一昨日も無理をせず安静にしていたのに、どうしたことか。それでも今日は、いい加減にリハビリというか、外を歩いてみなきゃいけないと思う。

さてアセンブラの件だ。アセンブラはCとは比べ物にならないほどのプログラミングパワーを使う。Cで書いたのと同じゲームプログラムをアセンブラで書こうとすると大変な苦労をする。私は、前回の記事(14)で書いた迷宮からは脱した。ところが今は別の試練迷宮にいる。どこまで行ってもまだ先がある。個々の処理は単純なものだが、それがいつ終わるともわからず続く。努力と根性が試される試練だ。そして「もうやめだ。こんなのにこれ以上時間を使っていられない」と思いやめた時、その人からアセンブラは永遠に遠ざかってゆく。

今回の記事はここまでだ。この先にはニーモニックが延々と続くが、解析した私自身でさえ訳がわからなくなるような膨大な量で、しかもまだサブルーチンの途中までだ。私がどんな試練迷宮にいるかをわかってもらうために出すが、この記事を読んで下さるあなたが読むのはここまでで十分だ。



コードセグメントのアドレス98D0Hから990DHまでの命令
メインルーチンから飛んできたサブルーチン。
最終的に条件分岐で990DHへジャンプするまで。サブルーチンは続く。

MOV Word Ptr CS:[9A0D],0000
コードセグメントのオフセット9A0DHから2バイトに0000Hを入れる

MOV CS:[9A0F],AX
コードセグメントのオフセット9A0FHにAXの値を入れる
(AXには、このサブルーチンに飛ぶ前に000AHを入れてある)

MOV AX,0002
INT 33
マウスポインタを非表示にする

MOV AX,CS:[9A0F]
コードセグメントのオフセット9A0FHから2バイト(先ほど000AHを格納した)をAXに入れる

LEA SI,[4A2A]
SIにアドレスとして4A2AHを入れる

SHL AX,1
AX(000AH)を2倍する(AX=0014H)

ADD SI,AX
SIにAXを足す(SI=4A3EH)

MOV AX,[SI]
データセグメントのオフセット4A3EHから2バイトをAXに入れる
(調べたところ、5107H。ただしプログラム開始後にデータの書き換えがあったかどうかまでは調べていない。)

MOV SI,AX
その2バイト(5107H)をSIに入れる

MOV AX,[SI]
CMP AX,0000
JNZ 990D
データセグメントのオフセット5107Hから2バイト(調べたところ0001H)が0000Hでなければ990DHへ飛べ




コードセグメントのアドレス990DHから9938Hまでの命令
サブルーチン98D0Hの続き。
最終的に条件分岐で9948Hへジャンプするまで。サブルーチンは続く。

MOV AX,[SI+02]
MOV [47E0],AX
MOV BX,[SI+04]
MOV [47E2],BX
データセグメントのオフセット[5107H+02]から2バイト(001EH=AX;後で使う)を[47E0H]に入れ、
[5107H+04]から2バイト(00C8H=BX;後で使う)を[47E2H]に入れる

MOV CX,[SI+06]
ADD CX,+02
MOV [47E4],CX
データセグメントのオフセット[5107H+06]から2バイトを取り出し、2を足し(000BH=CX;後で使う)、[47E4H]に入れる

MOV DX,[SI+08]
SHL DX,1
ADD DX,+01
MOV [47E6],DX
データセグメントのオフセット[5107H+08]から2バイトを取り出し、2倍し、1を足し(0005H=DX;後で使う)、[47E6H]に入れる

SHL CX,1
ADD AX,CX
CMP AX,004B
JB 9948
CXを2倍し、AXと足し(=34H)、それが004BHより小さければ9948Hへ飛べ




コードセグメントのアドレス9948Hから9955Hまでの命令
サブルーチン98D0Hの続き。
最終的に条件分岐で9962Hへジャンプするまで。サブルーチンは続く。

MOV AX,DX
SHL AX,03
ADD AX,DX
ADD AX,BX
CMP AX,0188
JB 9962
DXの値を8倍し、元のDX(0005H)を足し、BX(00C8H)を足す。それ(=00F5H)が0188Hより小さければ9962Hへ飛べ

感想
最初のうちはゲームの初期設定ルーチンが多くて懐かしく、INTやOUTなどデータブックと照合すると意味がわかる部分が多くて楽しめたが、ここまで来るとデータセグメントの何だかわからないデータを取り出しては計算したり分岐したりする処理ばかりで、興味がもてない。




コードセグメントのアドレス9962Hから9965Hまでの命令
サブルーチン98D0Hの続き。
最終的にサブルーチンA0D6Hへ行くまで。

PUSH SI
CALL A0D6




コードセグメントのアドレスA0D6HからA101Hまでの命令
サブルーチン98D0Hから飛んできたサブルーチン。
最終的にサブルーチンA32FHへ行くまで。(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H)
ここに出てくる[47E0], [47E2], [47E4], [47E8]にはこのサブルーチンが飛んできたサブルーチン98D0Hでデータを格納した。
この先のコード解析により、
WORD PTR[47E0]は(グラフィックVRAMからバッファへコピーする指定矩形領域の左上のX座標/8)、
WORD PTR[47E2]は指定矩形領域の左上のY座標(ライン数-1)、
WORD PTR[47E4]は(指定矩形領域の横バイト数/2)(横ピクセル数/16)
と思われる。矩形の座標やサイズが内部処理でのBYTE単位やWORD単位になっている。
WORD PTR[47E6]を元にして矩形領域の縦ピクセル数を算出しているが、そこの計算が何をやっているのかわからない。

LEA SI,[47E0]
MOV CX,[SI+04]
MOV AX,[SI+06]
データセグメントのオフセット[47E4]から2バイトをCXに入れ、オフセット[47E6]から2バイトをAXに入れる
(この先のコード解析により、WORD PTR[47E4]は(グラフィックVRAMからバッファへコピーする指定矩形領域の横バイト数/2)と判明。"/2"なのは2バイトずつコピーするから。)

ADD AX,0001
MOV BX,0009
MUL BX
MOV DX,AX
AXに1を足し、9を掛け、その結果の下位2バイトをDXに入れる
(この動作はまだわからないが、後で出てくるサブルーチンA200Hが転送する矩形領域の縦は9ピクセル。)




MOV AX,[SI+02]
MOV BX,0050
PUSH DX
MUL BX
POP DX
データセグメントのオフセット[47E2]から2バイトを取り出し、50H(グラフィックVRAM横バイト数:80)を掛け、その結果の下位2バイトだけがAXに格納される
(おそらくWORD PTR[47E2]は指定矩形領域の左上のY座標。)

ADD AX,[SI]
MOV SI,AX
データセグメントのオフセット47E0Hから2バイトの値をAXに足し、それをSIに入れる
(おそらくWORD PTR[47E0]は(指定矩形領域の左上のX座標/8)。それにAXを足すことで、指定矩形領域の左上のアドレスを得る。)

XOR DI,DI
MOV AX,7F97
MOV DS,AX
DIの値を0にし、データセグメントを7F97Hに変更

CALL A32F
A32FHへ行ってこい
(サブルーチンA32FHは、DS:DI以降のバッファにグラフィックVRAMからオフセットSI, 横(16*CX)ピクセル, 縦DXピクセルの指定矩形領域を格納する。



コードセグメントのアドレスA32FHからA337Hまでの命令
サブルーチンA0D6Hから飛んできたサブルーチン。
最終的にサブルーチンA356Hへ行く(初回)まで。(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H>サブルーチンA32FH)

MOV AX,A800
MOV ES,AX
PUSH SI
ESにA800H(グラフィックVRAMプレーン0セグメント)を入れ、SIを退避

CALL A356
A356へ行ってこい

感想
処理がグラフィック関係になったので、また興味がもてるようになった。とはいえ、ラスタ演算処理はまだ出てこない。




コードセグメントのアドレスA356HからA375Hまでの命令
サブルーチンA32FHから飛んできたサブルーチン。
最終的にサブルーチンA32FHへ戻るまで。(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H>サブルーチンA32FH>サブルーチンA356H)
ES=A800H(サブルーチンA32FH;グラフィックVRAMセグメント)
SI(サブルーチンA0D6H;グラフィックVRAMの指定矩形領域左上アドレス)
DS=7F97H(サブルーチンA0D6H;グラフィックデータ格納バッファオフセット)
DI=0(サブルーチンA0D6H;グラフィックデータ格納バッファ先頭アドレス)
CX=1D7C:[47E4](サブルーチンA0D6H;指定矩形領域の横バイト数/2)
DX(サブルーチンA0D6H;指定矩形領域の縦ピクセル数)

PUSH DX
DXの値を退避

A357: ;JMP Shortのラベルはここ
PUSH CX
CXの値を退避

A358: ;LOOPのラベルはここ
MOV AX,ES:[SI]
MOV [DI],AX
ADD SI,+02
ADD DI,+02
LOOP A358
A800:[SI]から7F97:[DI]へ CX回 転送
(これはグラフィックVRAMの指定矩形領域の横1ラインを転送している。矩形の横は(16*CX)ピクセル。)

POP CX
CXの値を元に戻す

MOV AX,0050
SUB AX,CX
SUB AX,CX
ADD SI,AX
DEC DX
JZ A374
JMP Short A357
50H(グラフィックVRAM横バイト数:80)から2*CX(指定矩形領域の横バイト数)を引いた値をSIに足し(=指定矩形領域の次ラインのアドレス)、DXをデクリメント、上のLOOPをDX回(指定矩形領域の縦ピクセル数)繰り返したらA374Hへ行け

A374: ;JZのラベルはここ
POP DX
DXの値を元に戻す

RET
サブルーチンA32FHへ戻れ




コードセグメントのアドレスA338HからA355Hまでの命令
サブルーチンA356H(初回)から戻ってきた後のサブルーチンA32FH。
最終的にサブルーチンA0D6Hへ戻るまで。(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H>サブルーチンA32FH)

POP SI
SIの値を元に戻す

MOV AX,B000
MOV ES,AX
PUSH SI
CALL A356
POP SI
引き続きグラフィックVRAMプレーン1から指定矩形領域をバッファに取得

MOV AX,B800
MOV ES,AX
PUSH SI
CALL A356
POP SI
グラフィックVRAMプレーン2から指定矩形領域をバッファに取得

MOV AX,E000
MOV ES,AX
CALL A356
グラフィックVRAMプレーン3から指定矩形領域をバッファに取得

RET
サブルーチンA0D6Hへ戻れ




コードセグメントのアドレスA102HからA12CHまでの命令
サブルーチンA0D6Hの続き。サブルーチンA32FHから帰って来たところ。
最終的にサブルーチンA200H(初回)へ行くまで。
(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H)

MOV AX,1D7C
MOV DS,AX
データセグメントを元に戻す

LEA SI,[47E0]
MOV AX,[SI+02]
MOV BX,0050
MUL BX
データセグメントのオフセット47E2Hから2バイト(指定矩形領域の左上のY座標)を取り出し、50H(グラフィックVRAM横バイト数:80)を掛ける

ADD AX,[SI]
その下位2バイトに[47E0](指定矩形領域の左上のX座標/8)を足す

MOV DI,AX
DIは指定矩形領域の左上のアドレス

MOV CX,[SI+04]
MOV DX,[SI+06]
DEC CX
CXに[47E4](指定矩形領域の横バイト数/2)を入れ、DXに[47E6]を入れる
CXからは1を引く

PUSH SI
PUSH DI
PUSH CX
PUSH DX
SI, DI, CX, DXの値を退避

MOV AX,993A
MOV DS,AX
MOV SI,0000
データセグメントは993AH、SIは0000H

CALL A200
サブルーチンA200Hへ行ってこい




コードセグメントのアドレスA200HからA208Hまでの命令
サブルーチンA0D6Hから飛んできた。
最終的にサブルーチンA2B9Hへ行くまで。
このサブルーチンA200Hは中身がほとんどなく、すぐにサブルーチンA2B9HをCALLしてRETする。サブルーチンA2B9Hのwrapperのようなもの。
(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H>サブルーチンA200H)

MOV CX,0001
MOV DX,0009
CALL A2B9
サブルーチンA2B9Hへ行ってこい

サブルーチンA2B9H自体はデータバッファDS:SIからグラフィックVRAM(全4プレーン)の指定場所(DI、セグメントはサブルーチン内で設定)へ、横(CX*2)バイト(=(CX*16)ピクセル)、縦DXピクセルの矩形領域を転送するものだが、上の指定により、ここ(サブルーチンA200H)では横16ピクセル縦9ピクセルの転送となる。サブルーチンA2B9Hの転送は単純な転送でラスタ演算処理はない。




コードセグメントのアドレスA2B9HからA2C1Hまでの命令
サブルーチンA2B9H。サブルーチンA200Hから飛んできた。
最終的にサブルーチンA2E0H(初回)へ行くまで。
(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H>サブルーチンA200H>サブルーチンA2B9H)
DS=993AH
SI=0000H(初回)
ES=このサブルーチン内で設定する
DI=指定矩形領域の左上のアドレス(初回)
CX=1
DX=9

MOV AX,A800
MOV ES,AX
PUSH DI
CALL A2E0
ESにA800H(グラフィックVRAMのプレーン0セグメント)を入れて、サブルーチンA2E0Hへ行ってこい




コードセグメントのアドレスA2E0HからA2F4Hまでの命令
サブルーチンA2B9Hから飛んできた。
最終的にサブルーチンA2B9Hへ戻るまで。
(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H>サブルーチンA200H>サブルーチンA2B9H>サブルーチンA2E0H)
DS=993AH
SI=0000H(初回)
ES=呼び出し側のサブルーチン内で設定済み(グラフィックVRAMの指定プレーンのセグメント)
DI=指定矩形領域の左上のアドレス(初回)
CX=1
DX=9

PUSH DX
A2E1: ;JMP Shortのラベルはここ
ループ開始

PUSH CX
REP MOVSW
POP CX
データバッファ(DS:SI)からグラフィックVRAMの指定プレーンの指定場所(ES:DI)へ2バイトを1回(CX=1)転送

MOV AX,0050
SUB AX,CX
SUB AX,CX
ADD DI,AX
50H(グラフィックVRAM横バイト数)から(CX(先ほどの転送回数)*2)を引いたものをDIに足すと、DIは指定矩形領域内の次のラインの先頭を指す

DEC DX
JZ A2F3
9回実行したらループから抜ける

JMP Short A2E1
A2F3: ;JZのラベルはここ
POP DX

RET
サブルーチンA2B9Hへ戻れ




コードセグメントのアドレスA2C2HからA2DFHまでの命令
サブルーチンA2B9Hの続き。サブルーチンA2E0H(初回)から帰って来たところ。
最終的にサブルーチンA200H(初回)へ戻るまで。
(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H>サブルーチンA200H>サブルーチンA2B9H)

POP DI
MOV AX,B000
MOV ES,AX
PUSH DI
CALL A2E0
POP DI
ESにB000H(グラフィックVRAMのプレーン1セグメント)を入れて、サブルーチンA2E0Hへ行ってこい

MOV AX,B800
MOV ES,AX
PUSH DI
CALL A2E0
POP DI
ESにB800H(グラフィックVRAMのプレーン2セグメント)を入れて、サブルーチンA2E0Hへ行ってこい

MOV AX,E000
MOV ES,AX
CALL A2E0
ESにE000H(グラフィックVRAMのプレーン3セグメント)を入れて、サブルーチンA2E0Hへ行ってこい

RET
サブルーチンA200Hへ戻れ




コードセグメントのアドレスA209HからA209Hまで、たった1バイトの命令
サブルーチンA200Hの続き。サブルーチンA2B9Hから帰って来たところ。
最終的にサブルーチンA0D6Hへ戻るまで。
(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H>サブルーチンA200H)

RET
サブルーチンA0D6Hへ戻れ




コードセグメントのアドレスA12DHから141Hまでの命令
サブルーチンA0D6Hの続き。サブルーチンA200H(初回)から帰って来たところ。
複雑なので直下のループ部分だけでひと区切りとする。サブルーチンは続く。
(メインルーチン>サブルーチン98D0H>サブルーチンA0D6H)

サブルーチンA200H(データバッファからグラフィックVRAMへ横16ピクセル縦9ピクセルの矩形領域を転送)はすでに一度実行している。下の処理でさらにループ内で(DX-1)回(DXは退避してあった値を戻して1D7C:[47E6]になっている)繰り返すので、そこまででサブルーチンA200HをDX回繰り返すことになる。毎回のサブルーチンA200Hの直前にSIは初回だけが0000Hでその後(ループ内)は00D8H、DIは初回だけが指定矩形領域の左上のアドレスでその後(ループ内)は02D0H(=720;グラフィックVRAMの9ライン分)を足す。CXとDXは参照しない。

A12D: ;JMP Shortのラベルはここ
(DX-1)回のループ開始

POP DX
POP CX
POP DI
DI, CX, DXの値を元に戻す(SIはそのまま)

DEC DX
JZ A142
DXをデクリメントし、0になったらループから抜ける

ADD DI,02D0
MOV SI,00D8
DIは前回よりも02D0H(=720;グラフィックVRAMの9ライン分)多く、SIは毎回00D8H

PUSH DI
PUSH CX
PUSH DX
DI, CX, DXの値を退避

CALL A200
サブルーチンA200Hへ行ってこい

JMP Short A12D
A12DHへ戻ってループせよ

A142: ;JZのラベルはここ
(DX-1)回のループ終了

ここまでで、データバッファからグラフィックVRAMの指定場所へ横16ピクセル縦1D7C:[47E6]ピクセルの矩形領域を転送したことになる。

感想
たいして複雑な処理をしていないと思うのだが、コードの行数が膨大で、サブルーチンからサブルーチンが呼び出されるので現在どこにいるかがわかりにくく、大変な作業になってきた。

コメント(0) 

孤独へ向って突っ走れ(14) [  PC-98x1(補完計画)]

今朝外出を試みた私は爪先を曲げる時の痛みのせいで家へUターンすることとなり、今日も寝たきりの生活だ。

横になる前に大急ぎでPCを起動してひとつだけ確認したことがある。1D7C:13B8に何が入っているか。それについては後で書く。話を最初から始めよう。



大変なことがわかった。私はやっぱりプログラムの迷宮で迷っていた。

まず、前の記事で書いたDOSコマンドによるファイルの読み込みの件。私の解析では、WORD PTR[1EE8]にはファイルを読み込むバッファのセグメント、WORD PTR[1EE8+2]には同じくバッファのオフセット、WORD PTR[1EE8+4]にはファイル長、[1EE8+8]からはファイル名が格納されている。で、ここはファイル名を調べるためにメモリをダンプしたのが残っていた。それを見たら、ファイル名以外は全部0だった。ありえへん!アドレス0000:0000にファイルから0バイト読み込む処理なんて。ファイル読み込みに失敗する方の分岐しかありえない。私は今まで、決して実行されない部分を一生懸命解析していたのだ。

それでは、ファイル読み込みに失敗する方の道を解析するか?いや待て。このサブルーチンはファイル読み込み用のルーチンだろうから、その先にはエラー表示が待っているだけの可能性が高い。

ここは、プログラム先頭から改めて条件分岐を探しては調べるほうがいい。

最初の条件分岐は、最初にサブルーチンへ飛んだ6FECHの少し先。私自身が次のように書いた。

CMP WORD PTR [13B8],+01
JNZ 7026
データセグメントのアドレス13B8Hからの2バイトが+01でなければ
アドレス7026Hへ飛べ
(1D7C:13B8にはまだ何か入れる命令はない。あらかじめ何かの値が入っている。)

私が横になる前にどうして1D7C:13B8を調べたかわかっただろう。で、そこに入っている値は0だった!あかん。私が一生懸命解析したコードのほとんど全部がゲームで使われないルーチンだ。

あなたはきっと、なんで私が1D7C:13B8の値を最初に調べなかったのかと思うだろう。私はずっと、dias.exeのダンプでアドレスを指定する時セグメントも指定できることを知らなかったのだ。それで愚かにも、コード解析と並行してEXEファイルのどこにデータセグメントに格納される部分があるかをネット検索し続けていた。

そんなこんなで色々あって、楽しかったけど無駄や落胆も多くて疲れた。ここから先は今までほどの頑張りは続かないだろう。またやる気が起きた時にやる。

コメント(0) 

孤独へ向って突っ走れ(13) [  PC-98x1(補完計画)]

朝起きて、足がまだ治っていない。腫れと赤みは引いたが、違和感と、どうかした時のちょっとした痛みはある。家の中はもう自由に歩ける。ただ私は、家の中を歩くのと外を歩くのが全然違うということを知っている。過去に足を怪我した時、家の中を自由に歩くまでに回復したので安心して靴を履いて一歩外を歩きだしたらうまく歩けずに愕然としたことは複数回ある。今日はこれから外を歩いてみなければならない。今日の作業は、一度行ったら夜の9時頃まで帰れない。なにしろ場所が遠いので。その間に私の左足はどうなってしまうだろう。せっかく良くなりかけた足をまた悪くするのではないか。すべては外を歩いてみてからわかる。

さて、例の逆アセンブルの件は、あれから壁に突き当たった。次のサブルーチンがやたらと大きく、私はちょっと不安になって先の方を見てみた。すると、なんだかわからない計算とマウスの設定ばかりが続く。どうもおかしい。いまだに正規ディスクかどうかのチェックルーチンがない。画面などの初期設定はもう済んでいるのに。それに、いまだに画面表示がひとつもない。実際のゲームを始めると、DOS立ち上げの直後にもうゲーム動作選択画面が出るのに。ひょっとすると、私はプログラムの迷路に迷い込んだのではないか。言うまでもなく今までの解析部分に条件分岐がいくつもあった。私は条件分岐が出ると、ひとまずジャンプしない方の道をたどった。でもゲーム本編はジャンプする方の道だったのかもしれない。一番気になるのはプログラムの最初の方にあったDOSコマンドによるファイルの読み込みだ。調べてみると、読み込むファイルのパスはb:\tennisだった。これがおかしい。確かにドライブBに入れるべきディスクBにはtennisというファイルがある。でもそれは中身を普通に読めるファイルではなく、ディレクトリなのだ。ファイルとして読み、中身をメモリ上に格納することはできないはず。ということは、もしやあのコードはデバッグ用のテストルーチンか何かで、ゲーム本編への道はほかにあったのでは?

私は次に何をすべきか。よく考えて行動しなければならない。

コメント(0) 

孤独へ向って突っ走れ(12) [  PC-98x1(補完計画)]

のんびりと日がな一日横になっているという王様生活も、病気ゆえという事情であり、いつまでも続くことは良いことでない。明日は元気に歩き回り仕事ができるようでありたい。そのために今あえて足に負担をかけないように横になっているのだから。

今日の分のコード解析はここまでとしよう。もし明日元気になっていたら私は働くから、この続きはUPできない。



コードセグメントのアドレス0026Hから0038Hまでの命令
サブルーチン4306Hから戻ってきたメインルーチン。
最終的にサブルーチン6629Hへ飛ぶ(初回)まで。

MOV AX,0000
INT 33
CMP AX,FFFF
JZ 0036
マウスの初期化をし、マウスを使用できる環境ならば0036Hへ飛べ
(マウスの初期化はすでにサブルーチン4306Hで済んでいるので、ここではマウスを使用できる環境かどうか調べているだけ。)

MOV AX,0000
JMP 43B1

0036: ;JZのラベルはここ
CALL 6629
6629Hへ行ってこい




コードセグメントのアドレス6629Hから663DHまでの命令
メインルーチンから飛んできたサブルーチン。
最終的にメインルーチンへ戻るまで。

MOV AH,09
LEA DX,[3B50]
INT 21
LEA DX,[3B55]
INT 21
LEA DX,[3B5B]
INT 21
DOSの文字列の表示コマンドを使って画面にエスケープシーケンスを送る
(ESC) [2J テキスト画面全消去
(ESC) [>5h カーソル非表示
(ESC) [>1h システム行非表示

RET
メインルーチンへ戻る




コードセグメントのアドレス0039Hから00AAHまでの命令
サブルーチン6629Hから戻ってきたメインルーチン。
最終的にサブルーチン663EHへ飛ぶまで。

MOV AH 0C
INT 18
テキスト画面用GDCに表示開始要求を行う

CALL 6629
DOSコマンドによるテキスト画面消去。サブルーチン6629Hへはほんの少し前にも飛んでいる。詳細はそちらを参照。
(なぜサブルーチン6629Hを2回実行する必要があるのか?)

MOV AH,41
INT 18
グラフィック画面の表示停止

MOV AX,0002
INT 33
マウスポインタを非表示

MOV AL,4B
OUT A2,AL
MOV AL,01
OUT A0,AL
グラフィックGDCにCSRFORMコマンドを送り、1行2ラインに変更する

MOV AX,0012
MOV BX,000F
INT 33
カーソル表示画面(プレーン)の設定か?

MOV AH,41
INT 18
グラフィック画面の表示停止

MOV WORD PTR[0002],0000
データセグメントのオフセット0002Hから2バイトに0000Hを入れる

MOV AX,[16E4]
ADD AX,[16E0]
ADD AX,[16E6]
ADD AX,[16DC]
ADD AX,[16DE]
XOR DX,DX
MOV BX,0005
DIV BX
MOV [1138],AL
データセグメントのオフセット16E4Hから2バイトの値に、オフセット16E0Hから2バイト、オフセット16E6Hから2バイト、オフセット16DCHから2バイト、オフセット16DEHから2バイトの値をすべて足し、それを5で割る。その商の下位1バイトをデータセグメントのオフセット1138Hに入れる

LEA SI,[0004]
ADD SI,1130
SIにアドレスとして0004Hを入れ、それに1130Hを足す
(もしもLEAでなくMOVならば、[0004]はデータセグメントのオフセット0004Hから2バイトの中身になる。でもここはLEAだから、[0004]は0004Hそのものを意味するはずだ。それならなぜ後から1130Hを足すというまどろっこしいことをする?)

LEA DI,[12C6]
ADD DI,+22
DIにアドレスとして12C6Hを入れ、それに22Hを足す

MOV CX,0006
0092: ;LOOPのラベルはここ
MOV AL,[SI]
XOR AH,AH
MOV [DI],AX
ADD SI,+01
ADD DI,+02
LOOP 0092
データセグメントのアドレス[SI]から1バイトを取り出してアドレス[DI]に入れ、次のアドレスには00Hを入れる。それをSIとDIをインクリメントしつつ6回繰り返す。最終的に元の[DI]から12バイトが変更されることになる。

MOV AH,41
INT 18
グラフィック画面の表示停止
(さっきからメインルーチンの中で同じ命令が3回目だ。さすがに無駄としか思えない。納期に急がされて以前のコードをチェックする暇がなかったのか?)

XOR AX,AX
OUT A6,AL
グラフィックVRAM描画画面0を選択する

CALL 663E
663EHへ行ってこい




コードセグメントのアドレス663EHから6664Hまでの命令
メインルーチンから飛んできたサブルーチン。
最終的にメインルーチンへ戻るまで。

MOV AL,80
OUT 7C,AL
MOV AL,00
OUT 7E,AL
MOV AL,00
OUT 7E,AL
MOV AL,00
OUT 7E,AL
MOV AL,00
OUT 7E,AL
GRCGを有効にする、CPUのVRAMリード時TCRモード、ライト時TDWモード、全プレーン有効
4つすべてのタイルレジスタに00Hを設定

CLD
MOV AX,A800
MOV ES,AX
XOR DI,DI
MOV CX,3E80
665D: ;LOOPのラベルはここ
STOSW
LOOP 665D
GRCGを使って4プレーン同時にタイルレジスタの値を書き込む
書き込む領域は画面全体(横640ピクセル×縦400ピクセル÷一回の書き込み分16=3E80H)

MOV AL,00
OUT 7C,AL
GRCGを無効にする

RET
メインルーチンへ戻る




コードセグメントのアドレス00ABHから00CAHまでの命令
サブルーチン663EHから戻ってきたメインルーチン。
最終的にサブルーチン98D0Hへ飛ぶまで。

XOR AX,AX
OUT A4,AL
グラフィックVRAM表示画面0を選択する

MOV AL,4B
OUT A2,AL
MOV AL,00
OUT A0,AL
グラフィックGDCにCSRFORMコマンドを送り、1行1ラインに設定する

LEA SI,[374B]
MOV CX,0010
SIにアドレスとして374BHを入れ、CXに0010Hを入れる

CALL 441C
441CHへ行ってこい

サブルーチン441CHは前に出てきた。DS:SIから格納されているパレットデータを使ってCX個のパレットを設定するルーチン。SIとCXの値も前回と同じ。

MOV AH,40
INT 18
グラフィック画面を表示する

MOV AX,000A
AXに000AHを入れ

CALL 98D0
98D0Hへ行ってこい

コメント(0) 

孤独へ向って突っ走れ(11) [  PC-98x1(補完計画)]

私の左足はまだ治っていない。昨日まではトイレに行ってスリッパが履けずに裸足だったが、今日はスリッパが履ける。だから昨日よりはいい。

そんな状態なので今日も引き続き横になって過ごさなければならず、暇に任せてブログ記事を複数書いてしまうと思う。もちろん記事はPCではなくスマホで時間をかけて作文している。


私は若い頃からおもにC系の言語を使ってきた。だからプログラミングの思考がどうにもC的になりがちだ。他言語で別のやり方が出てくると私は苦労する。たとえばBASIC系のVariant型は理解するまでに苦労した。メモリ上のサイズが異なる型のどれにもなれる、それどころか文字列も格納できるだって?そんないい加減なものがあってたまるか!というように。今はMASMの文法を学び直そうとして、たまに混乱している。

私にはLEAがわからない。
MASM文法において
LEA dest src
は、srcのアドレスをdestにロードする。destはレジスタまたはメモリが可能であり、srcは即値、レジスタ、メモリが可能である。
私にわかるのは、ひとつだけだ。下記のようにデータが定義されていたとして
msg db 'mojiretsu', 0
文字列を表すラベルを使って
lea ax,[msg]
とすれば、axにはデータmsgの先頭アドレスが入りそうな気がする。そこまでがわかる。
ところが、srcは即値やレジスタでもいいという。即値やレジスタにアドレスはないと私は確信する。
ひょっとすると、srcは即値やレジスタでもいいというのは
lea ax,[bx + 8] 
のような意味かもしれないと私は想像する。これは可能な文だという。でもやはり私はわからない。「bx + 8のアドレス」というものはあり得ないと私には思える。たぶんこれは、フラグレジスタの変更を別とすれば、
mov ax,bx
add ax,8
と同じ動作で、アドレスは関係ないのでは。

LEA SI,[1F6A]
というのが出てきた。1F6Aは即値であり、それのアドレスというのはあり得ないから、1F6AHそのものをSIに入れるとしか私には思えない。でもそうすると、なんでMOVを使わないのかという疑問が私には生じる。

コメント(0) 

孤独へ向って突っ走れ(10) [  PC-98x1(補完計画)]

前回の記事の翌日、私の左足は腫れ上がって歩けなくなった。それからPCの前に座れなかったのは言うまでもない。それでも久しぶりにアセンブラのニーモニックに接した私の衝動は抑えようがなく、私は逆アセンブルの結果をPCからスマホに移して、歩けないながらも安楽椅子の上だろうと布団の上だろうと狂ったようにスマホ画面上で指だけ動かした。

今もまだ私は外出できずに家で寝ていなければならない。せめてもの楽しみに、今まで解析した分をスマホからUPしたい。そういう状況だから今回は無駄口なしで、つまり最近の記事にあったウルトラマンがどうのというつまらん冗談はなしで、ただひたすら解析結果だけを出す。

でもこれはこれで、昔PC-98用ゲームをプログラミングした人間には懐かしい初期設定ルーチンのオンパレードだ。

では前回の記事に出したコードの続きから。

MOV AH,41
INT 18
グラフィック画面を非表示にする

MOV AL,01
OUT 6A,AL
標準グラフィックスモード(EGCでない)16色にする

XOR AX,AX
OUT A4,AL
グラフィックVRAM表示画面0を選択する

XOR AX,AX
OUT A6,AL
グラフィックVRAM描画画面0を選択する

MOV AX,2916
MOV DS,AX
MOV BYTE PTR [024A],00
MOV AL,01
MOV CL,20
SHR AL,CL
JZ 4347
MOV BYTE PTR [024A],01
MOV AX,1D7C
MOV DS,AX
データセグメントを2916Hに
(今までは1D7CHだった。)
データセグメントのアドレス024AHに00Hを入れる
01Hを右へ20H回ビットシフトし
ゼロフラグが立っていれば4347Hへ行け
(SHRには何の意味があるのか。ゼロフラグの状態はいつ決定するのか。)
データセグメントのアドレス024AHに01Hを入れる
データセグメントを1D7CHに
(一時的に2916Hにしていたのを元に戻した。)

MOV AH,0A
MOV AL,00
INT 18
CRTモードを縦25行、横80文字、VLモード、コードアクセスモードに設定する
(ようするに当時の一般的なモードと思われる。)

XOR AX,AX
INT 33
マウスを初期化する

CALL 67B3
67B3Hへ行ってこい




コードセグメントのアドレス67B3Hから6819Hまでの命令
サブルーチン4306Hから飛んできたサブルーチン。最終的にサブルーチン4306Hに戻るまで。

MOV AX,0000
MOV ES,AX
MOV AX,ES:[0014]
MOV CS:[67A7],AX
MOV AX,ES:[0016]
MOV CS:[67A9],AX
セグメント0000Hのオフセット0014Hから4バイト(COPYキーの割り込みベクタアドレス)を取り出し、それをコードセグメントのアドレス67A7H以降に入れる

MOV AX,6843
MOV BX,2C70
CLI
MOV ES:[0014],AX
MOV ES:[0016],BX
STI
セグメント0000Hのオフセット0014Hから4バイト(COPYキーの割り込みベクタアドレス)を2C70H:6843Hに変更する(変更中の割り込み禁止)

MOV AX,ES:[0018]
MOV CS:[67AB]AX
MOV AX,ES:[001A]
MOV CS:[67AD],AX
セグメント0000Hのオフセット0018Hから4バイト(STOPキーの割り込みベクタアドレス)を取り出し、それをコードセグメントのアドレス67ABH以降に入れる

MOV AX,6843
MOV BX,2C70
CLI
MOV ES:[0018],AX
MOV ES:[001A],BX
STI
セグメント0000Hのオフセット0018Hから4バイト(STOPキーの割り込みベクタアドレス)を2C70H:6843Hに変更する(変更中の割り込み禁止)
(COPYキーとSTOPキーの割り込みアドレスを同じにした。COPYキーとSTOPキーの無効化かもしれない。)

MOV AX,3523
INT 21
MOV AX,ES
MOV CS:[67AF],AX
MOV CS:[67B1],BX
割り込み番号23H(Ctrl-C割り込み)の割り込みベクタアドレスを求め、それをコードセグメントのアドレス67AFH以降に入れる

MOV DX,6846
MOV AX,2C70
PUSH DS
MOV DS,AX
MOV AX,2523
INT 21
POP DS
割り込み番号23H(Ctrl-C割り込み)の割り込みベクタアドレスを2C70H:6846Hに変更する

RET
サブルーチン4306Hへ戻る




コードセグメントのアドレス4359Hから4362Hまでの命令
サブルーチン67B3Hから戻ってきた。サブルーチン4306Hの続き。
すぐにまたサブルーチンへ441CH飛ぶ。

LEA SI,[374B]
MOV CX,0010
ソースインデックスレジスタにアドレスとして374BHを入れる(ここにはパレット情報が格納されている)
CXには0010Hを入れておく(これはパレットの数16を意味する)

CALL 441C
441CHへ行ってこい




コードセグメントのアドレス441CHから4441Hまでの命令
サブルーチン4306Hから飛んできたサブルーチン。最終的にサブルーチン4306Hに戻るまで。
このサブルーチンに来る前に、SIには374BHが、CXには0010Hが入っている。

ADD SI,+04
SUB CX,+01
SIには4を足し、CXからは1を引く
(なぜ最初からその値を入れないかだが、おそらく下のコードでパレット番号0が(0,0,0)に固定されているので、そのぶんを差し引いているのだろう)

MOV AL,00
OUT A8,AL
OUT AA,AL
OUT AC,AL
OUT AE,AL
パレット番号0を(0,0,0)に設定

CMP WORD PTR[0002],+00
JNZ 4442
データセグメントのオフセット0002Hからの2バイトが0でなければ4442Hへ飛べ

4433: ;LOOPのラベルはここ
LODSB
OUT A8,AL
LODSB
OUT AA,AL
LODSB
OUT AC,AL
LODSB
OUT AE,AL
LOOP 4433
データセグメントのアドレスSIから1バイトずつ読みながら15個のパレットを設定する

RET
サブルーチン4306Hに戻る




コードセグメントのアドレス4363Hから4387Hまでの命令
サブルーチン441CHから戻ってきた。サブルーチン4306Hの続き。
最終的にサブルーチンF065Hに飛ぶまで。

MOV AX,0000
MOV ES,AX
OR BYTE PTR ES:[0500],20
セグメント0000Hオフセット0500Hの1バイトについて、ビット5を1にする(キーバッファオーバーフロー時にビープを鳴らさない設定)

TEST BYTE PTR ES:[0501],80
MOV AX,2FFD
JZ 437C
セグメント0000Hオフセット0501Hの1バイトについて、ビット7が1(システムクロック周波数が8MHz)でなければ、AXに2FFDHを入れておいて437Cへ飛べ

MOV AX,26FC
MOV CS:[F063],AH
MOV CS:[F064],AL
コードセグメントのアドレスF063Hに26Hを入れ、アドレスF064HにFCHを入れる
(この値は次のサブルーチンでタイマのカウンタ#0(インターバルタイマ用)に書き込む。)

CALL F065
F065Hへ行ってこい




コードセグメントのアドレスF065HからF0A7Hまでの命令
サブルーチン4306Hから飛んできたサブルーチン。最終的にサブルーチン4306Hに戻るまで。

MOV AX,0000
MOV ES,AX
MOV SI,0020
MOV AX,ES:[SI]
MOV BX,ES:[SI+02]
MOV CS:[F05F],AX
MOV CS:[F061],BX
セグメント0000Hのオフセット0020Hから4バイト(タイマ用の割り込みベクタアドレス)を取り出し、それをコードセグメントのアドレスF05FH以降に入れる

CLI
MOV AX,F0C9
MOV BX,2C70
MOV ES:[SI],AX
MOV ES:[SI+02],BX
STI
セグメント0000Hのオフセット0020Hから4バイト(タイマ用の割り込みベクタアドレス)を2C70H:F0C9Hに変更する(変更中の割り込み禁止)

MOV AL,36
OUT 77,AL
タイマのモード設定。カウンタ#0、LSB,MSBの順にリード/ライト 、モード3(方形波ジェネレータ)、バイナリカウント

MOV AL,CS:[F064]
OUT 71,AL
MOV AL,CS:[F063]
OUT 71,AL
タイマのカウンタ#0(インターバルタイマ用)に書き込む

JMP SHORT F09F
NOP
F09F: ;JMP SHORTはここへ飛ぶ

CLI
IN AL,02
AND AL,FE
OUT 02,AL
STI
割り込みマスクレジスタを書き換える

RET
サブルーチン4306Hに戻る




コードセグメントのアドレス4387Hから43B0Hまでの命令
サブルーチンF065Hから戻ってきた。サブルーチン4306Hの続き。
最終的にメインルーチンに戻るまで。

PUSH CS
POP ES
MOV BX,4300
XOR AX,AX
INT 1C
日付・時刻を読み出し、コードセグメントのオフセット4300H以降に格納する

MOV AX,CS:[4304]
MOV BX,AX
ROR AX,1
OR AX,6997
OR BH,AB
MUL BX
ADD AX,142B
ADD CS:[6C8B],AX
ROR AX,1
ADD CS:[6C1A],AX
時刻のうち分と秒の部分2バイトを取り出し(上位4ビット下位4ビットがそれぞれ10の位1の位を表す)
右へ1ビットシフトして出たビットを左端に入れ
0110 1001 1001 0111bとORをとり
それとは別個に秒の部分だけ
1010 1011bとORをとったものを作り
両方を掛け合わせ
その下位2バイト分だけを取り出して
142BHを足し
それをコードセグメントのオフセット6C8BHからの2バイトに足す
コードセグメントのオフセット6C8BHからの2バイトには右へ1ビットシフトして出たビットを左端に入れたものを足す
(結局それは何をやりたいんだ?時刻からランダムな数を作るというのがあったが、ひょっとして?)

RET
メインルーチンに戻れ




ここまで来てようやく、逆アセンブル結果の解析をしたいという衝動が収まった。

コメント(1) 

孤独へ向って突っ走れ(9) [  PC-98x1(補完計画)]

朝起きたら、左足の親指付け根の関節が痛くなっていた。きのう一日中PCの前に座っていて血行不良になったのが、以前に腫らせて悪くなっている足の関節に悪影響を及ぼしたらしい。

でも昨日の続きをやりたくてしょうがない。私はああいう、PCのハードを直接制御するようなコードを見るとワクワクしてくるんだ。実際にはBIOS経由だから直接じゃないが。足が痛いからPCの前に座っちゃいけないんだが、少しだけ、少しだけやろう。

INT18でAHレジスタ=42Hの時、CHレジスタの値は何を入れるのか。
私自身が若いころに書いたソースコードと解説を読むと、この1バイトをビットごとに見ると
LLCP0000
で、
LLは01なら200ラインLOWER、10なら200ラインUPPER、11なら400ラインだ。
Cは0ならカラー、1ならモノクロ。
Pは0ならGVRAM表示ページ0、1なら表示ページ1。
だから

MOV AH,42
MOV CH,80
INT 18

は、GVRAMを200ラインUPPER、カラー、表示ページ0で使う設定だ。
ええと次は

MOV AL,08
OUT 68,AL

OUTだって。それ何。I/Oポートだって。いよいよハードウェア直接制御か。私は自作のCライブラリから68Hを検索した。ない。これはなかった。残念。

ネット上に情報があった。苦労して実験を重ね、自力で情報を作ったサイトらしい。それによると、この命令はPC-8801との互換のために存在するという。グラフィック画面の奇数ラスタを表示する指定だそうだ。

MOV AL,4B
OUT A2,AL
上記のサイトによると、4BHはグラフィックGDCへのコマンドCSRFORMらしい。I/Oポート番号A2HでGDCにコマンドCSRFORMを指定して、次の

MOV AL,00
OUT A0,AL
I/Oポート番号A0HでGDCにコマンドのパラメータを指定するようだ。

上記サイトにはI/Oポート番号A2Hに4BHをOUTするのがグラフィックGDCへのコマンドCSRFORMだということと、それに続くI/Oポート番号A0HへのOUTがCSRFORMコマンドのパラメータ指定だということは載っていた。しかしパラメータの詳細までは載っていなかった。

さらにネット検索したら、別のサイトにたまたま載っていた。コマンドCSRFORMの場合、パラメータ指定A0Hは全部で3回続ける。ただし2回目や3回目のOUTをしなければ、その時点までとなる。この使い方を思い出して、私は大昔に買った赤い本を懐かしく思った。

このコマンドをテキストGDCに送った場合、カーソル表示のいくつかの設定ができる。しかしグラフィックGDCの場合、グラフィック画面にカーソルは表示しないので、設定はただひとつ、1行のライン数だけだ。ふつうグラフィック画面は1行1ラインだから、このコマンドは本当はグラフィックGDCに送るまでもない。

GDCへ送る3回のパラメータのうち1回目の、ビット0からビット4までが、((1行のライン数)-1)を表す。つまりI/Oポート番号A0Hへ送っている00Hが、1行1ラインの設定だ。

残念ながら私のCライブラリにはこのコマンドがない。自分が有用だと思ったコマンドはライブラリに取り入れたが、不要だと思ったコマンドは取り入れなかった。前回の記事で、若いころの自作Cライブラリを見つけた時、私はまるで過去の私がウルトラマンのように助けに来てくれたと感じたものだった。たしかに若いころの私は、颯爽とやってきて、私を救ってくれた。でも、そのあとすぐに力尽きて倒れた。これはウルトラマンというよりウルトラの父の登場シーンだな。

PC-98用にCやMASMでプログラミングしていた人なら同じだろうが、私もPC-98のデータブックを買った。私の記憶では、青くて分厚い本にはメモリマップやBIOSの情報があった。GDCについてはそれでは足りなくて、ブツクサ言いながらしばらく過ごしていたら赤い本が出た。速攻で買った。どちらも当時の私の愛読書だった。でも時が流れPC-98用ソフトが過去のものとなり、私がまだエミュレータの存在を知らない(ひょっとするとまだなかった)時期に、今後は役立たないと思って処分した。今になってこんなに残念だ。

MOV AH,42
MOV CH,C0
INT 18
さっきGVRAMを200ラインUPPERにしたばかりなのに、ここで400ラインにしたぞ。

という所までで、残念ながら今日は終わりだ。足が本格的に痛くなってきた。それなのにニーモニックや割り込み処理を見ていると、やめられない止まらない。このままでは明日歩けない。実はPCの前に座るのが無理だから、さっきから安楽椅子に座ってスマホで書いていた。それもここまでにしなきゃ。

コメント(0) 

孤独へ向って突っ走れ(8) [  PC-98x1(補完計画)]

本日3つめの記事。とうとう1日中これをやっていた。たとえこれが世界一退屈なブログ記事であろうとも、書いてる本人は必死だ。
アセンブラは数十年やってない。ほとんど全部忘れた。それをネット検索で学び直しつつ、ニーモニックひとつひとつの意味を追ってゆく。
ニーモニックの解説はネット上にある。DOSのシステムコールの解説もネット上にある。問題はPC-98のソフトウェア割り込みの詳細だ。大昔、私はそれの分厚い本を持っていた。でも要らなくなったからとっくの昔に捨ててしまった。ネット上にはなかなか見つからない。どうするおじさん。その時空の彼方から我らがウルトラマンが助けに・・・は来なかった。なんと来たのは、私自身だった。昔の私がやって来た。それなりにえらいぞ私。大昔の若者だった私が、タイムマシンに乗って、いまおじさんの私を助けに来てくれたよ。という所までが今回の話。
このへんで書いておくが、ここから下の数十行だか百数十行だかには、ひたすらニーモニックがずらずらと続く。必死で解読した私以外の人には間違いなく退屈だから、このページを下のほうまでずーっとスクロールしちゃって、記事の最後のあたりまで行くといいと思う。



コードセグメントのアドレス0000Hから0016Hまでの命令
プログラム先頭のメインルーチン。最終的にアドレス6FECHへ飛ぶまで。

MOV AX,1D7C
MOV DS,AX
データセグメントは1D7CH
(これはまあ、たまたま1D7CHだったと考えるか。)

XOR AX,AX
MOV ES,AX
MOV AL,ES:[0584]
MOV [384B],AL
セグメント0000Hのアドレス0584Hにある1バイトの値を
データセグメントのアドレス384BHに入れる
(0000:0584はBIOSデータエリアで、OSがブートしたディスク装置の種類。その情報をデータ領域にコピーした。)

LEA SI,[1F6A]
ソースインデックスレジスタにアドレスとして1F6AHを入れる
(SIの値はCALL先で使う。何の値かはまだわからん。)

CALL 6FEC
コードセグメントのアドレス6FECHへ行ってこい




コードセグメントのアドレス6FECHから7024Hまでの命令
メインルーチンから飛んできたサブルーチン。最終的にアドレス7026Hまたは704EHへジャンプしてサブルーチンは続く。
上記CALL 6FECの次行へ戻れるのはいつのことやら。

CMP WORD PTR [13B8],+01
JNZ 7026
データセグメントのアドレス13B8Hからの2バイトが+01でなければ
アドレス7026Hへ飛べ
(1D7C:13B8にはまだ何か入れる命令はない。あらかじめ何かの値が入っている。)
(+01の記述の意味がまだ調べられないでいる。なんで+が付いているんだ。)

MOV AX,1D7C
MOV ES,AX
MOV DI,1EE8
ADD DI,+11
MOV AX,0000
MOV CX,0028
REP STOSW
セグメント1D7CH(データセグメント)のアドレス1EE8H+11から0000Hの書き込みを28H回(40回)繰り返す
(結果として1D7C:1EE8+11からの80バイトは0になる。)
(+11の記述の意味がまだ調べられないでいる。たぶん16進数だろうが。)
(CLDはどこでしているんだ?)

MOV DI,1EE8
MOV CX,0004
REP MOVSW
データセグメントのアドレス1F6AH(SIがここで使われる)から8バイト分、データセグメントのアドレス1EE8Hへコピーする
(SIはインクリメントされて1F72Hになり、DIはインクリメントされて1EF0Hになっている。)
(CLDはどこでしたんだ?)

MOV AX,[13BA]
ADD AL,62
MOV [DI],AL
データセグメントのアドレス13BAHにある1バイトを取り出し、それに62Hを足し、それをデータセグメントのアドレス1EF0Hに入れる

ADD SI,+02
ADD DI,+09
MOV CX,0050
REP MOVSB
SIは先ほどMOVSWで使われてインクリメントされ1F72Hになっているが、さらに2を足す。DIは1EF0Hになっているが、さらに9を足す。SIからDIへ50Hバイト分コピーする
(何度も言うがCLDはどこでしたんだ?)

MOV SI,1EE8
JMP SHORT 704E
ソースインデックスレジスタに値1EE8Hを入れ
アドレス704EHへ飛べ
(SIに入れた1EE8はどうやら何かのファイルを読み込むさいのアドレスで、WORD PTR[SI]にはファイルを読み込むバッファのセグメント、WORD PTR[SI+2]には同じくバッファのオフセット、WORD PTR[SI+4]にはファイル長、[SI+8]からはファイル名が格納されている。)

感想
PUSHもPOPも出てこないから、このプログラムはCで書かれていない。冗長でないのは有難い。でもまだまだ相当かかりそうだ。




コードセグメントのアドレス704EHから7079Hまでの命令
サブルーチンの続き。JMP SHORT 704Eのほうを追いかけることにした。最終的にRETでメインルーチンへ戻る。

PUSH SI
PUSH DS
SIとDSをスタックへ退避したぞ

MOV AX,3D00
MOV DX,SI
ADD DX,+08
INT 21
JB 707E
ハンドルによるファイルのオープン(ASCIIZファイル名はデータセグメントのアドレス(1EE8H+08H)にあり)
もしもエラーならば707EHへ飛べ

MOV BX,AX
PUSH BX
MOV AH,3F
MOV CX,[SI+04]
MOV DX,[SI+02]
MOV DS,[SI]
INT 21
POP BX
JB 707A
オープンしたファイルからセグメントWORD PTR[1EE8H]のオフセットWORD PTR[1EE8H+02H]へ
WORD PTR[1EE8H+04H]のバイト数だけ読み込む
もしもエラーならば707AHへ飛べ

POP DS
PUSH DS
MOV AH,3E
INT 21
JB 707A
ファイルをクローズ
もしもエラーならば707AHへ飛べ

POP DS
CLC
POP SI
DSとSIをスタックから戻した
キャリーフラグもクリア

RET
ああ、やっと最初のほうにあったCALL 6FECの次行へ戻れる




コードセグメントのアドレス0017Hから0025Hまでの命令
プログラム先頭から続くメインルーチンに戻ってきた。サブルーチンではない。

CLD
(あっ、いまごろCLDがっ)

MOV WORD PTR [0000],0000
MOV AX,1D7C
MOV DS,AX
データセグメントのオフセット0から2バイトを0に
データセグメントは1D7CH
(いちばん最初と同じだ。)

CALL 4306
コードセグメントのアドレス4306Hへ行ってこい




コードセグメントのアドレス4306Hから・・・

MOV AH,42
MOV CH,80
INT 18







ここでまたINT21か・・・あれ? INT18? DOSシステムコールじゃない!これはPC-98の何らかのハードウェアを制御するために割り込みをかけている。今までのたくさんのネット検索の間に、割り込み番号と用途の簡単な対応を記したサイトは見つかっていた。でも18HはキーボードおよびCRT BIOSに使われるとしか書いていない。AHレジスタに42Hを入れた時の機能がわからないと困る。私はなおもネット検索した。「詳細がネット上にあってびっくりした」という書き込みがあったが、肝心のその詳細へのリンクがすでに切れていた。さらにネット検索し続ければいずれはどこかに見つかるかもしれない。しかし大変なことだ。その時、私は思いついたことがあった。若いころに作ったPC-98の機能を引き出すためのCライブラリ、そこではINT18も使っているに違いない。私はそれを探した。こんなのが見つかった。(ソースコードはpreタグで括ってあるので右端はブログの表示域からはみ出てしまうと思う。たぶん、PCならマウスで選択してコピー&「メモ帳」へ貼り付けで見えるのでは。)
#include <dos.h>

/* 関数プロトタイプ宣言 */
void gsplit( int page, int color, int lines );


void gsplit( int page, int color, int lines )
{
    unsigned char info;
    union REGS inregs, outregs;

    info = ( page & 0x01 ) << 4 | ( color & 0x01 ) << 5 | ( lines + 3 & 0x03 ) << 6;
    inregs.h.ah = 0x42;
    inregs.h.ch = info;
    int86( 0x18, &inregs, &outregs );
}


ほとんど何も覚えてないが、とにかく0x18って書いてある。そしてah=0x42って書いてある。でもこれだけだと機能がわからない。私は自分が大昔に書いたドキュメントを探した。そうしたら、こんなのが残っていた。(ソースコードと同じくpreタグで括ってある。)

3-1.諸状態の設定

void gsplit( int page, int color, int lines )

BIOSを使って次の3項目の設定を同時に行う。
・表示ページの設定
・カラー/モノクロの設定
・400ライン/200ライン(およびVRAM上の表示アドレス)の設定
3項目同時に設定する必要がない場合は、下記のIOポート操作版の処理でそれぞれ別
個に設定することもできる。
・表示ページの設定は本ライブラリ中に別にIOポート操作版を設ける。
・カラー/モノクロの設定はTXTGDC.HにIOポート操作版modeff(GMODE,*)がある。
・400ライン/200ラインの設定はTXTGDC.Hのmodeff(GRPMODE,*)、本ライブラリの glin
  esset()、gsplitset()、gscopeset()を組み合わせることにより実現する。
  -glinesset()では一行のライン数を設定し、gsplitset()、gscopeset()ではGVR
  AMのどこから何ライン分(何行ではない)表示するかを設定する。たとえば、一行
  を1ラインとし、GVRAM先頭から400ライン分表示すれば普通の400ラインモード
  となるが、一行を2ラインとすれば200ライン LOWER モードとなる。一行を同じく2
  ラインとしてGVRAM真中から400ライン表示すれば、画面は200ライン UPPER と
  なる。
  -これでわかる通り、IOポートを直接操作すれば一行3ライン、4ライン、5ライ
  ン等の画面も作れる。この場合、画面は粗くなるが、使用するVRAMは節約できる。
  -いっぽう modeff(GRPMODE,*) は、400/200ラインそのものを設定するのではない。
  これを200ラインに設定すると1ラインおきに描画するらしく、画面に細かい横縞が
  現れる。BIOS版での200ラインモードはこの状態に相当する。しかし専用ディス
  プレイを使う場合には、modeff(GRPMODE,*) の設定は、画面表示の綺麗な400ライン
  用の設定のままが良いだろう。
この関数には以下のマクロが使える:
GPAGE0  GPAGE1  COLOR  MONO  DISPLAY200U  DISPLAY200L  DISPLAY400
マクロのうち COLOR, MONO は TXTGDC.H で、DISPLAY400 は TXTDOS.H で既に使ってい
るので、そのまま利用する。


私は「テニステニス2」のプログラムを見ることで自分を元気にしようと思ってきたが、それだけでなく、これはひょっとすると「大昔の自分との再会」という予想以上の喜ばしい出来事が起きてしまうかもしれない。でも今は、無理をしてPCの前に座り続けたせいでちょっと危ない腰をかばいつつ、もうやめなければいけない。

がんばれおじさん!
負けるなおじさん!
腰痛にも気をつけろ!

コメント(0) 

孤独へ向って突っ走れ(7) [  PC-98x1(補完計画)]

前回の記事の続き。

ウィキペディアに「EXEフォーマット」というページがあった。はじめは誤ってPEとかいうほうを調べ始めてしまったが、これはDOS時代のフォーマットではなく後のWindowsでの拡張フォーマットだった。DOS時代のEXEファイルの先頭には以下のヘッダがあるそうだ。

typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; /* 00: MZ Header signature */
WORD e_cblp; /* 02: Bytes on last page of file */
WORD e_cp; /* 04: Pages in file */
WORD e_crlc; /* 06: Relocations */
WORD e_cparhdr; /* 08: Size of header in paragraphs */
WORD e_minalloc; /* 0a: Minimum extra paragraphs needed */
WORD e_maxalloc; /* 0c: Maximum extra paragraphs needed */
WORD e_ss; /* 0e: Initial (relative) SS value */
WORD e_sp; /* 10: Initial SP value */
WORD e_csum; /* 12: Checksum */
WORD e_ip; /* 14: Initial IP value */
WORD e_cs; /* 16: Initial (relative) CS value */
WORD e_lfarlc; /* 18: File address of relocation table */
WORD e_ovno; /* 1a: Overlay number */
WORD e_res[4]; /* 1c: Reserved words */
WORD e_oemid; /* 24: OEM identifier (for e_oeminfo) */
WORD e_oeminfo; /* 26: OEM information; e_oemid specific */
WORD e_res2[10]; /* 28: Reserved words */
DWORD e_lfanew; /* 3c: Offset to extended header */
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

ところが、英語のサイトではオフセット1BHまでがEXE headerとして載っている。つまり上のe_ovnoまでだ。なんで異なるんだ。

私が知りたいのは、EXEファイルのオフセットいくつからCPUへの命令が格納されているかということだが、情報に行き当たらない。上記のヘッダのすぐ次とは限らないではないか。それに、ネット検索でちゃんと"MS-DOS"とキーワードを入れているのにPEばっかりヒットする。

そのうちにおじさんは情けなくも疲れてきた。人は疲れると短絡的思考に走る。どっかにEXEファイルの逆アセンブラはないだろうか。おじさんがどこからどこまでがヘッダか知らなくてもとにかく勝手に正しい場所を逆アセンブルしてくれるユーティリティはないだろうか。おじさんはMicrosoft Visual Studioを持っている。逆アセンブラくらい入っているだろ。試してみた。

エラー:ファイル 'T.EXE' が見つからないか、または PE ファイルではありません

またPEかよ!
加トちゃんPE!

VECTORのサイトで逆アセンブラを検索。Z80用。あれ・・・
NEフォーマットおよび、PEフォーマットの実行ファイルを解析する・・・はう・・・
6809の逆アセンブラです・・・そうですか、頑張ってください・・・

おじさんはもう疲れてだめだ。MASM6.0なら持ってるよ。これなら時代的にPEじゃないし、8086用だよ。これでアセンブルはできるけど、必要なのは逆アセンブラ。
tt04.png
逆アセンブラは、なんか入ってないみたいだ。

SYMDEB.EXEでも逆アセンブルできるという情報が。ところがねえ、今どき、このSYMDEB.EXEが探しても見つからない。

清十郎氏が作られた、DIASというSYMDEB的なソフトがあるという。
試してみた。もちろんWindows10上ではなくPC-98エミュ上で。

tt05.png
わあい!何か出て来たぞ。
ニーモニックだぁ。
やっとスタートラインに立ったぞ。

そういえばはるか昔にバカボンのパパが、パカランパカランやっと隣町に着いたぞとか何とか言わなかったっけ。今のおじさんはバカボンのパパと同レベルだ。

コメント(0) 

孤独へ向かって突っ走れ(6) [  PC-98x1(補完計画)]

今回は突っ走れないで酒におぼれている。飲みながらのヘボ記事でごめんなさい。

最初に書いておく。ブログ記事タイトル「孤独へ向かって突っ走れ」は今までプログラミング系の話だったはず。それが今回は横道に逸れてPC-98的な話になる。

同タイトルの前回の記事(5)が「音量を少しずつ均等に下げてゆくにはどうしたら良いのだろう」で終わった。あれがいけなかった。その答えはうすうす気づいていたんだ。アンチログ。またアンチログなのか。若い頃にアナログシンセのCVにアンチログを使って以来、なんだかアンチログには縁がある。でも私は、ウルトラマンが地球上では急激にエネルギーを消耗するように、数学が始まると急激に気力を消耗するのだ。昔のRPGゲーム風に言えば体力は残ってるが気力ゼロで動けない、みたいな。

それで私は逃げた。なにもこのまま最後まで逃げたままとは限らない。でもひとまず逃げた。

逃げながらネットサーフィンしていたら、PC-98ゲームのデータベースと称するサイトに行き当たった。そのことは少し前の記事に書いた。その時に、どこかで見たようなゲーム画面とどこかで見たようなゲームタイトルを見つけた。「エルムナイト」という。これはたぶん、私が雑誌の付録ディスクで体験版を少しだけやったゲームではないだろうか。3Dバトルが魅力的ながらも難しそうだったからか、私は結局買わないで終わってしまった。もっと情報はあるかなと思い、昔の雑誌を見てみた。そうしたら記事があった。

h583.jpg
極秘なわけないだろ、と、いちいちツッコミ入れてもつまらないので先へ行こう。

h584.jpg
記事のコンテンツはこんなになってる。私は体験版しか知らないから、いまひとつ記憶に残っていない。やはりゲームは自分で買って最後までプレイするのが大事だ。

h585.jpg
200ラインだそうだ。そういえば私が知っているマイクロキャビンの別のゲームも200ラインだった。当時のPCは処理速度が亀、いやナメクジ位の遅さだったので、素早い画面の動きが求められるタイプのゲームでは画面の解像度を落としてそのぶん描画の処理速度を上げるのは有効な手段だった。

私は根っからのゲーマーではない。若いころの私はCやBASICのプログラミングが好きで、ゲームは少しだけやっていた。そういう事情だから、今回せっかく見つけた「エルムナイト」も私の気力を復活させるアイテムにはならなかった。でも私はここでヒントをもらった。ある別のゲームのことを思い出したのだ。

「テニステニス2」というPC-98用ゲームがある。私はこれを若いころに買ってプレイしたことがあり、その後処分してしまったが、後年また中古を買った。2回も買ったわけだからそれなりの回数プレイしたが、それでもゲームに多くの選択肢(このゲームでいえば対戦相手)があれば、まだ一度も見ていない選手の顔がある。でもそういうのの完全制覇を目指せるのは元気な若者の頃だけで、私のようなおじさんはもう気力も体力も追いつかない。それでは、私の本来の趣味に戻り、このゲームをゲームとしてでなくプログラムとして楽しむのはどうか。

もしも私が機械語を使いこなせていたら、プログラムをたどってゆくと、そのプログラムを作った人の頑張りやこだわりが見えてきて有意義に違いない。でも残念ながら私は、昔の元気な若者の時ですら、ニーモニックをちょっと覚えた程度だった。それももうとっくに忘れた。

私は思った。若いころの私はBASICはできた。Cもできた。でも機械語の解析は大変すぎてできなかった。それが若いころの私の限界だった。いま、この歳で改めてその限界の突破に挑戦するってのはどうだ。

その試みは、例えて言えばヨボヨボのジジイがフルマラソンに参加するみたいなものだ。途中で体調を崩してリタイヤし、自分には無理だったと思い知る可能性が一番高い。もちろん、今回のこれはマラソン大会と違ってゴールまでの制限時間がないから、体調を崩したらそこまでにして後日またそこから走るというのはある。その結果、何年も後にゴールまでたどり着く可能性はある。何年も・・・何年も後に。たとえ忘れたほど後年にでも、私はゴールまでたどり着けるのだろうか。それとも今回の記事だけで、立ち消えか。

「テニステニス2」の頃は、たぶん、BASICでプログラムを組むのが主流だった時期が終わり、MS-DOSを手本にしたオリジナルOSで動くものが多い時期だったと思う。もしそうならば、内部のテキストデータは現在の日本語版Windowsと同じShift JISで読めるかもしれず、システムコールの類もMS-DOSに倣っているかもしれない。はじめ私はフロッピーのブートセクタとかいう部分から解析を始めなければならないかと悲痛な決心をしていたが、もう少し楽かもしれない。

その後いろいろあって、すでに年とったおじさんの私はバカな失敗をしつつも、ディスクAの中身を確認するまでに至った。おおもとのフロッピーを使うとPC-98実機に挿入しなければならず、その後の事が大変なので、すでにイメージ化してあるファイルを使った。そのイメージをまずvficを使ってDiskExplorerに読み込める形に変換するのだが、この時点でディスクBはなぜかエラーが出て不可。でもディスクAが変換できれば大丈夫だろう。ディスクAだけを挿入して起動しても最初の動作選択メニューまでは表示されるのをあらかじめ確認済みだ。つまりゲームを動かすプログラムはディスクAにある。

tt01.png
で、ディスクAの中身はこうだった。ラッキーだ。MS-DOSに似ている。ディスクの先頭にまずIO98.SYSとMEGDOS.SYSがある。でもこの2つはシステムだから、ゲームを動かすプログラムは別にある。あからさまにそれらしい巨大なサイズのT.EXEがあるが、先入観で決めてかかっちゃいけない。AUTOEXEC.BATはない。となると、CONFIG.SYSの中身を確認。

files=40
buffers=60
device=stmouse.sys
shell=t.exe

あった。shell=t.exe。これで、ゲームのプログラムはT.EXEということがわかった。ここまで来たら(いや、ここまで来なくても)、「メモ帳」にファイルを読み込んで表示させるとたまに面白い結果が見られる。

tt02.png
最初のMZは、このファイルが本当に実行可能ファイルだということを意味する。その後の文字化け部分は「メモ帳」では何もわからない。スクロールする。

tt03.png
なるほど、ゲームのテキストがそのまま実行可能ファイルに入ってる。ということは、このプログラムはこのゲーム専用のもので、次回作にこのプログラムを利用する気はなかったようだ。「本ゲームはコピーディスクでは動作しません」というのは実に当然のことだが、もしもユーザーがPCのディスプレイでこのメッセージを見たら「ガビーン!!」となる所だ。もちろん正規ディスクを使っていればこのメッセージは一生見ることがない。上の画像が一部黒塗りになっているのは、住所が書いてあったので私が焦って塗りつぶした所だ。今その住所に何があるか知らないが、個人情報に厳しいこんにち、住所なんてものは塗りつぶしておくほうがいい。

ここまでは、できて当然みたいなものだった。問題はここからだ。私はこれから一体どうすればいいのか。バイナリエディタ(ビューアー)を使うのか。データを1バイトずつ解析か。実行可能ファイルの解析という大変な作業を、私はやったことがない。

でも、ここまで書いてきて私はひとつ気づいた。この記事を書き始めた時あれほど無気力だったり逃げたりしていた私が、こんなに元気になった。やはり人間にはその人なりの得手不得手があり、その人なりの気力復活アイテムがあるんだ。

コメント(0) 
前の30件 | 次の30件   PC-98x1(補完計画) ブログトップ