package fmm import ( "bufio" "encoding/binary" "errors" "fmt" "io" "math" ) type AllowedCommands uint8 const ( AllowedCommandsInvalid AllowedCommands = iota AllowedCommandsTrue AllowedCommandsFalse AllowedCommandsAdminsOnly ) type PropertyTree interface { ptree() } type ( PropertyTreeNone struct{} PropertyTreeBool bool PropertyTreeNumber float64 PropertyTreeString string PropertyTreeList []PropertyTree PropertyTreeDict map[string]PropertyTree PropertyTreeSignedInteger int64 PropertyTreeUnsignedInteger uint64 ) func (self *PropertyTreeNone) ptree() {} func (self *PropertyTreeBool) ptree() {} func (self *PropertyTreeNumber) ptree() {} func (self *PropertyTreeString) ptree() {} func (self *PropertyTreeList) ptree() {} func (self *PropertyTreeDict) ptree() {} func (self *PropertyTreeSignedInteger) ptree() {} func (self *PropertyTreeUnsignedInteger) ptree() {} type ModSettings struct { MapVersion Version Settings PropertyTree } type DatReader struct { reader *bufio.Reader err error } func newDatReader(reader io.Reader) DatReader { return DatReader{ reader: bufio.NewReader(reader), } } func (r *DatReader) Read(buf []byte) (int, error) { if r.err != nil { return 0, nil } return r.reader.Read(buf) } func (r *DatReader) ReadBool() bool { if r.err != nil { return false } value := false if err := binary.Read(r, binary.LittleEndian, &value); err != nil { r.err = err } return value } func (r *DatReader) ReadUint8() uint8 { if r.err != nil { return 0 } value, err := r.reader.ReadByte() if err != nil { r.err = err return 0 } return uint8(value) } func (r *DatReader) ReadUint16() uint16 { if r.err != nil { return 0 } var value uint16 if err := binary.Read(r, binary.LittleEndian, &value); err != nil { r.err = err } return value } func (r *DatReader) ReadUint16Optimized() uint16 { if r.err != nil { return 0 } first := r.ReadUint8() if first < math.MaxUint8 { return uint16(first) } return r.ReadUint16() } func (r *DatReader) ReadUint32() uint32 { if r.err != nil { return 0 } var val uint32 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { r.err = err } return val } func (r *DatReader) ReadUint32Optimized() uint32 { if r.err != nil { return 0 } first := r.ReadUint8() if first < math.MaxUint8 { return uint32(first) } return r.ReadUint32() } func (r *DatReader) ReadUint64() uint64 { if r.err != nil { return 0 } var val uint64 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { r.err = err } return val } func (r *DatReader) ReadInt64() int64 { if r.err != nil { return 0 } var val int64 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { r.err = err } return val } func (r *DatReader) ReadDouble() float64 { if r.err != nil { return 0 } var val float64 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { r.err = err } return val } func (r *DatReader) ReadString() string { if r.err != nil { return "" } length := r.ReadUint32Optimized() stringBuf := make([]byte, length) _, err := io.ReadFull(r.reader, stringBuf) if err != nil { r.err = err } return string(stringBuf) } func (r *DatReader) ReadStringOptional() string { if r.err != nil { return "" } empty := r.ReadBool() if empty { return "" } return r.ReadString() } func (r *DatReader) ReadVersionOptimized(withBuild bool) Version { if r.err != nil { return Version{} } ver := Version{ r.ReadUint16Optimized(), r.ReadUint16Optimized(), r.ReadUint16Optimized(), } if withBuild { ver[3] = r.ReadUint16Optimized() } return ver } func (r *DatReader) ReadVersionUnoptimized() Version { if r.err != nil { return Version{} } return Version{ r.ReadUint16(), r.ReadUint16(), r.ReadUint16(), r.ReadUint16(), } } func (r *DatReader) ReadModWithCRC() ModIdent { if r.err != nil { return ModIdent{} } name := r.ReadString() version := r.ReadVersionOptimized(false) r.ReadUint32() // CRC return ModIdent{name, &version} } func (r *DatReader) ReadPropertyTree() PropertyTree { if r.err != nil { return &PropertyTreeNone{} } kind := r.ReadUint8() r.ReadBool() // Internal flag that we do not care about switch kind { case 0: return &PropertyTreeNone{} case 1: return ptr(PropertyTreeBool(r.ReadBool())) case 2: return ptr(PropertyTreeNumber(r.ReadDouble())) case 3: return ptr(PropertyTreeString(r.ReadStringOptional())) case 4: length := r.ReadUint32() res := []PropertyTree{} for i := uint32(0); i < length; i++ { r.ReadStringOptional() res = append(res, r.ReadPropertyTree()) } return ptr(PropertyTreeList(res)) case 5: length := r.ReadUint32() res := map[string]PropertyTree{} for i := uint32(0); i < length; i++ { res[r.ReadStringOptional()] = r.ReadPropertyTree() } return ptr(PropertyTreeDict(res)) case 6: return ptr(PropertyTreeSignedInteger(r.ReadInt64())) case 7: return ptr(PropertyTreeSignedInteger(r.ReadUint64())) } r.err = errors.New(fmt.Sprintf("unknown property tree kind: %d\n", kind)) return &PropertyTreeNone{} } func (r *DatReader) ReadModSettings() ModSettings { if r.err != nil { return ModSettings{} } mapVersion := r.ReadVersionUnoptimized() r.ReadBool() // Internal flag, always false settings := r.ReadPropertyTree() return ModSettings{mapVersion, settings} } func (r *DatReader) ReadAllowedCommands() AllowedCommands { result := AllowedCommandsInvalid if r.err != nil { return result } raw := r.ReadUint8() switch raw { case 1: result = AllowedCommandsTrue case 2: result = AllowedCommandsFalse case 3: result = AllowedCommandsAdminsOnly } return result } type DatWriter struct { writer *bufio.Writer err error } func (w *DatWriter) Write(buf []byte) (int, error) { if w.err != nil { return 0, nil } return w.writer.Write(buf) } func newDatWriter(writer io.Writer) DatWriter { return DatWriter{ writer: bufio.NewWriter(writer), } } func (w *DatWriter) WriteBool(value bool) { if w.err != nil { return } binary.Write(w, binary.LittleEndian, value) } func (w *DatWriter) WriteUint8(value uint8) { if w.err != nil { return } binary.Write(w, binary.LittleEndian, value) } func (w *DatWriter) WriteUint16(value uint16) { if w.err != nil { return } binary.Write(w, binary.LittleEndian, value) } func (w *DatWriter) WriteUint16Optimized(value uint16) { if w.err != nil { return } if value < math.MaxUint8 { w.WriteUint8(uint8(value)) } else { w.WriteUint16(value) } } func (w *DatWriter) WriteUint32(value uint32) { if w.err != nil { return } binary.Write(w, binary.LittleEndian, value) } func (w *DatWriter) WriteUint32Optimized(value uint32) { if w.err != nil { return } if value < math.MaxUint8 { w.WriteUint8(uint8(value)) } else { w.WriteUint32(value) } } func (w *DatWriter) WriteUint64(value uint64) { if w.err != nil { return } binary.Write(w, binary.LittleEndian, value) } func (w *DatWriter) WriteInt64(value int64) { if w.err != nil { return } binary.Write(w, binary.LittleEndian, value) } func (w *DatWriter) WriteDouble(value float64) { if w.err != nil { return } binary.Write(w, binary.LittleEndian, value) } func (w *DatWriter) WriteString(value string) { if w.err != nil { return } length := len(value) if length > math.MaxUint32 { w.err = errors.New(fmt.Sprintf("PropertyTree string is too long: %d", length)) return } w.WriteUint32Optimized(uint32(length)) _, err := w.writer.WriteString(value) if err != nil { w.err = err } } func (w *DatWriter) WriteStringOptional(value string) { if w.err != nil { return } if value == "" { w.WriteBool(true) return } w.WriteBool(false) w.WriteString(value) } func (w *DatWriter) WriteVersionUnoptimized(version Version) { if w.err != nil { return } w.WriteUint16(version[0]) w.WriteUint16(version[1]) w.WriteUint16(version[2]) w.WriteUint16(version[3]) } func (w *DatWriter) WritePropertyTree(pt PropertyTree) { if w.err != nil { return } switch val := pt.(type) { case *PropertyTreeNone: w.WriteUint8(0) // Type w.WriteUint8(0) // Unused internal flag case *PropertyTreeBool: w.WriteUint8(1) // Type w.WriteUint8(0) // Unused internal flag w.WriteBool(bool(*val)) case *PropertyTreeNumber: w.WriteUint8(2) // Type w.WriteUint8(0) // Unused internal flag w.WriteDouble(float64(*val)) case *PropertyTreeString: w.WriteUint8(3) // Type w.WriteUint8(0) // Unused internal flag w.WriteStringOptional(string(*val)) case *PropertyTreeList: w.WriteUint8(4) // Type w.WriteUint8(0) // Unused internal flag list := []PropertyTree(*val) w.WriteUint32(uint32(len(list))) for _, child := range list { w.WriteStringOptional("") w.WritePropertyTree(child) } case *PropertyTreeDict: w.WriteUint8(5) // Type w.WriteUint8(0) // Unused internal flag dict := map[string]PropertyTree(*val) w.WriteUint32(uint32(len(dict))) for key, child := range dict { w.WriteStringOptional(key) w.WritePropertyTree(child) } case *PropertyTreeSignedInteger: w.WriteUint8(6) // Type w.WriteUint8(0) // Unused internal flag w.WriteInt64(int64(*val)) case *PropertyTreeUnsignedInteger: w.WriteUint8(7) // Type w.WriteUint8(0) // Unused internal flag w.WriteUint64(uint64(*val)) } } func (w *DatWriter) WriteModSettings(input *ModSettings) { if w.err != nil { return } w.WriteVersionUnoptimized(input.MapVersion) w.WriteBool(false) // Internal flag, always false w.WritePropertyTree(input.Settings) } func (w *DatWriter) Flush() { if w.err != nil { return } w.err = w.writer.Flush() }