Files
TrollSSH/internal/tsf/format.go
T

120 lines
3.1 KiB
Go
Raw Normal View History

2026-07-16 23:29:14 +07:00
package tsf
2026-07-13 17:47:00 +07:00
import (
"bufio"
"encoding/binary"
"fmt"
"math"
"os"
)
2026-07-16 23:29:14 +07:00
func Write(output string, data *FramesContainer) error {
2026-07-16 21:56:41 +07:00
if data == nil {
return fmt.Errorf("cannot write nil frames container")
}
if math.IsNaN(data.FPS) || math.IsInf(data.FPS, 0) || data.FPS <= 0 || data.FPS > maxTSFFPS {
return fmt.Errorf("cannot write .tsf: fps must be finite, positive, and at most %d", maxTSFFPS)
}
if len(data.ColorFrames) > maxTSFFrameCount || uint64(len(data.ColorFrames)) > math.MaxUint32 {
return fmt.Errorf("cannot write .tsf: frame count exceeds limit")
}
for i, frame := range data.ColorFrames {
if uint64(len(frame)) > math.MaxUint32 {
return fmt.Errorf("cannot write .tsf: frame %d length exceeds uint32", i)
}
}
2026-07-13 17:47:00 +07:00
f, err := os.Create(output)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
2026-07-13 17:47:00 +07:00
w := bufio.NewWriterSize(f, 1<<20)
if _, err := w.WriteString(tsfMagic); err != nil {
return err
}
var hdr [14]byte
binary.LittleEndian.PutUint16(hdr[0:], tsfVersion)
binary.LittleEndian.PutUint64(hdr[2:], math.Float64bits(data.FPS))
binary.LittleEndian.PutUint32(hdr[10:], uint32(len(data.ColorFrames)))
2026-07-13 17:47:00 +07:00
if _, err := w.Write(hdr[:]); err != nil {
return err
}
var lenBuf [4]byte
for _, frame := range data.ColorFrames {
2026-07-13 17:47:00 +07:00
binary.LittleEndian.PutUint32(lenBuf[:], uint32(len(frame)))
if _, err := w.Write(lenBuf[:]); err != nil {
return err
}
if _, err := w.Write(frame); err != nil {
return err
}
}
return w.Flush()
}
2026-07-16 23:29:14 +07:00
func Load(filename string) (*FramesContainer, error) {
2026-07-16 21:56:41 +07:00
file, err := readFrameFile(filename)
2026-07-13 17:47:00 +07:00
if err != nil {
return nil, err
}
2026-07-16 21:56:41 +07:00
owned := false
defer func() {
if !owned {
_ = file.Close()
}
}()
raw := file.data
2026-07-13 17:47:00 +07:00
invalid := func() error {
return fmt.Errorf("invalid frames file %q: corrupt .tsf container", filename)
}
if len(raw) < 18 || string(raw[:4]) != tsfMagic {
return nil, invalid()
}
version := binary.LittleEndian.Uint16(raw[4:])
if version != tsfVersion {
return nil, fmt.Errorf("unsupported .tsf version %d in %q", version, filename)
}
fps := math.Float64frombits(binary.LittleEndian.Uint64(raw[6:]))
count := binary.LittleEndian.Uint32(raw[14:])
2026-07-16 21:56:41 +07:00
if math.IsNaN(fps) || math.IsInf(fps, 0) || fps <= 0 || fps > maxTSFFPS {
2026-07-13 17:47:00 +07:00
return nil, fmt.Errorf(
2026-07-16 21:56:41 +07:00
"invalid frames file %q: fps must be finite, greater than 0, and at most %d",
filename, maxTSFFPS,
2026-07-13 17:47:00 +07:00
)
}
2026-07-16 21:56:41 +07:00
if count == 0 {
return nil, fmt.Errorf("invalid frames file %q: expected non-empty frames", filename)
}
if count > maxTSFFrameCount || uint64(count) > uint64((len(raw)-18)/4) {
return nil, invalid()
}
colorFrames := make([][]byte, 0, int(count))
off := 18
for range count {
if len(raw)-off < 4 {
return nil, invalid()
}
n := uint64(binary.LittleEndian.Uint32(raw[off:]))
off += 4
if n > uint64(len(raw)-off) {
return nil, invalid()
}
nativeLen := int(n)
colorFrames = append(colorFrames, raw[off:off+nativeLen])
off += nativeLen
}
if off != len(raw) {
return nil, invalid()
}
data := &FramesContainer{ColorFrames: colorFrames, FPS: fps}
frameFileOwners.Store(data, file)
owned = true
return data, nil
2026-07-13 17:47:00 +07:00
}