松山事務所の石丸です。
みなさんブランチマイニングしてますか?
今日はマインクラフトのセーブデータなどで使用されている、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