Skip to content

Instantly share code, notes, and snippets.

@bemasher
Created July 10, 2012 12:51
Show Gist options
  • Save bemasher/3083068 to your computer and use it in GitHub Desktop.
Save bemasher/3083068 to your computer and use it in GitHub Desktop.
Named Binary Tag parser written in Golang using reflection to populate native types.
// This is the structure of the binary nbt file
TAG_Compound('Level') {
TAG_Compound('nested compound test') {
TAG_Compound('egg') {
TAG_String('name'): 'Eggbert'
TAG_Float('value'): 0.5
}
TAG_Compound('ham') {
TAG_String('name'): 'Hampus'
TAG_Float('value'): 0.75
}
}
TAG_Int('intTest'): 2147483647
TAG_Byte('byteTest'): 127
TAG_String('stringTest'): 'HELLO WORLD THIS IS A TEST STRING \xc3\x85\xc3\x84\xc3\x96!'
TAG_List('listTest (long)') {
TAG_Long(None): 11
TAG_Long(None): 12
TAG_Long(None): 13
TAG_Long(None): 14
TAG_Long(None): 15
}
TAG_Double('doubleTest'): 0.49312871321823148
TAG_Float('floatTest'): 0.49823147058486938
TAG_Long('longTest'): 9223372036854775807L
TAG_List('listTest (compound)') {
TAG_Compound(None) {
TAG_Long('created-on'): 1264099775885L
TAG_String('name'): 'Compound tag #0'
}
TAG_Compound(None) {
TAG_Long('created-on'): 1264099775885L
TAG_String('name'): 'Compound tag #1'
}
}
TAG_Byte_Array('byteArrayTest')
TAG_Short('shortTest'): 32767
}
package main
import (
"io"
"os"
"fmt"
"math"
"reflect"
"strings"
"errhandler"
"compress/gzip"
"encoding/json"
"encoding/binary"
)
const (
NBTFILE = "bigtest.nbt"
)
const (
TAG_END = iota
TAG_BYTE
TAG_SHORT
TAG_INT
TAG_LONG
TAG_FLOAT
TAG_DOUBLE
TAG_BYTE_ARRAY
TAG_STRING
TAG_LIST
TAG_COMPOUND
TAG_INT_ARRAY
TAG_UNKNOWN
)
type TagType byte
func (t TagType) String() string {
switch t {
case TAG_END: return "TAG_END"
case TAG_BYTE: return "TAG_BYTE"
case TAG_SHORT: return "TAG_SHORT"
case TAG_INT: return "TAG_INT"
case TAG_LONG: return "TAG_LONG"
case TAG_FLOAT: return "TAG_FLOAT"
case TAG_DOUBLE: return "TAG_DOUBLE"
case TAG_BYTE_ARRAY: return "TAG_BYTE_ARRAY"
case TAG_STRING: return "TAG_STRING"
case TAG_LIST: return "TAG_LIST"
case TAG_COMPOUND: return "TAG_COMPOUND"
case TAG_INT_ARRAY: return "TAG_INT_ARRAY"
}
return "TAG_UNKNOWN"
}
func StructFields(d reflect.Value) map[string]reflect.Value {
if d.Kind() == reflect.Struct {
tags := make(map[string]reflect.Value, 0)
tags[strings.ToLower(d.Type().Name())] = d
for i := 0; i < d.NumField(); i++ {
if field := d.Type().Field(i); field.Tag.Get("nbt") == "" {
tags[strings.ToLower(field.Name)] = d.Field(i)
} else {
tags[strings.ToLower(field.Tag.Get("nbt"))] = d.Field(i)
}
}
return tags
}
return nil
}
func Read(r io.Reader, data interface{}) {
ReadTag(r, TAG_UNKNOWN, reflect.ValueOf(data).Elem())
}
func ReadTag(r io.Reader, tagType TagType, data reflect.Value) (name string, newTagType TagType) {
if tagType == TAG_UNKNOWN {
newTagType = TagType(ReadByte(r))
if newTagType == TAG_END {
return
}
name = ReadString(r)
} else {
newTagType = TagType(tagType)
}
fields := StructFields(data)
var field reflect.Value
if data.IsValid() {
field = fields[strings.ToLower(name)]
}
if name == "" {
field = data
}
var temp interface{}
var list reflect.Value
switch newTagType {
case TAG_BYTE: temp = ReadByte(r)
case TAG_SHORT: temp = ReadShort(r)
case TAG_INT: temp = ReadInt(r)
case TAG_LONG: temp = ReadLong(r)
case TAG_FLOAT: temp = ReadFloat(r)
case TAG_DOUBLE: temp = ReadDouble(r)
case TAG_BYTE_ARRAY: temp = ReadByteArray(r)
case TAG_STRING: temp = ReadString(r)
case TAG_INT_ARRAY: temp = ReadIntArray(r)
case TAG_COMPOUND: ReadCompound(r, field)
case TAG_LIST: list = ReadList(r, field)
}
if field.IsValid() {
if temp != nil {
field.Set(reflect.ValueOf(temp))
} else if list.IsValid() {
field.Set(list)
}
}
return
}
func ReadByte(r io.Reader) (i byte) {
b := make([]byte, 1)
_, err := r.Read(b)
if err != io.EOF {
errhandler.Handle("Error reading Byte: ", err)
}
i = b[0]
return
}
func ReadShort(r io.Reader) (i int16) {
err := binary.Read(r, binary.BigEndian, &i)
errhandler.Handle("Error reading int16: ", err)
return
}
func ReadInt(r io.Reader) (i int32) {
err := binary.Read(r, binary.BigEndian, &i)
errhandler.Handle("Error reading int32: ", err)
return
}
func ReadLong(r io.Reader) (i int64) {
err := binary.Read(r, binary.BigEndian, &i)
errhandler.Handle("Error reading int32: ", err)
return
}
func ReadFloat(r io.Reader) (i float32) {
b := make([]byte, 4)
_, err := r.Read(b)
errhandler.Handle("Error reading Float: ", err)
i = math.Float32frombits(binary.BigEndian.Uint32(b))
return
}
func ReadDouble(r io.Reader) (i float64) {
b := make([]byte, 8)
_, err := r.Read(b)
errhandler.Handle("Error reading Double: ", err)
i = math.Float64frombits(binary.BigEndian.Uint64(b))
return
}
func ReadByteArray(r io.Reader) (i []byte) {
i = make([]byte, ReadInt(r))
_, err := r.Read(i)
errhandler.Handle("Error reading Byte Array: ", err)
return
}
func ReadString(r io.Reader) string {
result := make([]byte, ReadShort(r))
_, err := r.Read(result)
errhandler.Handle("Error reading String: ", err)
return string(result)
}
func ReadIntArray(r io.Reader) (list []int32) {
length := int(ReadInt(r))
for i := 0; i < length; i++ {
list = append(list, ReadInt(r))
}
return
}
func ReadCompound(r io.Reader, data reflect.Value) {
for _, tagType := ReadTag(r, TAG_UNKNOWN, data); tagType != TAG_END; _, tagType = ReadTag(r, TAG_UNKNOWN, data) {}
}
func ReadList(r io.Reader, data reflect.Value) reflect.Value {
listType := TagType(ReadByte(r))
length := ReadInt(r)
for i := int32(0); i < length; i++ {
if data.IsValid() {
temp := reflect.Indirect(reflect.New(data.Type().Elem()))
ReadTag(r, listType, temp)
data = reflect.Append(data, temp)
}
}
return data
}
type Level struct {
Nested Nested `nbt:"nested compound test"`
ByteTest byte
IntTest int32
StringTest string
ListTestLong []int64 `nbt:"listTest (long)"`
DoubleTest float64
FloatTest float32
LongTest int64
ListTestCompound []ListTest `nbt:"listtest (compound)"`
ByteArrayTest []byte `nbt:"bytearraytest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))"`
ShortTest int16
}
type Nested struct {
Egg Egg
Ham Ham
}
type Egg struct {
Name string
Value float32
}
type Ham struct {
Name string
Value float32
}
type ListTest struct {
CreatedOn int64 `nbt:"created-on"`
Name string
}
func main() {
nbtFile, err := os.Open(NBTFILE)
errhandler.Handle("Error opening chunk file: ", err)
defer nbtFile.Close()
rawNbtFile, err := gzip.NewReader(nbtFile)
errhandler.Handle("Error opening chunk file: ", err)
var l Level
Read(rawNbtFile, &l)
// Use json for pretty printing
data, err := json.MarshalIndent(l, "", "\t")
errhandler.Handle("Error opening chunk file: ", err)
fmt.Printf("%s\n", data)
}
{
"Nested": {
"Egg": {
"Name": "Eggbert",
"Value": 0.5
},
"Ham": {
"Name": "Hampus",
"Value": 0.75
}
},
"ByteTest": 127,
"IntTest": 2147483647,
"StringTest": "HELLO WORLD THIS IS A TEST STRING ÅÄÖ!",
"ListTestLong": [
11,
12,
13,
14,
15
],
"DoubleTest": 0.4931287132182315,
"FloatTest": 0.49823147,
"LongTest": 9223372036854775807,
"ListTestCompound": [
{
"CreatedOn": 1264099775885,
"Name": "Compound tag #0"
},
{
"CreatedOn": 1264099775885,
"Name": "Compound tag #1"
}
],
"ByteArrayTest": "AD4iEAgKFixMEkYgBFZOUFwOLlgoAko4MDI+VBA6CkgsGhIUIDZWHFAqDmBYWgIYOGIyDFRCOjxIXhpEFFI2JBweKkBgJlo0GAZiAAwiQgg8Fl5MREZSBCROHlxALiYoNEoGMAA+IhAIChYsTBJGIARWTlBcDi5YKAJKODAyPlQQOgpILBoSFCA2VhxQKg5gWFoCGDhiMgxUQjo8SF4aRBRSNiQcHipAYCZaNBgGYgAMIkIIPBZeTERGUgQkTh5cQC4mKDRKBjAAPiIQCAoWLEwSRiAEVk5QXA4uWCgCSjgwMj5UEDoKSCwaEhQgNlYcUCoOYFhaAhg4YjIMVEI6PEheGkQUUjYkHB4qQGAmWjQYBmIADCJCCDwWXkxERlIEJE4eXEAuJig0SgYwAD4iEAgKFixMEkYgBFZOUFwOLlgoAko4MDI+VBA6CkgsGhIUIDZWHFAqDmBYWgIYOGIyDFRCOjxIXhpEFFI2JBweKkBgJlo0GAZiAAwiQgg8Fl5MREZSBCROHlxALiYoNEoGMAA+IhAIChYsTBJGIARWTlBcDi5YKAJKODAyPlQQOgpILBoSFCA2VhxQKg5gWFoCGDhiMgxUQjo8SF4aRBRSNiQcHipAYCZaNBgGYgAMIkIIPBZeTERGUgQkTh5cQC4mKDRKBjAAPiIQCAoWLEwSRiAEVk5QXA4uWCgCSjgwMj5UEDoKSCwaEhQgNlYcUCoOYFhaAhg4YjIMVEI6PEheGkQUUjYkHB4qQGAmWjQYBmIADCJCCDwWXkxERlIEJE4eXEAuJig0SgYwAD4iEAgKFixMEkYgBFZOUFwOLlgoAko4MDI+VBA6CkgsGhIUIDZWHFAqDmBYWgIYOGIyDFRCOjxIXhpEFFI2JBweKkBgJlo0GAZiAAwiQgg8Fl5MREZSBCROHlxALiYoNEoGMAA+IhAIChYsTBJGIARWTlBcDi5YKAJKODAyPlQQOgpILBoSFCA2VhxQKg5gWFoCGDhiMgxUQjo8SF4aRBRSNiQcHipAYCZaNBgGYgAMIkIIPBZeTERGUgQkTh5cQC4mKDRKBjAAPiIQCAoWLEwSRiAEVk5QXA4uWCgCSjgwMj5UEDoKSCwaEhQgNlYcUCoOYFhaAhg4YjIMVEI6PEheGkQUUjYkHB4qQGAmWjQYBmIADCJCCDwWXkxERlIEJE4eXEAuJig0SgYwAD4iEAgKFixMEkYgBFZOUFwOLlgoAko4MDI+VBA6CkgsGhIUIDZWHFAqDmBYWgIYOGIyDFRCOjxIXhpEFFI2JBweKkBgJlo0GAZiAAwiQgg8Fl5MREZSBCROHlxALiYoNEoGMA==",
"ShortTest": 32767
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment