MANIMANIAのレトロエロゲーカウントダウン

人生の残り時間が半分を切ったというのに若き日に目にしたエロゲーに魂を引かれ続けるイタいおっさんがこなしたゲームを紹介。

FATの読み方

2010-08-01 22:34:49 | レトロパソコンのお作法
FATの読み方は難しい。
人間の生理を離れた作業が必要となるので,忘れたが最後,もう思い出せません。
そんなわけで,FATの読み方を整理しておきます。

FATってのは,ディスク内のファイルデータの配置を記録したデータですので,まずターゲットとなるファイルを設定しないことには具体的な話ができません。
今回は,自然数である0から638までを改行だけして並べたTESTファイルを作成して,これをDOS6.2のシステムだけ転送したフロッピーイメージに保存しました。

早速,ディレクトリ領域をSYMDEB.EXEで覗いてみます。


この機会に読み方も覚えておきましょう。





ファイル名は読んで字の如し。ファイル名が8文字に達しない場合は,20h(スペースの文字コード)で空白を埋められます。
ファイル名欄が00hから始まる行はその後方に有効なファイル情報が存在しないことを示します。
後述しますが,ファイル名欄が05Ehから始まるディレクトリエントリは削除されたファイルの痕跡です。



属性は,10の位と1の位を分けて考えます。
まず,10の位が2の場合,未バックアップファイル(アーカイブファイル)であることを意味します。これはBACKUPコマンドによってアーカイブされたことがあるかどうかを示すもので,大抵のファイルの10の位は2です。
また,10の位が1の場合,ファイルではなくサブディレクトリであることを示します。
次に,1の位は1,2,4,8の合計値に分解します。
例えば,上の画面中のIO.SYSやMSDOS.SYSの属性値の1の位は7ですから,
7=1+2+4
です。
このうち,1は,リードオンリーファイル属性,消去や変更の不可能な書き込み禁止ファイルというやつです。
2は,隠しファイル属性,DIRコマンドで表示されず,DELコマンドも及びません。
4は,システムファイル属性,リードオンリーかつ隠しファイルです。
このディスクにはボリュームラベルを付けませんでしたが,もしボリュームラベルを付けていれば,1の位には属性値8が表示されることになっていました。
コンピュータは,この部分に書き込まれた数値を見て,各ファイルの属性を判断するので,このIO.SYSやMSDOS.SYSの属性値を例えば20などに書き替えてしまえば,通常のファイル同様,DIRコマンドで表示させることができますし,変更も消去も自由にできるわけです。



次は更新日時です。データの並びからは更新時刻が先に来ていますので,まずこちらを説明します。
更新時刻は「D6 61」と並んでいますから,リトルエンディアン形式の表記に即して,「61 D6」と読み替えます。
この61D6hは2進数で,
0110000111010110
となります。電卓で2進数化すると上位の0が省略されて16桁にならない場合があるので,その場合は16桁になるよう頭に0を追加してください。

ちなみに,16進数を2進数化するのは簡単でして,「6」,「1」,「D」,「6」を順番に2進数にして並べればいいのです。
すなわち,各数値を8,4,2,1の和に分解して,あれば1,なければ0を順番に並べた数列を,さらに順番に並べればいいのです。
06hは,4+2ですから,0110,
01hは,1ですから,0001,
0Dhは,10進数で13,8+4+1ですから,1101。

そうすると,
06h,01h,0dh,06hは,0110,0001,1101,0110の並びになりますから,上述の数字になるわけです。
この数字を,5桁,6桁,5桁と切り分けます。
そうすると,
01100  001110   10110
(時)   (分)   (秒)
となります。
2進数の 01100は,10進数の,8+4で,12です。
2進数の001110は,10進数の,8+4+2で,14です。
2進数の 10110は,10進数の,16+4+2で,22です。
これは,このファイルが,12時14分22秒に更新されたことを示しています。

というのは少し誤り。
秒の欄を示す5桁の2進数は11111,10進数で16+8+4+2+1=31が上限の数字ですから,1秒ごとに数値をあてはめると32秒から59秒を表現できません。
そこで,秒については,半分の数値で記録しています。
つまり,記録された数値は22ですが,読み出す段階で2倍して44秒と読みます。
かくして,正解は,12時14分44秒となります。(秒部分訂正追記・10/10/27)

更新年月日についても同様に,「01 3D」→「3D 01」を2進数化したうえで,7桁,4桁,5桁に切り分けます。
そうすると,
03h,0Dh,00h,01hをそれぞれ2進数化すると,0011,1101,0000,0001となりますから,3D01hは0011110100000001です。これを7桁,4桁,5桁に切り分けると,

0011110  1000  00001
(年+1980) (月) (日)
となります。
2進数の0011110は,10進数の16+8+4+2で,30です。
2進数の   1000は,10進数の 8です。
2進数の  00001は,10進数の 1です。
これは,このファイルが,1980+30=2010年8月1日に更新されたことを示しています。



