Files
bd2/go/internal/server/calendar/binary.go
T
Flechazo d8771caef2 feat(all): add hunting and events, commerce and rewards, binary calendars, GameData caching and login diagnostics
Implement hunting AP and progress, event tasks, exchanges, games, stories and actions; connect battle and reward handling.

Add CashShop plugin and installation, server purchases and entitlements, recharge rules and plugin version metadata.

Distribute binary calendars independently; cache GameData in memory and fix single-connection query deadlocks.

Improve automatic login and recovery diagnostics; document configuration and release flows.
2026-10-05 11:50:31 +08:00

195 lines
4.1 KiB
Go

package calendar
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"unicode/utf8"
)
const MaxFileSize = 8 * 1024 * 1024
const maxRows = 100000
const maxString = 64 * 1024
const headerSize = 46
var magic = []byte{'B', 'D', '2', 'S', 'C', 'H', 0, 0}
type encoder struct {
data []byte
err error
rows uint64
}
func (e *encoder) u64(v uint64) {
if e.room(8) {
e.data = binary.LittleEndian.AppendUint64(e.data, v)
}
}
func (e *encoder) u32(v uint32) {
if e.room(4) {
e.data = binary.LittleEndian.AppendUint32(e.data, v)
}
}
func (e *encoder) b(v bool) {
if !e.room(1) {
return
}
if v {
e.data = append(e.data, 1)
} else {
e.data = append(e.data, 0)
}
}
func (e *encoder) str(v string) {
if e.err != nil {
return
}
if len(v) > maxString || !utf8.ValidString(v) {
e.err = fmt.Errorf("calendar: invalid/oversized UTF8 string")
return
}
if !e.room(4 + len(v)) {
return
}
e.u32(uint32(len(v)))
e.data = append(e.data, v...)
}
func (e *encoder) count(n int) {
if e.err != nil {
return
}
e.rows += uint64(n)
if n > maxRows || e.rows > maxRows {
e.err = fmt.Errorf("calendar: row limit exceeded")
return
}
e.u32(uint32(n))
}
func (e *encoder) room(n int) bool {
if e.err != nil {
return false
}
if n > MaxFileSize-headerSize-len(e.data) {
e.err = fmt.Errorf("calendar: file size limit exceeded")
return false
}
return true
}
type decoder struct {
data []byte
pos int
err error
rows uint64
}
func (d *decoder) take(n int) []byte {
if d.err != nil {
return nil
}
if n < 0 || n > len(d.data)-d.pos {
d.err = fmt.Errorf("calendar: truncated payload")
return nil
}
v := d.data[d.pos : d.pos+n]
d.pos += n
return v
}
func (d *decoder) u64() uint64 {
v := d.take(8)
if len(v) != 8 {
return 0
}
return binary.LittleEndian.Uint64(v)
}
func (d *decoder) u32() uint32 {
v := d.take(4)
if len(v) != 4 {
return 0
}
return binary.LittleEndian.Uint32(v)
}
func (d *decoder) b() bool {
v := d.take(1)
if len(v) != 1 {
return false
}
if v[0] > 1 {
d.err = fmt.Errorf("calendar: invalid boolean/presence")
}
return v[0] == 1
}
func (d *decoder) str() string {
n := d.u32()
if n > maxString {
d.err = fmt.Errorf("calendar: string limit exceeded")
return ""
}
v := d.take(int(n))
if !utf8.Valid(v) {
d.err = fmt.Errorf("calendar: invalid UTF8")
}
return string(v)
}
func (d *decoder) count() int {
n := d.u32()
d.rows += uint64(n)
if n > maxRows || d.rows > maxRows || uint64(n) > uint64(len(d.data)-d.pos) {
d.err = fmt.Errorf("calendar: row count limit/truncation")
return 0
}
return int(n)
}
// MarshalBinary emits format 1's fixed-order record payload and checksum.
func MarshalBinary(m Manifest) ([]byte, error) {
if m.SchemaVersion != 1 {
return nil, fmt.Errorf("calendar: unsupported schema")
}
e := &encoder{}
e.manifest(m)
if e.err != nil {
return nil, e.err
}
if len(e.data) > MaxFileSize-headerSize {
return nil, fmt.Errorf("calendar: file size limit exceeded")
}
out := append([]byte(nil), magic...)
out = binary.LittleEndian.AppendUint16(out, 1)
out = binary.LittleEndian.AppendUint32(out, uint32(len(e.data)))
sum := sha256.Sum256(e.data)
out = append(out, sum[:]...)
return append(out, e.data...), nil
}
// UnmarshalBinary rejects damaged, unsupported, oversized and trailing data.
func UnmarshalBinary(raw []byte) (Manifest, error) {
var m Manifest
if len(raw) < headerSize || len(raw) > MaxFileSize {
return m, fmt.Errorf("calendar: invalid file size")
}
if !bytes.Equal(raw[:8], magic) || binary.LittleEndian.Uint16(raw[8:10]) != 1 {
return m, fmt.Errorf("calendar: unsupported magic/format")
}
n := binary.LittleEndian.Uint32(raw[10:14])
if uint64(n) != uint64(len(raw)-headerSize) {
return m, fmt.Errorf("calendar: payload length mismatch")
}
payload := raw[headerSize:]
sum := sha256.Sum256(payload)
if !bytes.Equal(raw[14:46], sum[:]) {
return m, fmt.Errorf("calendar: checksum mismatch")
}
d := &decoder{data: payload}
m = d.manifest()
if d.err != nil {
return Manifest{}, d.err
}
if d.pos != len(payload) {
return Manifest{}, fmt.Errorf("calendar: trailing payload")
}
m.SchemaVersion = 1
return m, nil
}