Documentation
¶
Overview ¶
Package binary provides low-level binary I/O utilities for reading Terraria file formats.
Overview ¶
This package implements a binary reader with Terraria-specific data types including 7-bit encoded integers, .NET-style strings, and GUID handling. It is primarily used internally by the world and player packages.
Reader Usage ¶
Create a reader from any io.Reader:
f, _ := os.Open("file.bin")
r := binary.NewReader(f)
version := r.ReadInt32()
name := r.ReadString()
flag := r.ReadBool()
if r.Err() != nil {
log.Fatal(r.Err())
}
Seekable Reader ¶
For files that require seeking (like world files), use NewSeekableReader:
f, _ := os.Open("world.wld")
r := binary.NewSeekableReader(f)
// Seek to a specific position
r.Seek(1000, io.SeekStart)
Supported Types ¶
The reader supports these data types:
- Integers: Reader.ReadInt8, Reader.ReadInt16, Reader.ReadInt32, Reader.ReadInt64
- Unsigned: Reader.ReadByteValue, Reader.ReadUint16, Reader.ReadUint32, Reader.ReadUint64
- Floats: Reader.ReadFloat32, Reader.ReadFloat64
- Booleans: Reader.ReadBool
- Strings: Reader.ReadString (7-bit length prefix), Reader.ReadFixedString
- Special: Reader.Read7BitEncodedInt, Reader.ReadUUID, Reader.ReadBits
Error Handling ¶
The reader uses sticky error handling - once an error occurs, all subsequent reads return zero values and the error persists. Check Reader.Err after a sequence of reads:
v1 := r.ReadInt32()
v2 := r.ReadInt32()
v3 := r.ReadString()
if err := r.Err(); err != nil {
// Handle error from any of the reads
}
Position Tracking ¶
Track the current read position with Reader.Offset:
before := r.Offset()
r.ReadBytes(100)
after := r.Offset()
fmt.Printf("Read %d bytes\n", after - before)
7-Bit Encoded Integers ¶
Terraria (like .NET) uses 7-bit encoded integers for string lengths:
length := r.Read7BitEncodedInt() // Variable-length integer
This encoding uses the high bit as a continuation flag, allowing small numbers to use fewer bytes.
GUID/UUID Reading ¶
.NET GUIDs have a specific byte order. Use Reader.ReadUUID:
uuid := r.ReadUUID() // Returns formatted UUID string
Package binary provides low-level binary I/O utilities for reading Terraria file formats. It wraps the standard library's encoding/binary package with Terraria-specific data types such as 7-bit encoded integers for string lengths, and provides convenient methods for reading common data structures.
Index ¶
- type Reader
- func (r *Reader) Err() error
- func (r *Reader) Offset() int64
- func (r *Reader) Read7BitEncodedInt() int32
- func (r *Reader) ReadBits(n int) []bool
- func (r *Reader) ReadBitsByte() [8]bool
- func (r *Reader) ReadBool() bool
- func (r *Reader) ReadByteValue() byte
- func (r *Reader) ReadBytes(n int) []byte
- func (r *Reader) ReadFixedString(n int) string
- func (r *Reader) ReadFloat32() float32
- func (r *Reader) ReadFloat64() float64
- func (r *Reader) ReadInt16() int16
- func (r *Reader) ReadInt32() int32
- func (r *Reader) ReadInt64() int64
- func (r *Reader) ReadInt8() int8
- func (r *Reader) ReadString() string
- func (r *Reader) ReadUUID() string
- func (r *Reader) ReadUint16() uint16
- func (r *Reader) ReadUint32() uint32
- func (r *Reader) ReadUint64() uint64
- func (r *Reader) ReadUint8() uint8
- func (r *Reader) Skip(n int)
- type SeekableReader
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader wraps an io.Reader and provides methods for reading Terraria binary data types. All multi-byte integers are read in little-endian format, which is the format used by Terraria's save files.
func (*Reader) Err ¶
Err returns any error that occurred during reading. Once an error occurs, all subsequent reads will return zero values.
func (*Reader) Read7BitEncodedInt ¶
Read7BitEncodedInt reads a 7-bit encoded integer. This is the format used by .NET's BinaryReader.Read7BitEncodedInt(). Each byte contains 7 bits of the value, with the high bit indicating whether more bytes follow.
func (*Reader) ReadBits ¶
ReadBits reads n bits from the stream, reading whole bytes as needed. Returns a slice of booleans representing the bits.
func (*Reader) ReadBitsByte ¶
ReadBitsByte reads a single byte and returns it as an array of 8 booleans. Bit 0 (LSB) is at index 0, bit 7 (MSB) is at index 7.
func (*Reader) ReadByteValue ¶ added in v0.2.3
ReadByteValue reads and returns a single byte.
This method intentionally does not implement io.ByteReader interface. The binary.Reader uses a sticky error pattern where errors are stored in r.err and checked via r.Err() after a sequence of reads. This pattern is consistent across all Reader methods (ReadInt32, ReadString, etc.).
Design Decision (Option 2: Rename): We renamed from ReadByte() to ReadByteValue() to avoid the go vet warning while maintaining API consistency. This preserves the sticky error pattern used throughout the package.
Benefits of Current Approach: - Consistent API: All methods follow the same error handling pattern - Simpler usage: Check errors once after multiple reads - Lower risk: No mixing of error handling patterns - Maintainable: Single, clear error handling strategy
Limitations of Current Approach: - Does not implement io.ByteReader interface - Cannot be used directly with standard library functions expecting io.ByteReader - Method name is less conventional (ReadByteValue vs ReadByte)
Future Consideration (Option 3: Implement io.ByteReader): If standard library compatibility becomes important, we could change the signature to ReadByte() (byte, error) to implement io.ByteReader. However, this would create API inconsistency:
Benefits of Option 3: - Implements standard io.ByteReader interface - Compatible with standard library functions - More idiomatic Go (errors returned directly)
Risks of Option 3:
- API inconsistency: Only ReadByte would return errors directly
- Breaks sticky error pattern for this one method
- More complex: Mixing two error handling patterns
- Higher risk: Easy to forget error checks, inconsistent with other methods
- Internal methods (Read7BitEncodedInt, ReadBool, etc.) would need complex error handling to convert between patterns
Current Recommendation: Maintain Option 2 (rename) for consistency and simplicity. Consider Option 3 only if standard library io.ByteReader compatibility becomes a hard requirement.
func (*Reader) ReadFixedString ¶
ReadFixedString reads a fixed-length string of exactly n bytes.
func (*Reader) ReadFloat32 ¶
ReadFloat32 reads a 32-bit floating point number in little-endian format.
func (*Reader) ReadFloat64 ¶
ReadFloat64 reads a 64-bit floating point number in little-endian format.
func (*Reader) ReadString ¶
ReadString reads a length-prefixed string. The length is encoded as a 7-bit encoded integer, followed by the UTF-8 bytes.
func (*Reader) ReadUint16 ¶
ReadUint16 reads an unsigned 16-bit integer in little-endian format.
func (*Reader) ReadUint32 ¶
ReadUint32 reads an unsigned 32-bit integer in little-endian format.
func (*Reader) ReadUint64 ¶
ReadUint64 reads an unsigned 64-bit integer in little-endian format.
type SeekableReader ¶
type SeekableReader struct {
*Reader
// contains filtered or unexported fields
}
SeekableReader is a Reader that can seek to specific positions.
func NewSeekableReader ¶
func NewSeekableReader(rs io.ReadSeeker) *SeekableReader
NewSeekableReader creates a new SeekableReader from an io.ReadSeeker.
func (*SeekableReader) Seek ¶
func (r *SeekableReader) Seek(offset int64, whence int) (int64, error)
Seek sets the offset for the next read, interpreted according to whence: io.SeekStart means relative to the start of the file, io.SeekCurrent means relative to the current offset, and io.SeekEnd means relative to the end.