Exomizer は圧縮率で ZIP をも凌ぐ大変優れたソリューションです。欠点としては解凍速度が遅いことですが、絶対的な圧縮が欲しい時は、これの選択が唯一無二とまで思っています。例えば、データロードの時間を短くする時も、Exomizer を使って圧縮すれば、その後数秒の展開時間を待つだけで、場合によっては何分も短縮することが出来ます。
ここまでが実装の前段階というか準備となります。
続いて動作の実装についてとなります。
※お米不足って言うけど、独身者ならこういうのを食べてるからあまり関係ないと思うんですよね。あと一ヶ月もすれば解消する見込みとも言うし。
Exomizer は 2024/08/18 現在は、バージョン 3.1.2 が公開されています。
検索すると嘘 URL がヒットしますのでご注意ください。上記リンクは正しい URL となります。ダウンロードすると圧縮ツールや Z80 等の解凍処理アセンブラソースが同梱されてたりします。が、残念ながら Z80 のソースは LGPL 2.1 で、ちょっと使うのは大変困るライセンスとなっています。Z80 のソースは大変使いやすい zlib license でuniabis さんが公開されていますので、こちらをご使用ください。
- C# で解凍手段がない
自分が現在開発中の XeGrader というゲームでも、Exomizer は大活躍しています。全部で 4段の多段ロード形式となっていますが、数カ所 Exomizer で圧縮を掛けています。おかげでかなりのロード時間短縮を実現しているのですが、いざ、このプログラムを解析しようとして困ってしまいました。C# でのデコードルーチンが見つからないのです。
ただ、Z80 では解凍ルーチンが存在しているわけで、だったらこれをそのまま C# で動作させてしまえば良いんじゃないかと。つまりは Z80 のザイログコードのまま、C# で実行するシミュレート処理を作れば良いかと。幸いなことに Z80 の動作に関してはある程度熟知していますし、Z80 の動作に関しては仕様が 100% 明確になっています。Exomizer のデコードアルゴリズムが分からない以上、分かる側から攻めるのが移植のスジです。
ということで、uniabis さんの Z80 処理を元に C# に移植する作業に着手しました。これ、当初の予想より案外大変でした。どうやって Z80 っぽい動作を実現したのか、これから説明していきます。
- Z80 環境の再現
Z80 のプログラムを動かすために必要な事を列挙してみます。
- 64KB メモリ空間
- Z80 レジスタ動作
- スタック
メモリ空間に関しては、byte[] 配列で 65536 サイズで確保してしまえば終了です。現在の PC 環境からは 64KB など、ゴミのようなサイズです。なんぼでも確保できます。Z80 のレジスタに関しては Acc アキュムレータ、HL,DE,BC ペアレジスタ、IX,IY インデックスレジスタ、SP スタックポインタ等があります。扱いを簡単にするために、以下のクラスを用意しました。
■ Accumulator
アキュムレーターの再現に使用します。8bit の値と、CF, ZF をプロパティとして用意しました。
public byte Value = 0; public bool CF = false; public bool ZF = false;
本来であれば、F レジスタとして、8bit も別途に用意スべきですが、Exomizer の Z80 デコード処理では CF と ZF しか使われていなかったことと、PUSH AF / POP HL のような F をまともて取り出す処理もなかったので、CF と ZF は bool で用意しています。
※ ビールみたいでビールじゃない、気分はビールで健康志向。それがホッピー。これならちょっと飲んでもいいかなーって思っちゃいます。■ Register
HL 等のペアレジスタの再現に使用します。
16bit の値のみをプロパティとして用意しました。
public ushort Word = 0;
上位8bitと下位8bitはメソッドで読み書きするようにしました。
public byte Low { get { return (byte)(Word % 256); } set { Word = (ushort)(Word / 256 * 256 + value); } } public byte High { get { return (byte)(Word / 256); } set { Word = (ushort)(Word % 256 + value * 256); } }
■ IndexReg
IX,IYのインデックスレジスタの再現に使用します。プロパティは 16bit 値のみです。
public ushort Word = 0;
機能的には Register を継承しても良かったのですが、使われ方がものすごく限定的でしたので、今回は独立したクラスとして作っています。IYL 等の未定義命令を駆使している等、割と多機能に使われているのなら、継承スべきだと思います。
スタックに関しては SP を直接弄る命令が殆どなかったため、C# の Stack をそのまま活用しています。
- レジスタのインスタンス
Z80 ソースコード内で EQU で定義された内容を、そのまま記述しています。exo_mapbasebits は、キーテーブル展開バッファの位置です。サイズは 156 バイトなので、64KB 空間で最も当たり障りのない最後尾の位置に設定してあります。続いてメモリ空間
private const int tbl_bytes = 16 + 16 + 16 + 4; private const int tbl_shift = 41 - tbl_bytes - tbl_bytes; private const int tbl_ofs_bits = tbl_shift; private const int tbl_ofs_lo = tbl_shift + tbl_bytes; private const int tbl_ofs_hi = tbl_shift + tbl_bytes + tbl_bytes; private const ushort exo_mapbasebits = 0xFF00;
書き忘れてましたが、Exomizer デコード処理のクラスは静的としています。理由はいちいちインスタンスして呼び出すのが面倒解凍処理を複数インスタンスする意味はないためです。次にレジスタです。
private static readonly byte[] mem = new byte[65536];
private static readonly Accumulator A = new(); private static readonly Register HL = new(); private static readonly Register DE = new(); private static readonly Register BC = new(); private static readonly Accumulator _A = new(); private static readonly Register _HL = new(); private static readonly Register _DE = new(); private static readonly Register _BC = new(); private static readonly IndexReg IY = new(); private static readonly Stack<ushort> stack = new();
IX は未使用なので記述していません。アンダースコア( _ )が付いているのが裏レジスタです。裏レジスタは直接使えないバックアップレジスタなので、単純に 16bit の ushort で用意するだけでも良かったのですが、レジスタであると分かり易いするために裏も同一のクラスでインスタンスしています。
さて、このままだと、8bitレジスタが使いづらいので別名定義してしまいます。あと、アキュムレーターもついでに簡単に扱えるようにしてしまいます。
HL の記述がないのは Exomizer デコードでは使用していないためです。このような定義を書いておければ、デコードルーチン中は、特に上位下位を意識することなく、淡々とコードを置き換えていくだけとなります。
private static byte Acc { set { A.Value = value; } get { return A.Value; } } private static byte D { set { DE.High = value; } get { return DE.High; } } private static byte E { set { DE.Low = value; } get { return DE.Low; } } private static byte B { set { BC.High = value; } get { return BC.High; } } private static byte C { set { BC.Low = value; } get { return BC.Low; } }
ここまでが実装の前段階というか準備となります。
続いて動作の実装についてとなります。
※お米不足って言うけど、独身者ならこういうのを食べてるからあまり関係ないと思うんですよね。あと一ヶ月もすれば解消する見込みとも言うし。
コメント