ファイルサイズは,DWord(Double Word)形式で記述されています。
「0D 0C 00 00」はリトルエンディアン形式で表記されていますから,まず,下位2バイトと上位2バイトを入れ替え,「00 00」「0D 0C」の並びにしてから,さらにいつもどおりこれら2バイト内の上下を入れ替えます。すると,
00 00 0C 0D
となります。
0C0Dhは,10進数で3085ですから,このファイルサイズは3085byteということが分かります。
ファイル属性がサブディレクトリの場合,このサイズは0となります。



さて,残るは「先頭クラスタ番号」です。
先ほどのTESTファイルの先頭クラスタ番号を見てみましょう。
「009Fh」ですね(リトルエンディアン形式参照)。

フロッピーディスクのブートセクタ解析の説明の中でも書きましたが,FAT領域は,先頭にFAT-ID,次にFFが2つ続いて記録されています。
後述するFAT指定の様式に従うと,これら3バイト分のデータは,本来,クラスタ0とクラスタ1を指定すべきものですが,このとおり冒頭の2クラスタ分が使えない仕様となっています。
そのため,囲んではいませんが,ディレクトリ領域の最初のファイルであるIO.SYSのFATの先頭クラスタ番号は0002hとなっていますね。
そんなわけで,FATの示す先頭クラスタ番号から論理セクタ番号を計算するにあたってはこの2クラスタ分を減ずる必要があります。

また,フロッピーディスクのブートセクタ解析で説明したとおり,このディスクのデータ領域の先頭セクタは,論理セクタ番号で0Bhです。

したがって,TESTファイルの格納された論理セクタ番号は,
0Bh+(09Fh-2)=09Fh+9=0A8h
ということになります。
(実は,ここはセクタとクラスタのサイズ比が影響する場合があるのですが,今回のディスクは1クラスタあたり1セクタなので,あえて無視して記述を進めていますのでご注意ください。)

TESTファイルの先頭部分の格納された論理セクタ番号0A8hのセクタのデータをロードしてみます。


見事読み出せました,パチパチパチ。
って,FATは続けて読んでなんぼですから,ここで喜んでるようじゃ話になりません(喜んでたじゃないか)。

先のLコマンドの後,
-D 0 3FF
とやると,09Fhの指示するセクタに格納されたデータの末尾が見られます。



226までは,最初のクラスタに収まったようです。

では,「009Fh」のファイルの続きはFATのどこを見れば分かるのでしょうか。
今度は単なる足し算ではすみません。

09Fhを2で除して,小数点以下を切り捨て(整数部のみ残す),3を乗じます。
計算式で言えば,INT(09Fh/2)*3ということです。答は0EDhです。
Excelは16進数の計算をするのにいちいち10進数化しないといけないことが分かりました。イライラ。関数電卓のほうが優秀ってどういうことよ。



0EDh部分は,こちら。


そして,FATエントリの読み方のややこしさはリトルエンディアンの比じゃない。
まず,まず,左右の各1バイトをそれぞれ反対に付け替える。


真ん中で切り分ける。


さらに左右逆転。


クラスタ番号の偶数が前の分,クラスタ番号の奇数が後ろの分を取る。
クラスタ番号が2から始まることを覚えておけば偶数が先だと思い出せるはずです。
今回は,クラスタ番号が09Fhで,奇数なので,後ろの分である「0A0h」が次のFATエントリと判明しました。

「009Fh」の次が「0A0h」って,単なる続きやん!というツッコミが入りそうですが,いつもいつもこうとは限らないので基本の確認は大事なんです!

まあ一応続きも。
INT(0A0h/2)*3=0F0h

A1 20 0A を 0A 20 A1 と並べ替えて,0A2 と 0A1 に分けて,再び左右並べ替えると,0A1h 0A2h。
0A0hは偶数なので,前を取って,次のFATエントリは0A1h。
(哲哉様のコメントで誤りをご指摘いただいたので、修正しました。2022/11/22 14:21)

続いて,
INT(0A1h/2)*3=0F0h
0A1hは奇数なので,後ろを取って,次のFATエントリは0A2h。

そろそろ飽きてきましたが,
INT(0A2h/2)*3=0F3h

FF 0F 00 を 00 0F FF と並べ替えて,000 と FFF とに分けて,再び左右並べ替えると,FFF 000。
0A2hは偶数なので,前を取って,次のFATエントリはFFF。
というわけではなく,ファイルの最後のクラスタに対応するFATには,0FFFhが書き込まれています。

ちなみに,FATエントリの000hは未使用クラスタ,0FF7hは不良クラスタでスキップセクタがあるため使用しません,また001hの値は使われません。

結局,09Fh→0A0h→0A1h→0A2hというクラスタチェーンを経て,TESTファイルがすべて格納されました。
念のため,0A2hのクラスタ内のデータを確認しておきます。

論理セクタ番号は,
0Bh+(0A2h-2)=0A2h+9=0ABh
ですから,
論理セクタ番号0ABhのセクタのデータをロードしてみます。



はい。638までなんて中途半端なTESTファイルにしてしまったためにオチの画面が締まりませんが,ちゃんと最終セクタのデータまで辿り着くことができました。
ちなみに,1セクタあたり1024byteですから,3セクタ分3072byteに「36 0D 0A 36 33 37 0D 0A 36 33 38 0D 0A」の13byteが加わって,ファイルサイズは3085byteとなり,ディレクトリ領域に書かれていたファイルサイズとも一致するわけです。

