
Z80命令を逆アセンブルするOpenTypeフォント
あなたのお気に入りの逆アセンブラは何ですか?私はフォントです:
https://github.com/user-attachments/assets/bb6ceb18-c2fd-40a9-be4f-202321a214d9
このフォントは、16進小文字のシーケンスを逆アセンブルされたZ80命令に変換します。OpenTypeのGlyph Substitution Table (GSUB)とGlyph Positioning Table (GPOS)を広範囲に利用しています。
試してみたいだけなら、./test/z80-sans.ttf にコピーがあります。
Debian GNU/Linux 12 でテスト済み。このDebianバージョンにはrubyバージョン3が付属していますが、fontcustomはrubyバージョン2向けに書かれており、それ以降のバージョンとは互換性がありません(例:構文エラー)。また、rubyのインストールには互換性のあるOpenSSLのバージョンが必要です。そのため、RVMを使用してrubyとOpenSSLのローカルインストールの両方を管理できます。
apt install imagemagick potrace
pip install fonttools
git submodule update --init --recursive
# fontforge
(
cd ./modules/fontforge/
git checkout 4f4907d9541857b135bd0b361099e778325b4e28
git apply ../../resources/fontforge.diff
mkdir -p build
cd build
cmake -GNinja ..
ninja
ninja install
)
# woff2
(
cd ./modules/woff2/
make clean all
)
# fontcustom
rvm use 2.7
rvm pkg install openssl
rvm install 2.4 --with-openssl-dir=$HOME/.rvm/usr
gem update --system 3.3.22
(
export PATH=$PWD/modules/woff2/build:$PATH
cd ./modules/fontcustom/
git apply ../../resources/fontcustom.diff
gem build fontcustom.gemspec
gem install ./fontcustom-2.0.0.gem
)
cp ./resources/droid-sans-mono.ttf /tmp/base.ttf
./gen.py ./resources/instructions.json
.ttfフォントファイルは ~/.local/share/fonts/ にコピーされます。これは例えばLibreOfficeで使用されます。
他の呪われたフォントと比較して、Z80 Sansには以下の課題があります:
CALLのようなニーモニック)を作成できます。ただし、これは次の点にも関係します...65536 * 7 = 458752 の組み合わせが可能です。0x80..0xff の範囲のすべてのオフセットは、負の2の補数としてレンダリングする必要があります。これらすべてはプログラムによる解決策を必要とします。fontcustomとImageMagickがグリフの生成を担当する一方で、ルックアップルールを記述する便利な方法は.fea形式のようです。しかし、fonttoolsの.ttx形式(基本的にxml)と統合する方法が見つかりませんでした。そこで、Noto Sans Monoの.ttxを直接編集するという最も低レベルの共通分母アプローチを取りました(ただし、グリフ形状はDroid Sans Monoから計算されています。これはFontForgeにパッチを当て始めたときに使用していたものだからです)。
再帰下降パーサーを使用してすべての可能なグリフを生成します。これは、エンコーディング内の式を評価するのに役立ちます(例:SET b,(IX+o) はビットと変位を取り、DD CB o C6+8*b としてエンコードされます)。これらのエンコーディングは、オペランドが取り得るすべての値に展開され、最終的に、展開された命令をレンダリングするために必要な各逆アセンブルグリフに1つ以上の16進バイトが関連付けられます。
OpenTypeの機能に関する優れた参考文献はいくつかありますが、それらは高レベルで書かれているか、.fea形式です。
それらを.ttxに変換する方法はあまり明確ではないため、結局、Noto Sansファミリー全体を変換し、昔ながらの「例から学ぶ」というブルートフォースアプローチを使用しました。これは、.ttxから.ttfへの変換時に、fonttoolsで検証されないいくつかの前提条件(例えば、コンテキスト連鎖置換のクラス定義には、クラス値が "1" のカバレッジグリフが少なくとも1つ必要)により、多くの暗黙の失敗があるため、思った以上に楽しいものです。
ほとんどの課題は、コンテキスト連鎖ルールで解決されました。アドレスを処理するために、範囲 0..f 内の各ニブルは異なるグリフでエンコードされ、スペース文字を使用して複数の置換を一度に1文字ずつ作成しました。変位には追加の符号付きバリアントもあります。これにより、数字用のグリフは合計 (4 + 2) * 16 個になります。これだけで、フォントファイルを65536グリフの制限内に抑えるのに十分でした。
最悪だったのはもちろん、順序が入れ替わったオペランドです。しかし、これらは命令内でのバリエーションの数が限られているため、曖昧にエンコードされたプレフィックスを持つ命令と同じ戦略でカバーできます。例えば、
["SET b,(IX+o)", "DD CB o C6+8*b"],
["SET b,(IY+o)", "FD CB o C6+8*b"],
は、以下のルックアップルールと同じルールでカバーされます:
["SRA (IX+o)", "DD CB o 2E"],
["SRA (IY+o)", "FD CB o 2E"],
["SRL (IX+o)", "DD CB o 3E"],
["SRL (IY+o)", "FD CB o 3E"],
Z80 ISAの興味深い特性として、ビットとレジスタには最大8つのバリエーションがあり、これらの順序が入れ替わったケースにはオフセットとこれらの特定のオペランドの1つだけが含まれます。そのため、ビットやレジスタをリテラルとしてエンコードできます。十分な先読みがあれば、最後の16進バイトまで一致させ、各ケースに専用のルックアップを作成できます。最後のリテラルは、サフィックスグリフに一致する合字を生成することで削減できます。最終結果は、これらのケースに対して数十の追加の生成ルックアップになりました(これらはおそらくグループ化して数を減らすことができます)。
LD (IX+o),r は LD (IX+o r), とレンダリングされます。SET b,(IX+o) は SET b,(IX+o)) とレンダリングされます。FontForgeでは、コマンド GenerateFeatureFile() と MergeFeature() を使用したスクリプト可能な機能修正がサポートされています(The Terrible Secret of OpenType Glyph Substitution - Ansuz - mskala's home page で簡単に紹介されています)。私は.ttxベースの実装を行った後にこれに気付きましたが、.ttxファイルをいじる必要を避けることができたかもしれません。
より複雑な命令セットの場合、制約が少ない代替アプローチとして、フォントシェイパーを使用する方法があります。いくつかの例:
./resources/instructions.json は maziac/z80-instruction-set から改変しました。./resources/instructions.json は GNU Lesser General Public License version 3 の下にあります。