RPGツクールと数学のブログ

RPGツクールと数学についてのブログです。

RPGツクール2000の可変長データ構造(BER圧縮)

過去ブログの転載です。

RPGツクール2000の内部で使用されている整数値は、各ファイルのバイナリデータとして基本的に「BER圧縮」という可変長データ構造で保存されています。これはlong型(-2147483648~2147483647)の整数を圧縮するはたらきがあり、簡単な数ほど使用されるバイト数が小さくて済むという特徴があります。固定長のlongは4バイトですが、BER圧縮は1~5バイトで可変です。

例えばふつうに「128」を表すとすると(ビッグエンディアンだと)、固定長のlong型では[00 00 00 80]の4バイトが必要で、頭の00がどう考えても余計です。これをBER圧縮した形式では、[81 00]の2バイトで済みます。「12」とかを表すときはもっと小さくなり、[0C]の1バイトになります。数字が小さいほどサイズが小さくなるわけです。

RPGツクール2000のように、基本的に小さな整数を使うけれどたまに巨大な数を使うかもしれないというときにはこの圧縮方法は確かに有効と思われます。ただツクール2000ではマイナス値については細工されていないのか、マイナス値だと逆にサイズがでかくなってしまいます。(-50をBER圧縮すると[8F FF FF FF 4E]になってしまう)この仕様には疑問を抱きますけど、この際どうでもいいです。

BER圧縮された数値は、最後の桁が16進数で80未満(10進数だと128未満)というルールが決まっています。最後の桁以外は16進数で80以上になります。このルールに従って何バイトから何バイトが一つの整数を表すのかがはっきりします。

BER圧縮のもどしかた

このようにして復元します。

例:[92 A7 60]

1バイト目から見ていきます。92です。80以上ですから、最後の桁ではないと分かります。次いで、92-80=12とします。これは16進数なので10進数に直すと、18になります。

2バイト目はA7です。これも80以上なので、最後の桁ではありません。A7-80=27で、10進数に直すと、39になります。

3バイト目は60です。これは80未満です。よってこれが最後の桁です。これは80引きません。60を10進数に直すと、96になります。

18:39:96

これは128進数ですね。128進数を10進数に直すには、

 18\times 128^{2}+39\times 128+96=300000
とすればよいです。よって、[92 A7 60]は300000という意味でした。

例:[8F FF FF FF 7F]

同じ要領でやってみます。
1バイト目は8Fなので、最後の桁ではありません。
8F-80=0Fで、10進数に直すと15です。
2バイト目はFFなので、最後の桁ではありません。
FF-80=7Fで、10進数に直すと127です。
3バイト目はFFなので、最後の桁ではありません。
FF-80=7Fで、10進数に直すと127です。
4バイト目はFFなので、最後の桁ではありません。
FF-80=7Fで、10進数に直すと127です。
5バイト目は7Fなので、最後の桁です。
4Eは、10進数に直すと127です。

15:127:127:127:127

これは128進数なので、10進数に変換すると、

 15\times128^{4}+127\times128^{3}+127\times128^{2}+127\times128+127=4294967295
15×128^4 + 127×128^3 + 127×128^2 + 127×128 + 127
=4294967295 となります。

ただしツクール2000ではBER圧縮の型がsigned longなので、-2147483648~2147483647の範囲に収まるように4294967296が足し引きされます。

4294967295-4294967296=-1

よってこれは-1を表します。

実践問題

BER圧縮された整数が上記の方法で読めるので、実践問題としてイベント命令のバイナリを読み解いてみましょう。マシン語みたいですね^^!

[DD 6A 00 00 06 01 63 00 8F FF FF FF 7F 00 00]

↑あるイベントの命令一行を表します。

初めにあるのはBER圧縮された数値です。これはイベントコードを表します。

1バイト目はDDで、80以上なので最後の桁ではありません。
DD-80=5Dで、10進数に直すと93になります。
2バイト目は6Aで、80未満なのでこれが最後の桁です。
6Aは、10進数に直すと106になります。
93:106 を128進数から10進数に直すと、 93 \times 128 + 106 = 12010 となります。

この命令はイベントコード12010のもの、という意味です。以前はそれぞれのコードについて詳しく書かれたサイト様があったのですが、当時参考にしていたサイトは閉鎖されてしまっており、今はどう確認すればいいかよくわかりません……とりあえず、イベントコード12010に相当するのは「条件分岐」ですので、初めの[DD 6A]は「◆条件分岐:」という意味になります。

[(◆条件分岐:)00 00 06 01 63 00 8F FF FF FF 7F 00 00]

次にも数値が来て、これはネスト番号を表します。00ですね。条件分岐の中に条件分岐を入れていったりすると、イベント命令の画面ではインデントされていきますよね。その番号です。(1重ネストだったら01、2重ネストだったら02、……となっていきます)ここが00なので、ネストされてないということです。普通に置かれているということです。

[(◆条件分岐:)00 06 01 63 00 8F FF FF FF 7F 00 00]

次は通常、[00]が来ます。もし条件分岐で主人公の名前が****というものを設定していたら、その文字列がこの間に入ります。00は文字数を表します。(「あ(82 A0)」だったら、[02 82 A0])今回は[00]なので、名前の文字列はないということです。

次の[06]とは、あと6個パラメータが来るよ、という意味です。
[01][63][00][8F FF FF FF 7F][00][00]の6個ですね。

[(◆条件分岐:)01 63 00 8F FF FF FF 7F 00 00]

次は、条件分岐の種類です。0番だとスイッチ、1番だと変数、2番だとタイマー……といったように決まっています。01ということで、1番の「変数」を扱う条件分岐だと分かります。

[(◆条件分岐:変数)63 00 8F FF FF FF 7F 00 00]

次は何番の変数か、の番号が来ます。数値はBER圧縮されていますが、1バイト目が63で80未満なので、63を10進数に直して99番の変数とすぐ分かります。

[(◆条件分岐:変数[0099:]が)00 8F FF FF FF 7F 00 00]

次は何と比較するかが入ります。00だと定数と比較、01だと変数と比較です。00なので定数と比較となります。

[(◆条件分岐:変数[0099:]が)8F FF FF FF 7F 00 00]

次はその値です。[8F FF FF FF 7F]はBER圧縮されていますが、さっきもあったとおり-1ですので、これは-1です。

[(◆条件分岐:変数[0099:]が-1)00 00]

次は00なら同値、01なら以上、02なら以下、……などを意味します。00なので同値です。

[(◆条件分岐:変数[0099:]が-1)00]

最後の00は区切り。これでおしまいです。

◆条件分岐:変数[0099:]が-1

を意味するのでした。

実際条件分岐を作ると3行ぐらい一気に出ますが、その1行目だけを表すときはこれでOKです。