前提
あらまし
Minecraftのサーバーを動かしていて、Ubuntuのコマンドライン上でMinecraftのセーブデータを解析したい需要が生じたので、Minecraftのセーブデータのフォーマットと、それをJSON文字列化する方法を調べた。その結果、期待したようなものは得られなかったため、以下のNBTとJSONを相互に変換するプログラムをPerlで作った。
- MirrgieRiana / nbt_to_json_perl https://github.com/MirrgieRiana/nbt_to_json_perl
背景
Minecraftのサーバーを動かしていて、Ubuntuのコマンドライン上でMinecraftのセーブデータを解析したい需要が生じた。
NBT
Minecraftのゲームデータは、NBTフォーマットで記述されている。これは値に型がついたバイナリ形式のJSON的なものである。セーブデータはほとんどがこのフォーマットで書かれている。
詳しい説明はWikiに任せる。
- NBTフォーマット - Minecraft Wiki https://minecraft-ja.gamepedia.com/NBT%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88
既存のプログラム
NBTExplorer
NBTExplorerはぱっと見LinuxのCLI上でJSON化できるツールではなさそうだった。
Windows用のGUIのNBT編集ソフトとしてはとても素晴らしかったが、今回はGUIのないUbuntuを使うので使用できない。
NBTUtilみたいな名前のツールが同封されており、これはWindowsのコマンドプロンプト(Bashからも可能)からならコマンドでNBTを独特のフォーマットに文字列化できる。JSONにもできるかもしれないが、Windows環境が求められたので無視した。
nbt2json(midnightfreddie)
- GitHub - midnightfreddie/nbt2json https://github.com/midnightfreddie/nbt2json
Goで書かれたツールがあった。
こちらは入れようとしたけどGoの使い方が分からず、Go入門するくらいなら劣化版を自分で作った方が早そうだったので無視した。
prismarine-nbt
- PrismarineJS /
prismarine-nbt https://github.com/PrismarineJS/prismarine-nbt
node用のモジュールらしい。かなりまともみたいなので本記事で作ったものよりよさそう。
JSON-to-NBT-Converter(KSashaDF)
- KSashaDF /
JSON-to-NBT-Converter https://github.com/KSashaDF/JSON-to-NBT-Converter
2020年2月27日現在imperfectらしく良い出来ではなさそうだった。
成果物
以下のプログラムを作った。
- MirrgieRiana / nbt_to_json_perl https://github.com/MirrgieRiana/nbt_to_json_perl
元々はここに使用例などを記載する予定だったが、結局READMEに色々書いたのでここでは軽く触れるだけに留める。
NBTバイナリのJSON化
nbt2json
を使うとNBTバイナリをJSONにできる。ただし、Minecraftのセーブデータは大体gzip圧縮されていることに気を付けなければならない。nbt2json
は非圧縮のNBTバイナリデータを受け付ける。
- $
cat sample/1.nbt | ./nbt2json
{"key":"","type":10,"value":[{"key":"list","type":9,"value":{"type":1,"values":[1,2]}},{"key":"double","type":6,"value":"0x3fd0000000000000=0.25"},{"key":"int","type":3,"value":-1}]}
便利化と整形
-cp
を付けると便利で整形された出力になる。
-c
が便利化、p
が整形である。-c
を付けるとCompoundタグはJSONオブジェクトになるが、タグの順序は保持されないことに気を付けなければならない。
- $
cat sample/1.nbt | ./nbt2json -cp
{
"C": {
"Alist": {
"type": "byte",
"values": [
1,
2
]
},
"Ddouble": "0x3fd0000000000000=0.25",
"Iint": -1
}
}
逆変換
json2nbt -c
はnbt2json -cp
の逆関数で、JSONを呼んでNBTを吐く。
-c
はCompoundタグの内部のタグの順序が崩れること以外は大体元のNBTを返す。
nbt2json
にはいろいろなオプションがあるが、いくつかのオプションはjson2nbt
にそのまま食わせても読んでくれる。
浮動小数では、NaNや非正規数など複数のバイナリが一つの文字列表現になることがある。
これをjson2nbt
で逆変換すると恒等にならないため、浮動小数は16進文字列と10進表現ペアとして出力される。
nbt2json -H
やnbt2json -B
を使うとすべての数値が16進文字列や2進文字列に強制的になる。
json2nbt
には-H
や-B
のオプションはないが、付けなくても受け付けてくれる。
- $
cat sample/1.nbt | ./nbt2json -cp | ./json2nbt -c | ./nbt2json -cp
{
"C": {
"Alist": {
"type": "byte",
"values": [
1,
2
]
},
"Ddouble": "0x3fd0000000000000=0.25",
"Iint": -1
}
}
-e
オプションの使用例
level.dat
を読んでワールドのSeed値を得たい場合、jqとかを使うよりも次のように直接perlの変数を書き換えるのがよい。
- $
cat level.dat | inflate-gzip | nbt2json -cp -e '$_ = $_->{C}->{CData}->{LRandomSeed}'
7546217594578327695
jsonをすべて吐いてからjqを使う場合、データの変換のオーバーヘッドがかかるのはともかくとして、jsonは64ビット整数の扱いに難がある。
- $
cat level.dat | inflate-gzip | nbt2json -cp | jq '.C.CData.LRandomSeed'
7546217594578328000
そのような場合は-H
オプションで16進文字列にして出力するとよい。
- $
cat level.dat | inflate-gzip | nbt2json -cpH | jq '.C.CData.LRandomSeed'
"0x6c443070ec80dfb0"