Files
mc/server/world/mcdb/leveldat/level_dat.go
T

105 lines
2.8 KiB
Go
Raw Normal View History

2026-07-09 08:33:57 +08:00
package leveldat
import (
"bufio"
"encoding/binary"
"fmt"
"io"
"os"
"github.com/sandertv/gophertunnel/minecraft/nbt"
)
// LevelDat implements the encoding and decoding of level.dat files. An empty
// LevelDat is a valid value and may be used to Marshal and Write to a writer or
// file afterward.
type LevelDat struct {
hdr header
data []byte
}
// header holds the header for a level.dat file.
type header struct {
StorageVersion int32
FileLength int32
}
// ReadFile reads a level.dat at a path and returns it.
func ReadFile(name string) (*LevelDat, error) {
f, err := os.Open(name)
if err != nil {
return nil, fmt.Errorf("level.dat: open file: %w", err)
}
defer f.Close()
return Read(bufio.NewReader(f))
}
// Read reads a level.dat from r and returns it.
func Read(r io.Reader) (*LevelDat, error) {
var ldat LevelDat
if err := binary.Read(r, binary.LittleEndian, &ldat.hdr); err != nil {
return nil, fmt.Errorf("level.dat: read header: %w", err)
}
ldat.data = make([]byte, ldat.hdr.FileLength)
if n, err := io.ReadFull(r, ldat.data); err != nil || int32(n) != ldat.hdr.FileLength {
return nil, fmt.Errorf("level.dat: read data: %w", err)
}
return &ldat, nil
}
// Unmarshal decodes the level.dat properties from ld into dst. Unmarshal
// returns an error if dst was unable to store all properties found in the
// level.dat.
func (ld *LevelDat) Unmarshal(dst any) error {
if err := nbt.UnmarshalEncoding(ld.data, dst, nbt.LittleEndian); err != nil {
return fmt.Errorf("level.dat: decode nbt: %w", err)
}
return nil
}
// Ver returns the version of the level.dat decoded, or 0 if ld is the empty
// value.
func (ld *LevelDat) Ver() int {
return int(ld.hdr.StorageVersion)
}
// Marshal encodes src and stores it in the level.dat. src should be either a
// struct or a map of fields. Marshal updates the storage version to the latest.
func (ld *LevelDat) Marshal(src any) error {
var err error
ld.data, err = nbt.MarshalEncoding(src, nbt.LittleEndian)
if err != nil {
return fmt.Errorf("level.dat: encode nbt: %w", err)
}
ld.hdr = header{
StorageVersion: Version,
FileLength: int32(len(ld.data)),
}
return nil
}
// Write writes ld to w.
func (ld *LevelDat) Write(w io.Writer) error {
if err := binary.Write(w, binary.LittleEndian, ld.hdr); err != nil {
return fmt.Errorf("level.dat: write header: %w", err)
}
if _, err := w.Write(ld.data); err != nil {
return fmt.Errorf("level.dat: write data: %w", err)
}
return nil
}
// WriteFile writes ld to a file at name.
func (ld *LevelDat) WriteFile(name string) error {
f, err := os.OpenFile(name, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("level.dat: open file: %w", err)
}
w := bufio.NewWriter(f)
defer func() {
_ = w.Flush()
_ = f.Close()
}()
return ld.Write(w)
}