さて,ここまで読み進めてしまえば,最初のディレクトリ領域のダンプ画面から,IO.SYSやMSDOS.SYS,COMMAND.COMの各ファイルの各先頭クラスタを読み取って,FATエントリを目で追えるようになったのではないでしょうか。



とまあ,健全なFATだけ見てもつまらないので,消してしまったファイルの復活方法でも勉強しますか。

では,早速,フロッピードライブであるBドライブから,TESTファイルを削除してしまいましょう。



TESTファイルが削除されたことで,ディレクトリエントリやFATにはどのような変更が加わったでしょうか。

まず,ディレクトリエントリを見てみます。


0060 54 45 53 54 20 20 20 20 ・・・ TEST
となっていたディレクトリエントリが,
0060 E5 45 53 54 20 20 20 20 ・・・ eEST
となってしまいました。
ファイルを削除するとディレクトリエントリのファイル名の1文字目の1バイトがE5で上書きされることが分かります。

次に,FATを見てみます。


INT(09Fh/2)*3=0EDh
ですから,0EDhに注目です。


削除前の同一箇所はこうでした。


09Fh→0A0h→0A1h→0A2h(FFFh)と続いていたクラスタチェーンがすべて 00 で上書きされていることがお分かりかと思います。
前述したとおり,FATエントリの000hは未使用クラスタを意味します。
TESTファイルが削除されたことで,FAT上はTESTファイルの使用されていた領域が未使用クラスタとして登録されてしまったわけです。

ついでですから,TESTファイルの格納されていた論理セクタ番号である
0Bh+(09Fh-2)=09Fh+9=0A8h
を読み出してみましょう。


ご覧のとおり,元のデータ領域には何の手も加えられていなかったというわけです。

結局,うっかり削除してしまったファイルを復元するためには,
1 ディレクトリエントリのファイル名を元に戻す
2 FATのクラスタチェーンを復元する
という2工程が必要となります。

今回の場合,FATのクラスタチェーンの元の状態を分かっていますから,FATを読み出した上で,
メモリの内容を変更するE(Enter)コマンドでアドレス「EDh」からのメモリを「FF 0F 0A A1 20 0A FF 0F 00」と編集して,

-E ED FF 0F 0A A1 20 0A FF 0F 00
メモリをロードして,中身を確認します。
-D 0 FF


予定どおりのメモリ内容に変更されていますので,
メモリ内容をディスクに書き込むW(Write)コマンドで変更したメモリ内容をディスク内データに反映させます。コマンドは,ドライブ1の論理セクタ1に1セクタ分書き込むわけですから,
-W 0 1 1 1
と入力します。終わればQ(Quit)コマンドでデバッグ画面から抜けます。


早速成果を確認したいところですが,まだ1工程残っていました。
ディレクトリエントリのファイル名の修正です。
ディレクトリエントリを読み出した上で,60hのメモリをTの文字コードである54に書き替えます。
-E 60 54
-D 0 FF


メモリを編集しただけで満足せず,ちゃんとディスクに書き込みましょう。
-W 0 1 5 1


Qコマンドでデバッグ画面を抜けたら,DIRコマンドで,TESTファイルの復活を確認します。


ついでにSEDIT.EXEで中身を確認しておきます。
冒頭から,


最後まで無事に復元できました。



無事,復活できたようです。
しかし,FATデータがいったん消されてしまうと,ファイルの復活は容易ではないですね。
今回は,元のFATデータを把握していたので,000で上書きされた箇所に埋める数値が分かっていましたが,実際には,ディレクトリエントリから読み取ったファイルサイズに基づいて,先頭クラスタ番号から隣接するFATエントリにクラスタチェーンを作成していく(ラストは0FFFhとなることに注意)ことになるでしょうか。
ディスクに保存されたファイルが少なく,削除や更新を重ねていないファイルばかりなら楽でしょうが,そうでもなければあまり現実的な復元方法とは言えません。

参考文献
「応用MS-DOS 改訂新版 アスキーラーニングシステム③応用コース」(村瀬康治著・89/12/31・株式会社アスキー発行)
「MS-DOS6.2 プログラム開発ツールマニュアル」(NEC)拡張機能セットに付いてきた冊子。というか,あの構成要素はほとんど冊子だ。
「PC-9801/E/F/M ディスクシステム 解析マニュアル[第4巻]」(山内 直著・'85/01/15・秀和システムトレーディング発行)
「はじめて読む8086」(蒲池輝尚著・村瀬康治監修・'87/04/01・株式会社アスキー発行)

<<著作権に関して>>
本記事に引用している全てのソフトの名称・画像の著作権・その他権利は、制作、販売されたソフトハウス、メーカー、または作者様に帰属します。本サイトでの上記著作権物扱いは、著作権など各権利関係を侵害することが目的ではありません。問題などある場合は、メール(gekigangarあっとmail.goo.ne.jp)にてその旨お知らせください。