markfont.datは、/FONT内にあるdatファイル。
ゲーム内の特殊文字(アイコン)を格納している。ABXYLRボタン、十字ボタン、メールアイコン等・・・。
markfont.palと紐づいており、こちらはパレットファイルとなっている。
これらのアイコンはゲーム内文字列の"「M」から始まるタグ文字"が変換されることで表示される。
例えば、メッセージ0x08E9の場合
[item:0]を とうろくした! [M:B6]ボタンをおして[M:B7]ボタンで うてるぞ!
このうち「[item:0]」「[M:B6]」「[M:B7]」の3つがタグ文字。[item:0]はアイテム名が入るのでアイコンは使われず、Mから始まる残りの2つにアイコンが使われる。
[M:B6]にはLボタンのアイコン、[M:B7]にはRボタンのアイコンが表示される。ちなみに、Bは「Button」のB。
「仮」「未」「デ」と書かれているのは開発用に使われていたアイコン。ゲーム内では未使用部分含めて一切使用されていない、いわゆる没データ中の没データ。
それぞれ、仮は[M:T4]、未は[M:T5]、デは[M:T6]に割り当てられている。このTは「Text」のT。
仮はそのまんま「仮」の意味、未は「未使用」となんとなくわかる。デは分かりにくいがおそらく「デバッグ」のデだろう。
このようなアイコンが用意されているにもかかわらず、未使用・デバッグ系の文字には[M:D0]や[M:D1]という全く別のタグが付けられている・・・が、アイコンは割り当てられていないので何も表示されない。識別の用途?ちなみに、Dは「Debug」のDと思われる。
TCRFによると時闇のデバッグROMでは未使用の物にはちゃんと未が使われているらしい。
更に雑学を挟むと、海外版では[M:T0]、[M:T1]のアイコンが日本版と異なる。日本版では[M:T0]が「(鍵括弧)、[M:T1]が*(アスタリスク)だが、海外版の[M:T0]は:(コロン)、[M:T1]は💭のような吹き出しのアイコンが表示される。
staffont
staffont.datはスタッフクレジットに使われるフォントファイル。staffont.palがパレットデータ。
markfontと全く同じ構造で同じ方法で展開可能。
目立った没データは無いが、使われていないアルファベットぐらいは探せば見つかるかもしれない。(探していない)
名前が「stafffont」ではないのは、fが多すぎて冗長だから??
構造 (markfont.dat, staffont.dat)
datファイルの構造は以下の通り。
0x000~0x19F | エントリ情報テーブル |
0x200以降 | アイコンデータ群(8bpp) |
エントリ情報テーブルは4バイト毎に各アイコンの幅・高さ・開始アドレスが格納されている。
アイコンに関しては、このゲームでは珍しい8bppが使われている。他の画像データのほとんどは4bpp。
よってパレットは16色ではなく256色必要。後述のパレットデータ(pal)には128色分のデータしかないが、アイコンデータを覗くと0x81以上や0x00のバイトで敷き詰められている為、0x80=128色まで不要な色でパレットを埋めると数が合うようになる。
エントリ
エントリの構造は以下の通り。長さは4バイト。
0x00 | byte | アイコンの幅 |
0x01 | byte | アイコンの高さ |
0x02 | int16 | アイコンデータ開始アドレス |
幅・高さ共に0x00、アドレスが0xFFFFのエントリは予約領域として残っている無効なエントリ。プログラムで処理する場合はこの部分をスキップする必要がある。
開始アドレスはmarkfont.dat上のアドレスを示す。例えば、一番最初の「16 0B 00 02」は幅22px、高さ11pxの画像でアドレスは0x200から始まる。アドレス範囲の終端は次のエントリorファイルの終端。
構造 (markfont.pal, staffont.pal)
palファイル(パレットデータ)の構造は以下の通り。すべて4バイトのエントリで構成。
0x00 | byte | R値 |
0x01 | byte | G値 |
0x02 | byte | B値 |
0x03 | byte | 未使用 |
RGB値で構成される色データが格納されている。各エントリ内の最後のバイトは常に0x00か0xFFだが、無視して良い。
128エントリ=128色分の色が格納されているが、ここで使われるフォーマットは8bppなので256色必要。足りない分は先程の通り不要な色で128色分埋めて、以降にmarkfont.palの128色を足すことで256色のパレットが構成される。
なお、不要な色はゲーム内では「透明色」として表示される。
展開するプログラム (C#)
MarkFont.cs
using System; using System.Collections.Generic; using System.Drawing; public class MarkFont { /// <summary>エントリのバイト長</summary> public const int ENTRY_BYTE = 4; /// <summary>エントリデータ</summary> public List<MarkFontEntry> Fonts = new List<MarkFontEntry>(); /// <summary>パレット</summary> public List<Color> Palette = new List<Color>(); /// <summary> /// MarkFont /// </summary> /// <param name="dat">markfont.dat</param> /// <param name="pal">markfont.pal</param> public MarkFont(byte[] dat, byte[] pal) { // パレット取得 for (int i = 0; i < 0x80; i++) Palette.Add(Color.Transparent); for (int i = 0; i < pal.Length; i += 4) Palette.Add(Color.FromArgb(pal[i + 0], pal[i + 1], pal[i + 2])); // フォントデータ取得 for (int i = 0; i < BitConverter.ToInt16(dat, 0x2); i += ENTRY_BYTE) { int width = dat[i + 0]; int height = dat[i + 1]; int offset = BitConverter.ToInt16(dat, i + 2); if (width > 0 && height > 0) { byte[] data = dat[offset..(offset + width * height)]; Fonts.Add(new MarkFontEntry(width, height, data)); } } } }
MarkFontEntry.cs
using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; public class MarkFontEntry { /// <summary>幅</summary> public int Width { get; set; } /// <summary>高さ</summary> public int Height { get; set; } /// <summary>アイコンデータ</summary> public byte[] Data { get; set; } public MarkFontEntry(int width, int height, byte[] data) { Width = width; Height = height; Data = data; } /// <summary> /// Bitmapとして生成 /// </summary> /// <param name="palette">パレットデータ</param> /// <returns></returns> public Bitmap GenerateBitmap(List<Color> palette) { Bitmap res = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); BitmapData bmp = res.LockBits( new Rectangle(0, 0, res.Width, res.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); for (int i = 0; i < Data.Length; i++) Marshal.WriteInt32(bmp.Scan0, i * 4, palette[Data[i]].ToArgb()); res.UnlockBits(bmp); return res; } }
MarkFontクラスにmarkfont.datとmarkfont.palをbyte配列で持たせて、生成されたList内のMarkFontEntryクラスのGenerateBitmapにパレット持たせて実行すればBitmapに変換されます。
Marshal.Copyでささっと8bppのまま出力したかったのに何故か座標がずれてうまくいかないので、32bppにしてMarshal.WriteInt32で色をポチポチ置いていくことでなんとかしました。
もっと良い方法がありそうだけど技術不足なので今はこれでいいかな…。
参考
skytemple-files (graphics/fonts/graphic_font)
markfont.datのファイル構造が全く分からなかったので超参考にした。
コードはpythonで書かれており、私自身もpythonは少しわかるのでC#化は簡単だったが、画像フォーマットをずっと4bppだと思い込んでいて一度詰んだ。
ソースコードとにらめっこし続けていると画像生成処理にImage.frombytesを使っているっぽいのがわかったのでググって出てきたPILのリファレンスを参照したところ、modeを"P"に指定していると8ビット(8bpp)で生成すると書かれていたのでここでようやく問題解決。
「空の探検隊の画像は全部4bpp」という固定観念に囚われてはいけない。
github.com
Pillow (PIL Fork) 10.0.0 documentation
さっき出てきたPILのリファレンス。pythonひよっこなので世界が広がった。
pillow.readthedocs.io
ExplorerScript Boundary Test
海外製の空の探検隊ROMハックツール「SkyTemple」と一緒に開発されたテキストテストツール。
文字タグに対応するアイコンについて参考にした。
textbox.skytemple.org