松山事務所の石丸です。

みなさんブランチマイニングしてますか?
今日はマインクラフトのセーブデータなどで使用されている、NBTというフォーマットについてまとめてみました。

NBTフォーマットについて

NBTとはNamed Binary Tagの略で、XMLより小さいサイズで大きなデータを表現できるように、マインクラフトの開発者によって設計されたフォーマットだそうです。
マインクラフトのセーブデータのほとんどはこのフォーマットで記録されています。
バイナリ形式のXMLのようなもので、ファイルをパースしていくとクラスの定義が見えてきます。

タグの種類

Ver1.11の時点でデータ、データ構造を表すタグの種類は次の12種類になります。

タグタイプ タグ名 説明
0 TAG_End 終了タグ
1 TAG_Byte 1バイト(8bit)の整数
2 TAG_Short 2バイト(16bit)の整数
3 TAG_Int 4バイト(32bit)の整数
4 TAG_Long 8バイト(64bit)の整数
5 TAG_Float 4バイト(32bit)の浮動小数
6 TAG_Double 8バイト(64bit)の浮動小数
7 TAG_Byte_Array 1バイト(8bit)整数の配列
8 TAG_String UTF-8の文字列
9 TAG_List タグのリスト
10 TAG_Compound 開始タグ
11 TAG_Int_Array 4バイト(32bit)整数の配列

TAG_End、TAG_List、TAG_Compoundはデータ構造を表現するタグ、それ以外が値を表現するタグに分類できます。
TAG_Compoundはクラス(構造体)で、TAG_Listはジェネリックリストだと思えばわかりやすいかもしれません。

簡単なデータ構造の例

簡単なデータ構造をNBTフォーマットで表現してみます。

struct Foo
{
  struct Bar
  {
    int id = 0;
    string name = "";
  }
  list bars[2];
}
Foo foo;

このようなデータ構造の値fooをNBTフォーマットにすると、TAGは次のような構造になります。

TAG_Compound("foo")
  TAG_List("bars")
    TAG_Compound
        TAG_Int("id")
        TAG_String("name")
    TAG_End
    TAG_Compound
        TAG_Int("id")
        TAG_String("name")
    TAG_End
TAG_End

TAG_End以外は名前(変数名)も保持しています。
TAG_List直下のタグ(3,7行目のTAG_Compound)は、配列の要素を表しているため名前(変数名)がありません。

タグの詳細

各タグの仕様をもっと細かく見ていきます。
すべてのタグは次のようなNamedTagフォーマットになっています。

NamedTagフォーマット

名前 バイト数 備考
タグタイプ byte 1 表1のタグタイプ0〜11
タグ名のバイト数 short 2バイト
タグ名 string 可変 UTF-8文字列
データ部 * * 以下に記載

タグの種別を表すタグタイプとタグの名前に続いて、各タグ毎のデータ部が続きます。
いくつかのタグのフォーマットを確認していきましょう。

TAG_Compound、TAG_End

TAG_Compoundのデータ部には、内包されたタグ(データ)が続きます。
またタグ名は空文字の場合があります。
TAG_Endにはタグ名、データ部がありません。

TAG_String

名前 バイト数 備考
文字列長 2 文字列ののバイト数
文字列長 UTF-8文字列

TAG_Stringは、名前の通り文字列データを表します。
可変長なので、文字列長(バイト数)の後に、UTF-8の文字列データが続きます。

値型タグ

タグ名 バイト数 備考
TAG_Byte 1
TAG_Short 2
TAG_Int 4
TAG_Long 8
TAG_Float 4 IEEE 754-2008
TAG_Double 8 IEEE 754-2008

上記のように値型タグのデータ部は、タグ名が表す通りのデータになります。
例えばTAG_Intは次のようになります。

TAG_ByteArray

名前 バイト数 備考
項目数 4
1バイト*項目数

TAG_ByteArrayは、Byte型配列です。
項目数の後ろに1バイトの値が項目数分続きます。

TAG_IntArray

名前 バイト数 備考
項目数 4
4バイト*項目数

TAG_IntArrayはInt型配列です。
項目数の後ろに4バイトの値が項目数分続きます。

TAG_List

名前 バイト数 備考
タグタイプ 1 表1のタグタイプ0〜11
項目数 4

TAG_Listは、ジェネリックリストです。
ジェネリックなので、ByteArrayやIntArrayにはなかったタグタイプの指定があります。TAG_Listの終わりを表すタグありません。
ここにTAG_Compoundを指定すれば構造体の配列も表せます。
タグタイプ2(Short)を指定してShortArrayを表現すると次のようになります。

実ファイルをパースする際に気を付けること

GZip解凍する必要があります

セーブデータファイルのほとんどはGZipで圧縮されているため、解凍してからパースする必要があります。
JavaだとGZIPInputStreamで簡単なのですが、C++だと標準ライブラリにそんなのないので、boostライブラリを使うなど一手間かかります。

エンディアンの変換が必要かもしれません

マインクラフトのデータはビッグエンディアンで記録されています。
リトルエンディアンな環境で扱うときは変換が必要です。
float、doubleもint、longと同じようにひっくり返せば大丈夫っぽいです。

これらのことに注意しながら、ファイルの先頭のNamedTagから順番に読み込んでいけばOKです。

参考資料

minecraft wikiのNBT Format
http://minecraft.gamepedia.com/NBT_format

マインクラフトの開発者本人による仕様書(昔は公式サイトにアップされてたらしい)
http://web.archive.org/web/20110723210920/http://www.minecraft.net/docs/NBT.txt

TOP
TOP