
Fonte OpenType que desmonta instruções Z80
Qual é o seu desassemblador favorito? O meu é uma fonte:
https://github.com/user-attachments/assets/bb6ceb18-c2fd-40a9-be4f-202321a214d9
Esta fonte converte sequências de caracteres hexadecimais minúsculos em instruções Z80 desassembladas, fazendo uso extensivo da Tabela de Substituição de Glifos (GSUB) e da Tabela de Posicionamento de Glifos (GPOS) do OpenType.
Se quiser apenas testá-la, uma cópia está disponível em ./test/z80-sans.ttf.
Testado no Debian GNU/Linux 12. Note que esta versão do Debian vem com Ruby versão 3, enquanto o fontcustom foi escrito para Ruby versão 2 e é incompatível com versões posteriores (por exemplo, erros de sintaxe). Uma instalação do Ruby também requer uma versão compatível do OpenSSL. Portanto, o RVM pode ser usado para gerir tanto o Ruby quanto uma instalação local do 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
O ficheiro de fonte .ttf é copiado para ~/.local/share/fonts/, que é usado por exemplo pelo LibreOffice.
Comparada a outras fontes amaldiçoadas, a Z80 Sans tem estes desafios:
CALL), no entanto isto também se liga ao próximo ponto...65536 * 7 = 458752 combinações possíveis;0x80..0xff precisam de ser renderizados como um número negativo em complemento para dois;Tudo isto convida a uma solução programática. Enquanto o fontcustom e o ImageMagick cuidam da geração de glifos, parece que uma forma conveniente de escrever regras de pesquisa é o formato .fea, mas não encontrei uma forma de o integrar com o formato .ttx do fonttools (que é basicamente xml). Adotei a abordagem do menor denominador comum, editando diretamente o .ttx do Noto Sans Mono (embora as formas dos glifos sejam calculadas a partir do Droid Sans Mono, pois foi com ele que comecei ao corrigir o FontForge).
Um analisador sintático descendente recursivo é usado para gerar todos os glifos possíveis, o que ajuda a avaliar expressões em codificações (ex.: SET b,(IX+o) recebe um bit e um deslocamento, codificado como expressão DD CB o C6+8*b). Estas codificações foram então expandidas para todos os valores possíveis que os operandos podem tomar, antes de finalmente associar 1 ou mais bytes hexadecimais a cada glifo de desassemblagem necessário para renderizar uma instrução expandida.
Existem algumas referências úteis para funcionalidades OpenType, mas estão escritas a um nível elevado, ou em formato .fea(?):
Nunca é muito claro como traduzi-los para .ttx, então no final simplesmente converti toda a família Noto Sans e usei a boa e velha abordagem de "aprender por exemplos". Isto é ainda mais divertido do que parece, graças às inúmeras falhas silenciosas ao converter de .ttx para .ttf, onde as pesquisas não correspondem devido a alguns pressupostos não validados pelo fonttools (ex.: definições de classe para substituições de encadeamento contextual devem ter pelo menos um glifo de cobertura com class value='1').
Praticamente a maioria dos desafios foi resolvida com regras de encadeamento contextual. Para lidar com endereços, cada nibble no intervalo 0..f foi codificado com glifos distintos, usando caracteres de espaçamento para criar múltiplas substituições, um caractere de cada vez. Deslocamentos também têm variantes com sinal adicionais. Isto dá-nos um total de (4 + 2) * 16 glifos para números. Isto já foi suficiente para manter o ficheiro de fonte abaixo do limite de 65536 glifos.
A pior parte foi, claro, os operandos fora de ordem. No entanto, devido ao número limitado de variações que estes têm nas instruções, puderam ser cobertos pela mesma estratégia que as instruções com prefixos codificados de forma ambígua, ex.:
["SET b,(IX+o)", "DD CB o C6+8*b"],
["SET b,(IY+o)", "FD CB o C6+8*b"],
São cobertos pelas mesmas regras de pesquisa que:
["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"],
Uma propriedade interessante na ISA Z80 é que bits e registos têm até 8 variações, e estes casos fora de ordem apenas envolvem deslocamentos e um desses operandos específicos. Portanto, podemos codificar bits ou registos como literais. Com antecipações suficientes, podemos corresponder até ao último byte hexadecimal e criar pesquisas dedicadas para cada caso. Os últimos literais podem ser reduzidos gerando uma ligadura que corresponda ao glifo do sufixo. O resultado final foram dezenas de pesquisas geradas adicionais para estes casos (que provavelmente podem ser agrupadas para reduzir este número).
LD (IX+o),r é renderizado como LD (IX+o r),;SET b,(IX+o) é renderizado como SET b,(IX+o));O FontForge suporta modificação scriptada de funcionalidades usando os comandos GenerateFeatureFile() e MergeFeature() (brevemente abordados em The Terrible Secret of OpenType Glyph Substitution - Ansuz - mskala's home page). Só tomei conhecimento disto depois de fazer a implementação baseada em .ttx, mas poderia potencialmente ter evitado a confusão com ficheiros .ttx.
Para conjuntos de instruções mais complexos, uma abordagem alternativa que parece ter menos restrições é usar modeladores de fonte (font shapers). Alguns exemplos:
./resources/instructions.json foi adaptado de maziac/z80-instruction-set;./resources/instructions.json está sob GNU Lesser General Public License versão 3;