aboutsummaryrefslogtreecommitdiffstats
path: root/packet
diff options
context:
space:
mode:
authorGravatar BanceDev 2026-02-16 16:31:54 -0500
committerGravatar BanceDev 2026-02-16 16:31:54 -0500
commitca90ebdfa8789654766c5d7969baa7afacd9ebd2 (patch)
tree9693e0c7a5af6713f4c5e39372dcf22d05844ec3 /packet
initial commitHEADmain
Diffstat (limited to 'packet')
-rw-r--r--packet/clc/connectionless.go64
-rw-r--r--packet/clc/gamedata.go101
-rw-r--r--packet/clc/parse.go18
-rw-r--r--packet/command/a2aping/a2aping.go17
-rw-r--r--packet/command/a2cclientcommand/a2cclientcommand.go37
-rw-r--r--packet/command/a2cprint/a2cprint.go52
-rw-r--r--packet/command/bad/bad.go19
-rw-r--r--packet/command/baseline/baseline.go89
-rw-r--r--packet/command/bigkick/bigkick.go17
-rw-r--r--packet/command/cdtrack/cdtrack.go46
-rw-r--r--packet/command/centerprint/centerprint.go31
-rw-r--r--packet/command/chokecount/chokecount.go31
-rw-r--r--packet/command/clientdata/clientdata.go164
-rw-r--r--packet/command/command.go5
-rw-r--r--packet/command/connect/connect.go67
-rw-r--r--packet/command/damage/damage.go61
-rw-r--r--packet/command/delta/delta.go26
-rw-r--r--packet/command/deltapacketentities/deltapacketentities.go43
-rw-r--r--packet/command/deltausercommand/deltausercommand.go178
-rw-r--r--packet/command/disconnect/disconnect.go42
-rw-r--r--packet/command/download/download.go90
-rw-r--r--packet/command/entgravity/entgravity.go31
-rw-r--r--packet/command/fastupdate/fastupdate.go182
-rw-r--r--packet/command/finale/finale.go31
-rw-r--r--packet/command/foundsecret/foundsecret.go17
-rw-r--r--packet/command/ftedownload/ftedownload.go54
-rw-r--r--packet/command/ftemodellist/ftemodellist.go57
-rw-r--r--packet/command/ftespawnbaseline/ftespawnbaseline.go41
-rw-r--r--packet/command/ftespawnstatic/ftespawnstatic.go41
-rw-r--r--packet/command/ftevoicechatc/ftevoicechatc.go49
-rw-r--r--packet/command/ftevoicechats/ftevoicechats.go55
-rw-r--r--packet/command/getchallenge/getchallenge.go16
-rw-r--r--packet/command/intermission/intermission.go72
-rw-r--r--packet/command/ip/ip.go22
-rw-r--r--packet/command/killedmonster/killedmonster.go17
-rw-r--r--packet/command/lightstyle/lightstyle.go37
-rw-r--r--packet/command/maxspeed/maxspeed.go31
-rw-r--r--packet/command/modellist/modellist.go83
-rw-r--r--packet/command/move/move.go79
-rw-r--r--packet/command/muzzleflash/muzzleflash.go31
-rw-r--r--packet/command/mvdweapon/mvdweapon.go54
-rw-r--r--packet/command/nails/nails.go50
-rw-r--r--packet/command/nails2/nails2.go56
-rw-r--r--packet/command/nopc/nopc.go17
-rw-r--r--packet/command/nops/nops.go17
-rw-r--r--packet/command/packetentities/packetentities.go37
-rw-r--r--packet/command/packetentity/packetentity.go90
-rw-r--r--packet/command/packetentitydelta/packetentitydelta.go254
-rw-r--r--packet/command/particle/particle.go85
-rw-r--r--packet/command/passthrough/passthrough.go38
-rw-r--r--packet/command/playerinfo/playerinfo.go337
-rw-r--r--packet/command/print/print.go47
-rw-r--r--packet/command/qizmovoice/qizmovoice.go29
-rw-r--r--packet/command/qtvconnect/qtvconnect.go32
-rw-r--r--packet/command/qtvstringcmd/qtvstringcmd.go20
-rw-r--r--packet/command/s2cchallenge/s2cchallenge.go72
-rw-r--r--packet/command/s2cconnection/s2cconnection.go17
-rw-r--r--packet/command/sellscreen/sellscreen.go17
-rw-r--r--packet/command/serverdata/serverdata.go285
-rw-r--r--packet/command/serverinfo/serverinfo.go37
-rw-r--r--packet/command/setangle/setangle.go66
-rw-r--r--packet/command/setinfo/setinfo.go43
-rw-r--r--packet/command/setpause/setpause.go26
-rw-r--r--packet/command/setview/setview.go31
-rw-r--r--packet/command/signonnum/signonnum.go26
-rw-r--r--packet/command/smallkick/smallkick.go17
-rw-r--r--packet/command/sound/sound.go142
-rw-r--r--packet/command/soundlist/soundlist.go83
-rw-r--r--packet/command/spawnbaseline/spawnbaseline.go41
-rw-r--r--packet/command/spawnstatic/spawnstatic.go35
-rw-r--r--packet/command/spawnstaticsound/spawnstaticsound.go68
-rw-r--r--packet/command/stopsound/stopsound.go31
-rw-r--r--packet/command/stringcmd/stringcmd.go31
-rw-r--r--packet/command/stufftext/stufftext.go32
-rw-r--r--packet/command/tempentity/tempentity.go176
-rw-r--r--packet/command/time/time.go31
-rw-r--r--packet/command/tmove/tmove.go36
-rw-r--r--packet/command/updatecolors/updatecolors.go31
-rw-r--r--packet/command/updateentertime/updateentertime.go37
-rw-r--r--packet/command/updatefrags/updatefrags.go37
-rw-r--r--packet/command/updatename/updatename.go37
-rw-r--r--packet/command/updateping/updateping.go37
-rw-r--r--packet/command/updatepl/updatepl.go31
-rw-r--r--packet/command/updatestat/updatestat.go53
-rw-r--r--packet/command/updatestatlong/updatestatlong.go37
-rw-r--r--packet/command/updateuserinfo/updateuserinfo.go43
-rw-r--r--packet/command/upload/upload.go43
-rw-r--r--packet/command/version/version.go31
-rw-r--r--packet/packet.go5
-rw-r--r--packet/svc/connectionless.go67
-rw-r--r--packet/svc/gamedata.go265
-rw-r--r--packet/svc/parse.go18
92 files changed, 5422 insertions, 0 deletions
diff --git a/packet/clc/connectionless.go b/packet/clc/connectionless.go
new file mode 100644
index 0000000..96db112
--- /dev/null
+++ b/packet/clc/connectionless.go
@@ -0,0 +1,64 @@
+package clc
+
+import (
+ "errors"
+
+ "github.com/osm/quake/common/args"
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command"
+ "github.com/osm/quake/packet/command/connect"
+ "github.com/osm/quake/packet/command/getchallenge"
+ "github.com/osm/quake/packet/command/passthrough"
+)
+
+type Connectionless struct {
+ Command command.Command
+}
+
+func (cmd *Connectionless) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutInt32(-1)
+ buf.PutBytes(cmd.Command.Bytes())
+
+ return buf.Bytes()
+}
+
+func parseConnectionless(ctx *context.Context, buf *buffer.Buffer) (*Connectionless, error) {
+ var err error
+ var pkg Connectionless
+
+ if err := buf.Skip(4); err != nil {
+ return nil, err
+ }
+
+ var str string
+ if str, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ args := args.Parse(str)
+ if len(args) != 1 {
+ return nil, errors.New("unexpected length of parsed arguments")
+ }
+
+ arg := args[0]
+
+ var cmd command.Command
+ switch arg.Cmd {
+ case "connect":
+ cmd, err = connect.Parse(ctx, buf, arg)
+ case "getchallenge":
+ cmd, err = getchallenge.Parse(ctx, buf)
+ default:
+ cmd, err = passthrough.Parse(ctx, buf, str)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ pkg.Command = cmd
+
+ return &pkg, nil
+}
diff --git a/packet/clc/gamedata.go b/packet/clc/gamedata.go
new file mode 100644
index 0000000..7ee5890
--- /dev/null
+++ b/packet/clc/gamedata.go
@@ -0,0 +1,101 @@
+package clc
+
+import (
+ "errors"
+
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command"
+ "github.com/osm/quake/packet/command/bad"
+ "github.com/osm/quake/packet/command/delta"
+ "github.com/osm/quake/packet/command/ftevoicechatc"
+ "github.com/osm/quake/packet/command/move"
+ "github.com/osm/quake/packet/command/mvdweapon"
+ "github.com/osm/quake/packet/command/nopc"
+ "github.com/osm/quake/packet/command/stringcmd"
+ "github.com/osm/quake/packet/command/tmove"
+ "github.com/osm/quake/packet/command/upload"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+ "github.com/osm/quake/protocol/mvd"
+)
+
+var ErrUnknownCommandType = errors.New("unknown command type")
+
+type GameData struct {
+ Seq uint32
+ Ack uint32
+ QPort uint16
+ Commands []command.Command
+}
+
+func (gd *GameData) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutUint32(gd.Seq)
+ buf.PutUint32(gd.Ack)
+ buf.PutUint16(gd.QPort)
+
+ for _, c := range gd.Commands {
+ buf.PutBytes(c.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func parseGameData(ctx *context.Context, buf *buffer.Buffer) (*GameData, error) {
+ var err error
+ var pkg GameData
+
+ if pkg.Seq, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+
+ if pkg.Ack, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+
+ if pkg.QPort, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ var cmd command.Command
+ for buf.Off() < buf.Len() {
+ typ, err := buf.ReadByte()
+ if err != nil {
+ return nil, err
+ }
+
+ switch protocol.CommandType(typ) {
+ case protocol.CLCBad:
+ cmd, err = bad.Parse(ctx, buf, protocol.CLCBad)
+ case protocol.CLCNOP:
+ cmd, err = nopc.Parse(ctx, buf)
+ case protocol.CLCDoubleMove:
+ case protocol.CLCMove:
+ cmd, err = move.Parse(ctx, buf)
+ case protocol.CLCStringCmd:
+ cmd, err = stringcmd.Parse(ctx, buf)
+ case protocol.CLCDelta:
+ cmd, err = delta.Parse(ctx, buf)
+ case protocol.CLCTMove:
+ cmd, err = tmove.Parse(ctx, buf)
+ case protocol.CLCUpload:
+ cmd, err = upload.Parse(ctx, buf)
+ case fte.CLCVoiceChat:
+ cmd, err = ftevoicechatc.Parse(ctx, buf)
+ case mvd.CLCWeapon:
+ cmd, err = mvdweapon.Parse(ctx, buf)
+ default:
+ return nil, ErrUnknownCommandType
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ pkg.Commands = append(pkg.Commands, cmd)
+ }
+
+ return &pkg, nil
+}
diff --git a/packet/clc/parse.go b/packet/clc/parse.go
new file mode 100644
index 0000000..594a9dd
--- /dev/null
+++ b/packet/clc/parse.go
@@ -0,0 +1,18 @@
+package clc
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet"
+)
+
+func Parse(ctx *context.Context, data []byte) (packet.Packet, error) {
+ buf := buffer.New(buffer.WithData(data))
+
+ header, _ := buf.PeekInt32()
+ if header == -1 {
+ return parseConnectionless(ctx, buf)
+ }
+
+ return parseGameData(ctx, buf)
+}
diff --git a/packet/command/a2aping/a2aping.go b/packet/command/a2aping/a2aping.go
new file mode 100644
index 0000000..37929a3
--- /dev/null
+++ b/packet/command/a2aping/a2aping.go
@@ -0,0 +1,17 @@
+package a2aping
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.A2APing}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/a2cclientcommand/a2cclientcommand.go b/packet/command/a2cclientcommand/a2cclientcommand.go
new file mode 100644
index 0000000..e2d8011
--- /dev/null
+++ b/packet/command/a2cclientcommand/a2cclientcommand.go
@@ -0,0 +1,37 @@
+package a2cclientcommand
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Command string
+ LocalID string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.A2CClientCommand)
+ buf.PutString(cmd.Command)
+ buf.PutString(cmd.LocalID)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Command, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ if cmd.LocalID, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/a2cprint/a2cprint.go b/packet/command/a2cprint/a2cprint.go
new file mode 100644
index 0000000..01c6051
--- /dev/null
+++ b/packet/command/a2cprint/a2cprint.go
@@ -0,0 +1,52 @@
+package a2cprint
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/ftedownload"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ String string
+
+ IsChunkedDownload bool
+ Download *ftedownload.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.A2CPrint)
+
+ if cmd.IsChunkedDownload && cmd.Download != nil {
+ buf.PutBytes(cmd.Download.Bytes())
+ } else {
+ buf.PutString(cmd.String)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ bytes, _ := buf.PeekBytes(6)
+ cmd.IsChunkedDownload = bytes != nil && string(bytes) == "\\chunk"
+
+ if cmd.IsChunkedDownload {
+ cmd.IsChunkedDownload = true
+
+ if cmd.Download, err = ftedownload.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ }
+
+ if cmd.String, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/bad/bad.go b/packet/command/bad/bad.go
new file mode 100644
index 0000000..dfab5de
--- /dev/null
+++ b/packet/command/bad/bad.go
@@ -0,0 +1,19 @@
+package bad
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Type protocol.CommandType
+}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{byte(cmd.Type)}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer, typ protocol.CommandType) (*Command, error) {
+ return &Command{Type: typ}, nil
+}
diff --git a/packet/command/baseline/baseline.go b/packet/command/baseline/baseline.go
new file mode 100644
index 0000000..bc775bb
--- /dev/null
+++ b/packet/command/baseline/baseline.go
@@ -0,0 +1,89 @@
+package baseline
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+)
+
+type Command struct {
+ AngleSize uint8
+ CoordSize uint8
+
+ ModelIndex byte
+ Frame byte
+ ColorMap byte
+ SkinNum byte
+ Coord [3]float32
+ Angle [3]float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ writeAngle := buf.PutAngle8
+ if cmd.AngleSize == 2 {
+ writeAngle = buf.PutAngle16
+ }
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutByte(cmd.ModelIndex)
+ buf.PutByte(cmd.Frame)
+ buf.PutByte(cmd.ColorMap)
+ buf.PutByte(cmd.SkinNum)
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ writeAngle(cmd.Angle[i])
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.AngleSize = ctx.GetAngleSize()
+ readAngle := buf.GetAngle8
+ if cmd.AngleSize == 2 {
+ readAngle = buf.GetAngle16
+ }
+
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 {
+ readCoord = buf.GetCoord32
+ }
+
+ if cmd.ModelIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Frame, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.ColorMap, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.SkinNum, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Angle[i], err = readAngle(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/bigkick/bigkick.go b/packet/command/bigkick/bigkick.go
new file mode 100644
index 0000000..b5971b5
--- /dev/null
+++ b/packet/command/bigkick/bigkick.go
@@ -0,0 +1,17 @@
+package bigkick
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCBigKick}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/cdtrack/cdtrack.go b/packet/command/cdtrack/cdtrack.go
new file mode 100644
index 0000000..55720bf
--- /dev/null
+++ b/packet/command/cdtrack/cdtrack.go
@@ -0,0 +1,46 @@
+package cdtrack
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ IsNQ bool
+
+ Track byte
+ Loop byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCCDTrack)
+ buf.PutByte(cmd.Track)
+
+ if cmd.IsNQ {
+ buf.PutByte(cmd.Loop)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.IsNQ = ctx.GetIsNQ()
+
+ if cmd.Track, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.IsNQ {
+ if cmd.Loop, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/centerprint/centerprint.go b/packet/command/centerprint/centerprint.go
new file mode 100644
index 0000000..b4bfc8b
--- /dev/null
+++ b/packet/command/centerprint/centerprint.go
@@ -0,0 +1,31 @@
+package centerprint
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ String string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCCenterPrint)
+ buf.PutString(cmd.String)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.String, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/chokecount/chokecount.go b/packet/command/chokecount/chokecount.go
new file mode 100644
index 0000000..7c6d8f8
--- /dev/null
+++ b/packet/command/chokecount/chokecount.go
@@ -0,0 +1,31 @@
+package chokecount
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Count byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCChokeCount)
+ buf.PutByte(cmd.Count)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Count, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/clientdata/clientdata.go b/packet/command/clientdata/clientdata.go
new file mode 100644
index 0000000..d032edd
--- /dev/null
+++ b/packet/command/clientdata/clientdata.go
@@ -0,0 +1,164 @@
+package clientdata
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Bits uint16
+ ViewHeight byte
+ IdealPitch byte
+ PunchAngle [3]byte
+ Velocity [3]byte
+ Items uint32
+ WeaponFrame byte
+ Armor byte
+ Weapon byte
+ Health uint16
+ ActiveAmmo byte
+ AmmoShells byte
+ AmmoNails byte
+ AmmoRockets byte
+ AmmoCells byte
+ ActiveWeapon byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCClientData)
+ buf.PutUint16(cmd.Bits)
+
+ if cmd.Bits&protocol.SUViewHeight != 0 {
+ buf.PutByte(cmd.ViewHeight)
+ }
+
+ if cmd.Bits&protocol.SUIdealPitch != 0 {
+ buf.PutByte(cmd.IdealPitch)
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Bits&(protocol.SUPunch1<<i) != 0 {
+ buf.PutByte(cmd.PunchAngle[i])
+ }
+
+ if cmd.Bits&(protocol.SUVelocity1<<i) != 0 {
+ buf.PutByte(cmd.Velocity[i])
+ }
+ }
+
+ if cmd.Bits&protocol.SUItems != 0 {
+ buf.PutUint32(cmd.Items)
+ }
+
+ if cmd.Bits&protocol.SUWeaponFrame != 0 {
+ buf.PutByte(cmd.WeaponFrame)
+ }
+
+ if cmd.Bits&protocol.SUArmor != 0 {
+ buf.PutByte(cmd.Armor)
+ }
+
+ if cmd.Bits&protocol.SUWeapon != 0 {
+ buf.PutByte(cmd.Weapon)
+ }
+
+ buf.PutUint16(cmd.Health)
+ buf.PutByte(cmd.ActiveAmmo)
+ buf.PutByte(cmd.AmmoShells)
+ buf.PutByte(cmd.AmmoNails)
+ buf.PutByte(cmd.AmmoRockets)
+ buf.PutByte(cmd.AmmoCells)
+ buf.PutByte(cmd.ActiveWeapon)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Bits, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Bits&protocol.SUViewHeight != 0 {
+ if cmd.ViewHeight, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.SUIdealPitch != 0 {
+ if cmd.IdealPitch, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Bits&(protocol.SUPunch1<<i) != 0 {
+ if cmd.PunchAngle[i], err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&(protocol.SUVelocity1<<i) != 0 {
+ if cmd.Velocity[i], err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if cmd.Items, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Bits&protocol.SUWeaponFrame != 0 {
+ if cmd.WeaponFrame, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.SUArmor != 0 {
+ if cmd.Armor, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.SUWeapon != 0 {
+ if cmd.Weapon, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Health, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.ActiveAmmo, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.AmmoShells, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.AmmoNails, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.AmmoRockets, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.AmmoCells, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.ActiveWeapon, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/command.go b/packet/command/command.go
new file mode 100644
index 0000000..3b5034f
--- /dev/null
+++ b/packet/command/command.go
@@ -0,0 +1,5 @@
+package command
+
+type Command interface {
+ Bytes() []byte
+}
diff --git a/packet/command/connect/connect.go b/packet/command/connect/connect.go
new file mode 100644
index 0000000..35527f1
--- /dev/null
+++ b/packet/command/connect/connect.go
@@ -0,0 +1,67 @@
+package connect
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+
+ "github.com/osm/quake/common/args"
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/common/infostring"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Command string
+ Version string
+ QPort uint16
+ ChallengeID string
+ UserInfo *infostring.InfoString
+ Extensions []*protocol.Extension
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutBytes([]byte(cmd.Command + " "))
+ buf.PutBytes([]byte(cmd.Version + " "))
+ buf.PutBytes([]byte(fmt.Sprintf("%v ", cmd.QPort)))
+ buf.PutBytes([]byte(cmd.ChallengeID + " "))
+
+ if cmd.UserInfo != nil {
+ buf.PutBytes(cmd.UserInfo.Bytes())
+ }
+
+ buf.PutByte(0x0a)
+
+ for _, ext := range cmd.Extensions {
+ if ext != nil {
+ buf.PutBytes(ext.Bytes())
+ }
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer, arg args.Arg) (*Command, error) {
+ var cmd Command
+
+ if len(arg.Args) < 4 {
+ return nil, errors.New("unexpected length of args")
+ }
+
+ cmd.Command = arg.Cmd
+ cmd.Version = arg.Args[0]
+
+ qPort, err := strconv.ParseUint(arg.Args[1], 10, 16)
+ if err != nil {
+ return nil, err
+ }
+ cmd.QPort = uint16(qPort)
+
+ cmd.ChallengeID = arg.Args[2]
+ cmd.UserInfo = infostring.Parse(arg.Args[3])
+
+ return &cmd, nil
+}
diff --git a/packet/command/damage/damage.go b/packet/command/damage/damage.go
new file mode 100644
index 0000000..93ddb1e
--- /dev/null
+++ b/packet/command/damage/damage.go
@@ -0,0 +1,61 @@
+package damage
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ CoordSize uint8
+
+ Armor byte
+ Blood byte
+ Coord [3]float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutByte(byte(protocol.SVCDamage))
+ buf.PutByte(cmd.Armor)
+ buf.PutByte(cmd.Blood)
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 {
+ readCoord = buf.GetCoord32
+ }
+
+ if cmd.Armor, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Blood, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/delta/delta.go b/packet/command/delta/delta.go
new file mode 100644
index 0000000..90fdffe
--- /dev/null
+++ b/packet/command/delta/delta.go
@@ -0,0 +1,26 @@
+package delta
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Seq byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.CLCDelta, cmd.Seq}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Seq, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/deltapacketentities/deltapacketentities.go b/packet/command/deltapacketentities/deltapacketentities.go
new file mode 100644
index 0000000..7a1d415
--- /dev/null
+++ b/packet/command/deltapacketentities/deltapacketentities.go
@@ -0,0 +1,43 @@
+package deltapacketentities
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/packetentity"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Index byte
+ Entities []*packetentity.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCDeltaPacketEntities)
+ buf.PutByte(cmd.Index)
+
+ for i := 0; i < len(cmd.Entities); i++ {
+ buf.PutBytes(cmd.Entities[i].Bytes())
+ }
+
+ buf.PutUint16(0x0000)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Index, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Entities, err = packetentity.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/deltausercommand/deltausercommand.go b/packet/command/deltausercommand/deltausercommand.go
new file mode 100644
index 0000000..3a18371
--- /dev/null
+++ b/packet/command/deltausercommand/deltausercommand.go
@@ -0,0 +1,178 @@
+package deltausercommand
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ ProtocolVersion uint32
+
+ Bits byte
+
+ CMAngle1 float32
+ CMAngle2 float32
+ CMAngle3 float32
+ CMForward8 byte
+ CMForward16 uint16
+ CMSide8 byte
+ CMSide16 uint16
+ CMUp8 byte
+ CMUp16 uint16
+ CMButtons byte
+ CMImpulse byte
+ CMMsec byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(cmd.Bits)
+
+ if cmd.Bits&protocol.CMAngle1 != 0 {
+ buf.PutAngle16(cmd.CMAngle1)
+ }
+
+ if cmd.Bits&protocol.CMAngle2 != 0 {
+ buf.PutAngle16(cmd.CMAngle2)
+ }
+
+ if cmd.Bits&protocol.CMAngle3 != 0 {
+ buf.PutAngle16(cmd.CMAngle3)
+ }
+
+ if cmd.ProtocolVersion <= 26 {
+ if cmd.Bits&protocol.CMForward != 0 {
+ buf.PutByte(cmd.CMForward8)
+ }
+
+ if cmd.Bits&protocol.CMSide != 0 {
+ buf.PutByte(cmd.CMSide8)
+ }
+
+ if cmd.Bits&protocol.CMUp != 0 {
+ buf.PutByte(cmd.CMUp8)
+ }
+ } else {
+ if cmd.Bits&protocol.CMForward != 0 {
+ buf.PutUint16(cmd.CMForward16)
+ }
+
+ if cmd.Bits&protocol.CMSide != 0 {
+ buf.PutUint16(cmd.CMSide16)
+ }
+
+ if cmd.Bits&protocol.CMUp != 0 {
+ buf.PutUint16(cmd.CMUp16)
+ }
+ }
+
+ if cmd.Bits&protocol.CMButtons != 0 {
+ buf.PutByte(cmd.CMButtons)
+ }
+
+ if cmd.Bits&protocol.CMImpulse != 0 {
+ buf.PutByte(cmd.CMImpulse)
+ }
+
+ if cmd.ProtocolVersion <= 26 && cmd.Bits&protocol.CMMsec != 0 {
+ buf.PutByte(cmd.CMMsec)
+ } else if cmd.ProtocolVersion >= 27 {
+ buf.PutByte(cmd.CMMsec)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.ProtocolVersion = ctx.GetProtocolVersion()
+
+ if cmd.Bits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Bits&protocol.CMAngle1 != 0 {
+ if cmd.CMAngle1, err = buf.GetAngle16(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.CMAngle2 != 0 {
+ if cmd.CMAngle2, err = buf.GetAngle16(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.CMAngle3 != 0 {
+ if cmd.CMAngle3, err = buf.GetAngle16(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.ProtocolVersion <= 26 {
+ if cmd.Bits&protocol.CMForward != 0 {
+ if cmd.CMForward8, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.CMSide != 0 {
+ if cmd.CMSide8, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.CMUp != 0 {
+ if cmd.CMUp8, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+ } else {
+ if cmd.Bits&protocol.CMForward != 0 {
+ if cmd.CMForward16, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.CMSide != 0 {
+ if cmd.CMSide16, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.CMUp != 0 {
+ if cmd.CMUp16, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+ }
+
+ }
+
+ if cmd.Bits&protocol.CMButtons != 0 {
+ if cmd.CMButtons, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.CMImpulse != 0 {
+ if cmd.CMImpulse, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.ProtocolVersion <= 26 && cmd.Bits&protocol.CMMsec != 0 {
+ if cmd.CMMsec, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ } else if cmd.ProtocolVersion >= 27 {
+ if cmd.CMMsec, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/disconnect/disconnect.go b/packet/command/disconnect/disconnect.go
new file mode 100644
index 0000000..1045ee2
--- /dev/null
+++ b/packet/command/disconnect/disconnect.go
@@ -0,0 +1,42 @@
+package disconnect
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ IsMVD bool
+ IsQWD bool
+
+ String string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCDisconnect)
+
+ if cmd.IsMVD || cmd.IsQWD {
+ buf.PutString(cmd.String)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.IsQWD = ctx.GetIsQWD()
+ cmd.IsMVD = ctx.GetIsMVD()
+
+ if cmd.IsQWD || cmd.IsMVD {
+ if cmd.String, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/download/download.go b/packet/command/download/download.go
new file mode 100644
index 0000000..0872f6f
--- /dev/null
+++ b/packet/command/download/download.go
@@ -0,0 +1,90 @@
+package download
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+)
+
+type Command struct {
+ FTEProtocolExtension uint32
+
+ Size16 int16
+ Size32 int32
+ Percent byte
+ Number int32
+ Name string
+ Data []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCDownload)
+
+ if cmd.FTEProtocolExtension&fte.ExtensionChunkedDownloads != 0 {
+ buf.PutInt32(cmd.Number)
+
+ if cmd.Number == -1 {
+ buf.PutInt32(cmd.Size32)
+ buf.PutString(cmd.Name)
+ } else {
+ buf.PutBytes(cmd.Data)
+ }
+ } else {
+ buf.PutInt16(cmd.Size16)
+ buf.PutByte(cmd.Percent)
+
+ if cmd.Size16 != -1 {
+ buf.PutBytes(cmd.Data)
+ }
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.FTEProtocolExtension = ctx.GetFTEProtocolExtension()
+
+ if cmd.FTEProtocolExtension&fte.ExtensionChunkedDownloads != 0 {
+ if cmd.Number, err = buf.GetInt32(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Number < 0 {
+ if cmd.Size32, err = buf.GetInt32(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Name, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+ }
+
+ if cmd.Data, err = buf.GetBytes(protocol.DownloadBlockSize); err != nil {
+ return nil, err
+ }
+ } else {
+ if cmd.Size16, err = buf.GetInt16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Percent, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Size16 > 0 {
+ if cmd.Data, err = buf.GetBytes(int(cmd.Size16)); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/entgravity/entgravity.go b/packet/command/entgravity/entgravity.go
new file mode 100644
index 0000000..c6567ce
--- /dev/null
+++ b/packet/command/entgravity/entgravity.go
@@ -0,0 +1,31 @@
+package entgravity
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ EntGravity float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCEntGravity)
+ buf.PutFloat32(cmd.EntGravity)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.EntGravity, err = buf.GetFloat32(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/fastupdate/fastupdate.go b/packet/command/fastupdate/fastupdate.go
new file mode 100644
index 0000000..e6e344c
--- /dev/null
+++ b/packet/command/fastupdate/fastupdate.go
@@ -0,0 +1,182 @@
+package fastupdate
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Bits byte
+ MoreBits byte
+ Entity8 byte
+ Entity16 uint16
+ Model byte
+ Frame byte
+ ColorMap byte
+ Skin byte
+ Effects byte
+ Origin [3]float32
+ Angle [3]float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(cmd.Bits)
+
+ bits16 := uint16(cmd.Bits)
+ bits16 &= 127
+
+ if bits16&protocol.NQUMoreBits != 0 {
+ buf.PutByte(cmd.MoreBits)
+ bits16 |= uint16(cmd.MoreBits) << 8
+ }
+
+ if bits16&protocol.NQULongEntity != 0 {
+ buf.PutUint16(cmd.Entity16)
+ } else {
+ buf.PutByte(cmd.Entity8)
+ }
+
+ if bits16&protocol.NQUModel != 0 {
+ buf.PutByte(cmd.Model)
+ }
+
+ if bits16&protocol.NQUFrame != 0 {
+ buf.PutByte(cmd.Frame)
+ }
+
+ if bits16&protocol.NQUColorMap != 0 {
+ buf.PutByte(cmd.ColorMap)
+ }
+
+ if bits16&protocol.NQUSkin != 0 {
+ buf.PutByte(cmd.Skin)
+ }
+
+ if bits16&protocol.NQUEffects != 0 {
+ buf.PutByte(cmd.Effects)
+ }
+
+ if bits16&protocol.NQUOrigin1 != 0 {
+ buf.PutCoord16(cmd.Origin[0])
+ }
+
+ if bits16&protocol.NQUAngle1 != 0 {
+ buf.PutAngle8(cmd.Angle[0])
+ }
+
+ if bits16&protocol.NQUOrigin2 != 0 {
+ buf.PutCoord16(cmd.Origin[1])
+ }
+
+ if bits16&protocol.NQUAngle2 != 0 {
+ buf.PutAngle8(cmd.Angle[1])
+ }
+
+ if bits16&protocol.NQUOrigin3 != 0 {
+ buf.PutCoord16(cmd.Origin[2])
+ }
+
+ if bits16&protocol.NQUAngle3 != 0 {
+ buf.PutAngle8(cmd.Angle[2])
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer, bits byte) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.Bits = bits
+
+ bits16 := uint16(bits)
+ bits16 &= 127
+
+ if bits16&protocol.NQUMoreBits != 0 {
+ if cmd.MoreBits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ bits16 |= uint16(cmd.MoreBits) << 8
+ }
+
+ if bits16&protocol.NQULongEntity != 0 {
+ if cmd.Entity16, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+ } else {
+ if cmd.Entity8, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUModel != 0 {
+ if cmd.Model, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUFrame != 0 {
+ if cmd.Frame, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUColorMap != 0 {
+ if cmd.ColorMap, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUSkin != 0 {
+ if cmd.Skin, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUEffects != 0 {
+ if cmd.Effects, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUOrigin1 != 0 {
+ if cmd.Origin[0], err = buf.GetCoord16(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUAngle1 != 0 {
+ if cmd.Angle[0], err = buf.GetAngle8(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUOrigin2 != 0 {
+ if cmd.Origin[1], err = buf.GetCoord16(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUAngle2 != 0 {
+ if cmd.Angle[1], err = buf.GetAngle8(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUOrigin3 != 0 {
+ if cmd.Origin[2], err = buf.GetCoord16(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits16&protocol.NQUAngle3 != 0 {
+ if cmd.Angle[2], err = buf.GetAngle8(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/finale/finale.go b/packet/command/finale/finale.go
new file mode 100644
index 0000000..c49ec6d
--- /dev/null
+++ b/packet/command/finale/finale.go
@@ -0,0 +1,31 @@
+package finale
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ String string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCFinale)
+ buf.PutString(cmd.String)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.String, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/foundsecret/foundsecret.go b/packet/command/foundsecret/foundsecret.go
new file mode 100644
index 0000000..b63517a
--- /dev/null
+++ b/packet/command/foundsecret/foundsecret.go
@@ -0,0 +1,17 @@
+package foundsecret
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCFoundSecret}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/ftedownload/ftedownload.go b/packet/command/ftedownload/ftedownload.go
new file mode 100644
index 0000000..f9b1ee6
--- /dev/null
+++ b/packet/command/ftedownload/ftedownload.go
@@ -0,0 +1,54 @@
+package ftedownload
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/download"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Number int32
+ DownloadID int32
+ Command byte
+ Chunk *download.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.A2CPrint)
+ buf.PutBytes([]byte("\\chunk"))
+ buf.PutInt32(cmd.DownloadID)
+
+ if cmd.Chunk != nil {
+ buf.PutByte(cmd.Command)
+ buf.PutInt32(cmd.Number)
+ buf.PutBytes(cmd.Chunk.Data)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if err := buf.Skip(6); err != nil {
+ return nil, err
+ }
+
+ if cmd.Number, err = buf.GetInt32(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Command, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Chunk, err = download.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ return nil, nil
+}
diff --git a/packet/command/ftemodellist/ftemodellist.go b/packet/command/ftemodellist/ftemodellist.go
new file mode 100644
index 0000000..b9af505
--- /dev/null
+++ b/packet/command/ftemodellist/ftemodellist.go
@@ -0,0 +1,57 @@
+package ftemodellist
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol/fte"
+)
+
+type Command struct {
+ NumModels uint16
+ Models []string
+ More byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(fte.SVCModelListShort)
+ buf.PutUint16(cmd.NumModels)
+
+ for i := 0; i < len(cmd.Models); i++ {
+ buf.PutString(cmd.Models[i])
+ }
+ buf.PutByte(0x00)
+
+ buf.PutByte(cmd.More)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.NumModels, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ for {
+ var model string
+ if model, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ if model == "" {
+ break
+ }
+
+ cmd.Models = append(cmd.Models, model)
+ }
+
+ if cmd.More, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/ftespawnbaseline/ftespawnbaseline.go b/packet/command/ftespawnbaseline/ftespawnbaseline.go
new file mode 100644
index 0000000..39abd1e
--- /dev/null
+++ b/packet/command/ftespawnbaseline/ftespawnbaseline.go
@@ -0,0 +1,41 @@
+package ftespawnbaseline
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/packetentitydelta"
+ "github.com/osm/quake/protocol/fte"
+)
+
+type Command struct {
+ Index uint16
+ Delta *packetentitydelta.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(fte.SVCSpawnBaseline)
+ buf.PutUint16(cmd.Index)
+
+ if cmd.Delta != nil {
+ buf.PutBytes(cmd.Delta.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Index, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Delta, err = packetentitydelta.Parse(ctx, buf, cmd.Index); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/ftespawnstatic/ftespawnstatic.go b/packet/command/ftespawnstatic/ftespawnstatic.go
new file mode 100644
index 0000000..b093424
--- /dev/null
+++ b/packet/command/ftespawnstatic/ftespawnstatic.go
@@ -0,0 +1,41 @@
+package ftespawnstatic
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/packetentitydelta"
+ "github.com/osm/quake/protocol/fte"
+)
+
+type Command struct {
+ Bits uint16
+ Delta *packetentitydelta.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(fte.SVCSpawnStatic)
+ buf.PutUint16(cmd.Bits)
+
+ if cmd.Delta != nil {
+ buf.PutBytes(cmd.Delta.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Bits, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Delta, err = packetentitydelta.Parse(ctx, buf, cmd.Bits); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/ftevoicechatc/ftevoicechatc.go b/packet/command/ftevoicechatc/ftevoicechatc.go
new file mode 100644
index 0000000..369fb12
--- /dev/null
+++ b/packet/command/ftevoicechatc/ftevoicechatc.go
@@ -0,0 +1,49 @@
+package ftevoicechatc
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol/fte"
+)
+
+type Command struct {
+ Gen byte
+ Seq byte
+ Size uint16
+ Data []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(fte.CLCVoiceChat)
+ buf.PutByte(cmd.Gen)
+ buf.PutByte(cmd.Seq)
+ buf.PutUint16(cmd.Size)
+ buf.PutBytes(cmd.Data)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Gen, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Seq, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Size, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Data, err = buf.GetBytes(int(cmd.Size)); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/ftevoicechats/ftevoicechats.go b/packet/command/ftevoicechats/ftevoicechats.go
new file mode 100644
index 0000000..be4109d
--- /dev/null
+++ b/packet/command/ftevoicechats/ftevoicechats.go
@@ -0,0 +1,55 @@
+package ftevoicechats
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol/fte"
+)
+
+type Command struct {
+ Sender byte
+ Gen byte
+ Seq byte
+ Size uint16
+ Data []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(fte.SVCVoiceChat)
+ buf.PutByte(cmd.Sender)
+ buf.PutByte(cmd.Gen)
+ buf.PutByte(cmd.Seq)
+ buf.PutUint16(cmd.Size)
+ buf.PutBytes(cmd.Data)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Sender, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Gen, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Seq, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Size, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Data, err = buf.GetBytes(int(cmd.Size)); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/getchallenge/getchallenge.go b/packet/command/getchallenge/getchallenge.go
new file mode 100644
index 0000000..090f78d
--- /dev/null
+++ b/packet/command/getchallenge/getchallenge.go
@@ -0,0 +1,16 @@
+package getchallenge
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte("getchallenge\n")
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/intermission/intermission.go b/packet/command/intermission/intermission.go
new file mode 100644
index 0000000..41177ff
--- /dev/null
+++ b/packet/command/intermission/intermission.go
@@ -0,0 +1,72 @@
+package intermission
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ AngleSize uint8
+ CoordSize uint8
+
+ Coord [3]float32
+ Angle [3]float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ writeAngle := buf.PutAngle8
+ if cmd.AngleSize == 2 {
+ writeAngle = buf.PutAngle16
+ }
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutByte(protocol.SVCIntermission)
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+
+ for i := 0; i < 3; i++ {
+ writeAngle(cmd.Angle[i])
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.AngleSize = ctx.GetAngleSize()
+ readAngle := buf.GetAngle8
+ if cmd.AngleSize == 2 {
+ readAngle = buf.GetAngle16
+ }
+
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 {
+ readCoord = buf.GetCoord32
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Angle[i], err = readAngle(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/ip/ip.go b/packet/command/ip/ip.go
new file mode 100644
index 0000000..2d658a1
--- /dev/null
+++ b/packet/command/ip/ip.go
@@ -0,0 +1,22 @@
+package ip
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+)
+
+type Command struct {
+ String string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutString(cmd.String)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/killedmonster/killedmonster.go b/packet/command/killedmonster/killedmonster.go
new file mode 100644
index 0000000..1de4ad0
--- /dev/null
+++ b/packet/command/killedmonster/killedmonster.go
@@ -0,0 +1,17 @@
+package killedmonster
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCKilledMonster}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/lightstyle/lightstyle.go b/packet/command/lightstyle/lightstyle.go
new file mode 100644
index 0000000..0b66c5c
--- /dev/null
+++ b/packet/command/lightstyle/lightstyle.go
@@ -0,0 +1,37 @@
+package lightstyle
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Index byte
+ Command string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCLightStyle)
+ buf.PutByte(cmd.Index)
+ buf.PutString(cmd.Command)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Index, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Command, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/maxspeed/maxspeed.go b/packet/command/maxspeed/maxspeed.go
new file mode 100644
index 0000000..ee11b79
--- /dev/null
+++ b/packet/command/maxspeed/maxspeed.go
@@ -0,0 +1,31 @@
+package maxspeed
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Command float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCMaxSpeed)
+ buf.PutFloat32(cmd.Command)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Command, err = buf.GetFloat32(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/modellist/modellist.go b/packet/command/modellist/modellist.go
new file mode 100644
index 0000000..2df83b0
--- /dev/null
+++ b/packet/command/modellist/modellist.go
@@ -0,0 +1,83 @@
+package modellist
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ ProtocolVersion uint32
+ NumModels byte
+ Models []string
+ Index byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCModelList)
+
+ if cmd.ProtocolVersion >= 26 {
+ buf.PutByte(cmd.NumModels)
+
+ for i := 0; i < len(cmd.Models); i++ {
+ buf.PutString(cmd.Models[i])
+ }
+ buf.PutByte(0x00)
+
+ buf.PutByte(cmd.Index)
+ } else {
+ for i := 0; i < len(cmd.Models); i++ {
+ buf.PutString(cmd.Models[i])
+ }
+ buf.PutByte(0x00)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.ProtocolVersion = ctx.GetProtocolVersion()
+
+ if cmd.ProtocolVersion >= 26 {
+ if cmd.NumModels, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for {
+ var model string
+ if model, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ if model == "" {
+ break
+ }
+
+ cmd.Models = append(cmd.Models, model)
+ }
+
+ if cmd.Index, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ } else {
+ for {
+ var model string
+ if model, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ if model == "" {
+ break
+ }
+
+ cmd.Models = append(cmd.Models, model)
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/move/move.go b/packet/command/move/move.go
new file mode 100644
index 0000000..d52bb27
--- /dev/null
+++ b/packet/command/move/move.go
@@ -0,0 +1,79 @@
+package move
+
+import (
+ "slices"
+
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/common/crc"
+ "github.com/osm/quake/packet/command/deltausercommand"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Checksum byte
+ Lossage byte
+ Null *deltausercommand.Command
+ Old *deltausercommand.Command
+ New *deltausercommand.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.CLCMove)
+ buf.PutByte(cmd.Checksum)
+ buf.PutByte(cmd.Lossage)
+
+ if cmd.Null != nil {
+ buf.PutBytes(cmd.Null.Bytes())
+ }
+
+ if cmd.Old != nil {
+ buf.PutBytes(cmd.Old.Bytes())
+ }
+
+ if cmd.New != nil {
+ buf.PutBytes(cmd.New.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func (cmd *Command) GetChecksum(sequence uint32) byte {
+ b := slices.Concat(
+ []byte{cmd.Lossage},
+ cmd.Null.Bytes(),
+ cmd.Old.Bytes(),
+ cmd.New.Bytes(),
+ )
+
+ return crc.Byte(b, int(sequence))
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Checksum, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Lossage, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Null, err = deltausercommand.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ if cmd.Old, err = deltausercommand.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ if cmd.New, err = deltausercommand.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/muzzleflash/muzzleflash.go b/packet/command/muzzleflash/muzzleflash.go
new file mode 100644
index 0000000..9db50b2
--- /dev/null
+++ b/packet/command/muzzleflash/muzzleflash.go
@@ -0,0 +1,31 @@
+package muzzleflash
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex uint16
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCMuzzleFlash)
+ buf.PutUint16(cmd.PlayerIndex)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/mvdweapon/mvdweapon.go b/packet/command/mvdweapon/mvdweapon.go
new file mode 100644
index 0000000..6e726f5
--- /dev/null
+++ b/packet/command/mvdweapon/mvdweapon.go
@@ -0,0 +1,54 @@
+package mvdweapon
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol/mvd"
+)
+
+type Command struct {
+ Bits byte
+ Age byte
+ Weapons []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(mvd.CLCWeapon)
+ buf.PutByte(cmd.Bits)
+ buf.PutByte(cmd.Age)
+ buf.PutBytes(cmd.Weapons)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Bits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Bits&mvd.CLCWeaponForgetRanking != 0 {
+ if cmd.Age, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ for {
+ weapon, err := buf.ReadByte()
+ if err != nil {
+ return nil, err
+ }
+
+ if weapon == 0 {
+ break
+ }
+
+ cmd.Weapons = append(cmd.Weapons, weapon)
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/nails/nails.go b/packet/command/nails/nails.go
new file mode 100644
index 0000000..a4a3987
--- /dev/null
+++ b/packet/command/nails/nails.go
@@ -0,0 +1,50 @@
+package nails
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Count byte
+ Command []Nail
+}
+
+type Nail struct {
+ Bits []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCNails)
+ buf.PutByte(cmd.Count)
+
+ for i := 0; i < len(cmd.Command); i++ {
+ buf.PutBytes(cmd.Command[i].Bits)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Count, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < int(cmd.Count); i++ {
+ var nail Nail
+
+ if nail.Bits, err = buf.GetBytes(6); err != nil {
+ return nil, err
+ }
+
+ cmd.Command = append(cmd.Command, nail)
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/nails2/nails2.go b/packet/command/nails2/nails2.go
new file mode 100644
index 0000000..9bf8038
--- /dev/null
+++ b/packet/command/nails2/nails2.go
@@ -0,0 +1,56 @@
+package nails2
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Count byte
+ Nails []Nail2
+}
+
+type Nail2 struct {
+ Index byte
+ Bits []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCNails2)
+ buf.PutByte(cmd.Count)
+
+ for i := 0; i < len(cmd.Nails); i++ {
+ buf.PutByte(cmd.Nails[i].Index)
+ buf.PutBytes(cmd.Nails[i].Bits)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Count, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < int(cmd.Count); i++ {
+ var nail Nail2
+
+ if nail.Index, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if nail.Bits, err = buf.GetBytes(6); err != nil {
+ return nil, err
+ }
+
+ cmd.Nails = append(cmd.Nails, nail)
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/nopc/nopc.go b/packet/command/nopc/nopc.go
new file mode 100644
index 0000000..5610433
--- /dev/null
+++ b/packet/command/nopc/nopc.go
@@ -0,0 +1,17 @@
+package nopc
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.CLCNOP}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/nops/nops.go b/packet/command/nops/nops.go
new file mode 100644
index 0000000..bcf354f
--- /dev/null
+++ b/packet/command/nops/nops.go
@@ -0,0 +1,17 @@
+package nops
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCNOP}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/packetentities/packetentities.go b/packet/command/packetentities/packetentities.go
new file mode 100644
index 0000000..5ce85ff
--- /dev/null
+++ b/packet/command/packetentities/packetentities.go
@@ -0,0 +1,37 @@
+package packetentities
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/packetentity"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Entities []*packetentity.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCPacketEntities)
+
+ for i := 0; i < len(cmd.Entities); i++ {
+ buf.PutBytes(cmd.Entities[i].Bytes())
+ }
+
+ buf.PutUint16(0x0000)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Entities, err = packetentity.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/packetentity/packetentity.go b/packet/command/packetentity/packetentity.go
new file mode 100644
index 0000000..6accec7
--- /dev/null
+++ b/packet/command/packetentity/packetentity.go
@@ -0,0 +1,90 @@
+package packetentity
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/packetentitydelta"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+)
+
+type Command struct {
+ FTEProtocolExtension uint32
+
+ Bits uint16
+ MoreBits byte
+ EvenMoreBits byte
+ PacketEntityDelta *packetentitydelta.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutUint16(cmd.Bits)
+
+ if cmd.Bits == 0 {
+ goto end
+ }
+
+ if cmd.Bits&protocol.URemove != 0 {
+ if cmd.Bits&protocol.UMoreBits != 0 &&
+ cmd.FTEProtocolExtension&fte.ExtensionEntityDbl != 0 {
+ buf.PutByte(cmd.MoreBits)
+
+ if cmd.MoreBits&fte.UEvenMore != 0 {
+ buf.PutByte(cmd.EvenMoreBits)
+ }
+ }
+ goto end
+ }
+
+ if cmd.PacketEntityDelta != nil {
+ buf.PutBytes(cmd.PacketEntityDelta.Bytes())
+ }
+
+end:
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) ([]*Command, error) {
+ var err error
+ var cmds []*Command
+
+ for {
+ var cmd Command
+ cmd.FTEProtocolExtension = ctx.GetFTEProtocolExtension()
+
+ if cmd.Bits, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Bits == 0 {
+ break
+ }
+
+ if cmd.Bits&protocol.URemove != 0 {
+ if cmd.Bits&protocol.UMoreBits != 0 &&
+ cmd.FTEProtocolExtension&fte.ExtensionEntityDbl != 0 {
+ if cmd.MoreBits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.MoreBits&fte.UEvenMore != 0 {
+ if cmd.EvenMoreBits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+ }
+ goto next
+ }
+
+ cmd.PacketEntityDelta, err = packetentitydelta.Parse(ctx, buf, cmd.Bits)
+ if err != nil {
+ return nil, err
+ }
+ next:
+ cmds = append(cmds, &cmd)
+ }
+
+ return cmds, nil
+}
diff --git a/packet/command/packetentitydelta/packetentitydelta.go b/packet/command/packetentitydelta/packetentitydelta.go
new file mode 100644
index 0000000..d131020
--- /dev/null
+++ b/packet/command/packetentitydelta/packetentitydelta.go
@@ -0,0 +1,254 @@
+package packetentitydelta
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+ "github.com/osm/quake/protocol/mvd"
+)
+
+type Command struct {
+ AngleSize uint8
+ CoordSize uint8
+ FTEProtocolExtension uint32
+ MVDProtocolExtension uint32
+
+ bits uint16
+ Number uint16
+
+ MoreBits byte
+ EvenMoreBits byte
+ YetMoreBits byte
+
+ ModelIndex byte
+ Frame byte
+ ColorMap byte
+ Skin byte
+ Effects byte
+ Coord [3]float32
+ Angle [3]float32
+ Trans byte
+ ColorMod [3]byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ writeAngle := buf.PutAngle8
+ if cmd.AngleSize == 2 {
+ writeAngle = buf.PutAngle16
+ }
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 || cmd.MVDProtocolExtension&mvd.ExtensionFloatCoords != 0 {
+ writeCoord = buf.PutCoord32
+ }
+
+ bits := cmd.bits
+ bits &= ^uint16(511)
+
+ if bits&protocol.UMoreBits != 0 {
+ buf.PutByte(cmd.MoreBits)
+ bits |= uint16(cmd.MoreBits)
+ }
+
+ var moreBits uint16
+ if bits&fte.UEvenMore != 0 && cmd.FTEProtocolExtension > 0 {
+ buf.PutByte(cmd.EvenMoreBits)
+ moreBits = uint16(cmd.EvenMoreBits)
+
+ if cmd.EvenMoreBits&fte.UYetMore != 0 {
+ buf.PutByte(cmd.YetMoreBits)
+ moreBits |= uint16(cmd.YetMoreBits) << 8
+ }
+ }
+
+ if bits&protocol.UModel != 0 {
+ buf.PutByte(cmd.ModelIndex)
+ }
+
+ if bits&protocol.UFrame != 0 {
+ buf.PutByte(cmd.Frame)
+ }
+
+ if bits&protocol.UColorMap != 0 {
+ buf.PutByte(cmd.ColorMap)
+ }
+
+ if bits&protocol.USkin != 0 {
+ buf.PutByte(cmd.Skin)
+ }
+
+ if bits&protocol.UEffects != 0 {
+ buf.PutByte(cmd.Effects)
+ }
+
+ if bits&protocol.UOrigin1 != 0 {
+ writeCoord(cmd.Coord[0])
+ }
+
+ if bits&protocol.UAngle1 != 0 {
+ writeAngle(cmd.Angle[0])
+ }
+
+ if bits&protocol.UOrigin2 != 0 {
+ writeCoord(cmd.Coord[1])
+ }
+
+ if bits&protocol.UAngle2 != 0 {
+ writeAngle(cmd.Angle[1])
+ }
+
+ if bits&protocol.UOrigin3 != 0 {
+ writeCoord(cmd.Coord[2])
+ }
+
+ if bits&protocol.UAngle3 != 0 {
+ writeAngle(cmd.Angle[2])
+ }
+
+ if moreBits&fte.UTrans != 0 && cmd.FTEProtocolExtension&fte.ExtensionTrans != 0 {
+ buf.PutByte(cmd.Trans)
+ }
+
+ if moreBits&fte.UColorMod != 0 && cmd.FTEProtocolExtension&fte.ExtensionColorMod != 0 {
+ for i := 0; i < len(cmd.ColorMod); i++ {
+ buf.PutByte(cmd.ColorMod[i])
+ }
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer, bits uint16) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.FTEProtocolExtension = ctx.GetFTEProtocolExtension()
+ cmd.MVDProtocolExtension = ctx.GetMVDProtocolExtension()
+
+ cmd.AngleSize = ctx.GetAngleSize()
+ readAngle := buf.GetAngle8
+ if cmd.AngleSize == 2 {
+ readAngle = buf.GetAngle16
+ }
+
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 || cmd.MVDProtocolExtension&mvd.ExtensionFloatCoords != 0 {
+ readCoord = buf.GetCoord32
+ }
+
+ cmd.Number = bits & 511
+ cmd.bits = bits
+
+ bits &= ^uint16(511)
+
+ if bits&protocol.UMoreBits != 0 {
+ if cmd.MoreBits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ bits |= uint16(cmd.MoreBits)
+ }
+
+ var moreBits uint16
+ if bits&fte.UEvenMore != 0 && cmd.FTEProtocolExtension > 0 {
+ if cmd.EvenMoreBits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ moreBits = uint16(cmd.EvenMoreBits)
+
+ if cmd.EvenMoreBits&fte.UYetMore != 0 {
+ if cmd.YetMoreBits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ yetMoreBits := uint16(cmd.YetMoreBits)
+
+ moreBits |= yetMoreBits << 8
+ }
+ }
+
+ if bits&protocol.UModel != 0 {
+ if cmd.ModelIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UFrame != 0 {
+ if cmd.Frame, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UColorMap != 0 {
+ if cmd.ColorMap, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.USkin != 0 {
+ if cmd.Skin, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UEffects != 0 {
+ if cmd.Effects, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UOrigin1 != 0 {
+ if cmd.Coord[0], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UAngle1 != 0 {
+ if cmd.Angle[0], err = readAngle(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UOrigin2 != 0 {
+ if cmd.Coord[1], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UAngle2 != 0 {
+ if cmd.Angle[1], err = readAngle(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UOrigin3 != 0 {
+ if cmd.Coord[2], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.UAngle3 != 0 {
+ if cmd.Angle[2], err = readAngle(); err != nil {
+ return nil, err
+ }
+ }
+
+ if moreBits&fte.UTrans != 0 && cmd.FTEProtocolExtension&fte.ExtensionTrans != 0 {
+ if cmd.Trans, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if moreBits&fte.UColorMod != 0 && cmd.FTEProtocolExtension&fte.ExtensionColorMod != 0 {
+ for i := 0; i < len(cmd.ColorMod); i++ {
+ if cmd.ColorMod[i], err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/particle/particle.go b/packet/command/particle/particle.go
new file mode 100644
index 0000000..b1f3942
--- /dev/null
+++ b/packet/command/particle/particle.go
@@ -0,0 +1,85 @@
+package particle
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ AngleSize uint8
+ CoordSize uint8
+
+ Coord [3]float32
+ Angle [3]float32
+ Count byte
+ Color byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ writeAngle := buf.PutAngle8
+ if cmd.AngleSize == 2 {
+ writeAngle = buf.PutAngle16
+ }
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutByte(protocol.SVCParticle)
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+
+ for i := 0; i < 3; i++ {
+ writeAngle(cmd.Angle[i])
+ }
+
+ buf.PutByte(cmd.Count)
+ buf.PutByte(cmd.Color)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.AngleSize = ctx.GetAngleSize()
+ readAngle := buf.GetAngle8
+ if cmd.AngleSize == 2 {
+ readAngle = buf.GetAngle16
+ }
+
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 {
+ readCoord = buf.GetCoord32
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Angle[i], err = readAngle(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Count, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Color, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/passthrough/passthrough.go b/packet/command/passthrough/passthrough.go
new file mode 100644
index 0000000..58c937c
--- /dev/null
+++ b/packet/command/passthrough/passthrough.go
@@ -0,0 +1,38 @@
+package passthrough
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+)
+
+type Command struct {
+ Data []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutBytes(cmd.Data)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer, str string) (*Command, error) {
+ var cmd Command
+
+ if len(str) > 0 {
+ cmd.Data = []byte(str)
+ }
+
+ remaining := buf.Len() - buf.Off()
+ if remaining > 0 {
+ data, err := buf.GetBytes(remaining)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd.Data = append(cmd.Data, data...)
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/playerinfo/playerinfo.go b/packet/command/playerinfo/playerinfo.go
new file mode 100644
index 0000000..962101b
--- /dev/null
+++ b/packet/command/playerinfo/playerinfo.go
@@ -0,0 +1,337 @@
+package playerinfo
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/deltausercommand"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+ "github.com/osm/quake/protocol/mvd"
+)
+
+const fteExtensions uint32 = fte.ExtensionHullSize |
+ fte.ExtensionTrans |
+ fte.ExtensionScale |
+ fte.ExtensionFatness
+
+type Command struct {
+ IsMVD bool
+
+ Index byte
+ Default *CommandDefault
+ MVD *CommandMVD
+}
+
+type CommandDefault struct {
+ CoordSize uint8
+ MVDProtocolExtension uint32
+ FTEProtocolExtension uint32
+
+ Bits uint16
+ ExtraBits byte
+ Coord [3]float32
+ Frame byte
+ Msec byte
+ DeltaUserCommand *deltausercommand.Command
+ Velocity [3]uint16
+ ModelIndex byte
+ SkinNum byte
+ Effects byte
+ WeaponFrame byte
+}
+
+type CommandMVD struct {
+ CoordSize uint8
+
+ Bits uint16
+ Frame byte
+ Coord [3]float32
+ Angle [3]float32
+ ModelIndex byte
+ SkinNum byte
+ Effects byte
+ WeaponFrame byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCPlayerInfo)
+ buf.PutByte(cmd.Index)
+
+ if cmd.IsMVD && cmd.MVD != nil {
+ buf.PutBytes(cmd.MVD.Bytes())
+ } else if cmd.Default != nil {
+ buf.PutBytes(cmd.Default.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func (cmd *CommandDefault) Bytes() []byte {
+ buf := buffer.New()
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 ||
+ cmd.MVDProtocolExtension&mvd.ExtensionFloatCoords != 0 ||
+ cmd.FTEProtocolExtension&fteExtensions != 0 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutUint16(cmd.Bits)
+
+ if cmd.FTEProtocolExtension&fteExtensions != 0 && cmd.Bits&fte.UFarMore != 0 {
+ buf.PutByte(cmd.ExtraBits)
+ }
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+
+ buf.PutByte(cmd.Frame)
+
+ if cmd.Bits&protocol.PFMsec != 0 {
+ buf.PutByte(cmd.Msec)
+ }
+
+ if cmd.Bits&protocol.PFCommand != 0 {
+ buf.PutBytes(cmd.DeltaUserCommand.Bytes())
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Bits&(protocol.PFVelocity1<<i) != 0 {
+ buf.PutUint16(cmd.Velocity[i])
+ }
+ }
+
+ if cmd.Bits&protocol.PFModel != 0 {
+ buf.PutByte(cmd.ModelIndex)
+ }
+
+ if cmd.Bits&protocol.PFSkinNum != 0 {
+ buf.PutByte(cmd.SkinNum)
+ }
+
+ if cmd.Bits&protocol.PFEffects != 0 {
+ buf.PutByte(cmd.Effects)
+ }
+
+ if cmd.Bits&protocol.PFWeaponFrame != 0 {
+ buf.PutByte(cmd.WeaponFrame)
+ }
+
+ return buf.Bytes()
+}
+
+func (cmd *CommandMVD) Bytes() []byte {
+ buf := buffer.New()
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutUint16(cmd.Bits)
+ buf.PutByte(cmd.Frame)
+
+ for i := 0; i < 3; i++ {
+ if cmd.Bits&(protocol.DFOrigin<<i) != 0 {
+ writeCoord(cmd.Coord[i])
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Bits&(protocol.DFAngles<<i) != 0 {
+ buf.PutAngle16(cmd.Angle[i])
+ }
+ }
+
+ if cmd.Bits&protocol.DFModel != 0 {
+ buf.PutByte(cmd.ModelIndex)
+ }
+
+ if cmd.Bits&protocol.DFSkinNum != 0 {
+ buf.PutByte(cmd.SkinNum)
+ }
+
+ if cmd.Bits&protocol.DFEffects != 0 {
+ buf.PutByte(cmd.Effects)
+ }
+
+ if cmd.Bits&protocol.DFWeaponFrame != 0 {
+ buf.PutByte(cmd.WeaponFrame)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.IsMVD = ctx.GetIsMVD()
+
+ if cmd.Index, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.IsMVD {
+ if cmd.MVD, err = parseCommandMVD(ctx, buf); err != nil {
+ return nil, err
+ }
+ } else {
+ if cmd.Default, err = parseCommandDefault(ctx, buf); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
+
+func parseCommandDefault(ctx *context.Context, buf *buffer.Buffer) (*CommandDefault, error) {
+ var err error
+ var cmd CommandDefault
+
+ cmd.FTEProtocolExtension = ctx.GetFTEProtocolExtension()
+ cmd.MVDProtocolExtension = ctx.GetMVDProtocolExtension()
+ cmd.CoordSize = ctx.GetCoordSize()
+
+ readCoord := buf.GetCoord16
+
+ if cmd.CoordSize == 4 ||
+ cmd.MVDProtocolExtension&mvd.ExtensionFloatCoords != 0 ||
+ cmd.FTEProtocolExtension&fteExtensions != 0 {
+ readCoord = buf.GetCoord32
+ }
+
+ if cmd.Bits, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ var bits uint32 = uint32(cmd.Bits)
+
+ if cmd.FTEProtocolExtension&fteExtensions != 0 && bits&fte.UFarMore != 0 {
+ if cmd.ExtraBits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ bits = (uint32(cmd.ExtraBits) << 16) | uint32(cmd.Bits)
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Frame, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if bits&protocol.PFMsec != 0 {
+ if cmd.Msec, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.PFCommand != 0 {
+ if cmd.DeltaUserCommand, err = deltausercommand.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if bits&(protocol.PFVelocity1<<i) != 0 {
+ if cmd.Velocity[i], err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if bits&protocol.PFModel != 0 {
+ if cmd.ModelIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.PFSkinNum != 0 {
+ if cmd.SkinNum, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.PFEffects != 0 {
+ if cmd.Effects, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if bits&protocol.PFWeaponFrame != 0 {
+ if cmd.WeaponFrame, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
+
+func parseCommandMVD(ctx *context.Context, buf *buffer.Buffer) (*CommandMVD, error) {
+ var err error
+ var cmd CommandMVD
+
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 {
+ readCoord = buf.GetCoord32
+ }
+
+ if cmd.Bits, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Frame, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Bits&(protocol.DFOrigin<<i) != 0 {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Bits&(protocol.DFAngles<<i) != 0 {
+ if cmd.Angle[i], err = buf.GetAngle16(); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if cmd.Bits&protocol.DFModel != 0 {
+ if cmd.ModelIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.DFSkinNum != 0 {
+ if cmd.SkinNum, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.DFEffects != 0 {
+ if cmd.Effects, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.DFWeaponFrame != 0 {
+ if cmd.WeaponFrame, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/print/print.go b/packet/command/print/print.go
new file mode 100644
index 0000000..5e6766b
--- /dev/null
+++ b/packet/command/print/print.go
@@ -0,0 +1,47 @@
+package print
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ IsNQ bool
+
+ ID byte
+ String string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCPrint)
+
+ if !cmd.IsNQ {
+ buf.PutByte(cmd.ID)
+ }
+
+ buf.PutString(cmd.String)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.IsNQ = ctx.GetIsNQ()
+
+ if !cmd.IsNQ {
+ if cmd.ID, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.String, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/qizmovoice/qizmovoice.go b/packet/command/qizmovoice/qizmovoice.go
new file mode 100644
index 0000000..4504ceb
--- /dev/null
+++ b/packet/command/qizmovoice/qizmovoice.go
@@ -0,0 +1,29 @@
+package qizmovoice
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Data []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ return append([]byte{
+ protocol.SVCQizmoVoice},
+ cmd.Data...,
+ )
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Data, err = buf.GetBytes(34); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/qtvconnect/qtvconnect.go b/packet/command/qtvconnect/qtvconnect.go
new file mode 100644
index 0000000..86adab3
--- /dev/null
+++ b/packet/command/qtvconnect/qtvconnect.go
@@ -0,0 +1,32 @@
+package qtvconnect
+
+import (
+ "fmt"
+
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/infostring"
+)
+
+type Command struct {
+ Version byte
+ Extensions uint32
+ Source string
+ UserInfo *infostring.InfoString
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutBytes([]byte("QTV\n"))
+ buf.PutBytes([]byte(fmt.Sprintf("VERSION: %d\n", cmd.Version)))
+ buf.PutBytes([]byte(fmt.Sprintf("QTV_EZQUAKE_EXT: %d\n", cmd.Extensions)))
+ buf.PutBytes([]byte(fmt.Sprintf("SOURCE: %s\n", cmd.Source)))
+
+ if cmd.UserInfo != nil {
+ buf.PutBytes([]byte(fmt.Sprintf("USERINFO: %s\n", string(cmd.UserInfo.Bytes()))))
+ }
+
+ buf.PutBytes([]byte("\n"))
+
+ return buf.Bytes()
+}
diff --git a/packet/command/qtvstringcmd/qtvstringcmd.go b/packet/command/qtvstringcmd/qtvstringcmd.go
new file mode 100644
index 0000000..c8cd49e
--- /dev/null
+++ b/packet/command/qtvstringcmd/qtvstringcmd.go
@@ -0,0 +1,20 @@
+package qtvstringcmd
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/protocol/qtv"
+)
+
+type Command struct {
+ String string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutUint16(uint16(1+2+len(cmd.String)) + 1)
+ buf.PutByte(byte(qtv.CLCStringCmd))
+ buf.PutString(cmd.String)
+
+ return buf.Bytes()
+}
diff --git a/packet/command/s2cchallenge/s2cchallenge.go b/packet/command/s2cchallenge/s2cchallenge.go
new file mode 100644
index 0000000..95cba70
--- /dev/null
+++ b/packet/command/s2cchallenge/s2cchallenge.go
@@ -0,0 +1,72 @@
+package s2cchallenge
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+ "github.com/osm/quake/protocol/mvd"
+)
+
+type Command struct {
+ ChallengeID string
+ Extensions []*protocol.Extension
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(byte(protocol.S2CChallenge))
+ buf.PutString(cmd.ChallengeID)
+
+ for _, ext := range cmd.Extensions {
+ buf.PutBytes(ext.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.ChallengeID, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ for buf.Off() < buf.Len() {
+ version, err := buf.GetUint32()
+ if err != nil {
+ return nil, err
+ }
+
+ extensions, err := buf.GetUint32()
+ if err != nil {
+ return nil, err
+ }
+
+ if ctx.GetIsFTEEnabled() && version == fte.ProtocolVersion {
+ ctx.SetFTEProtocolExtension(extensions)
+
+ if extensions&fte.ExtensionFloatCoords != 0 {
+ ctx.SetAngleSize(1)
+ ctx.SetCoordSize(2)
+ }
+ }
+
+ if ctx.GetIsFTE2Enabled() && version == fte.ProtocolVersion2 {
+ ctx.SetFTE2ProtocolExtension(extensions)
+ }
+
+ if ctx.GetIsMVDEnabled() && version == mvd.ProtocolVersion {
+ ctx.SetMVDProtocolExtension(extensions)
+ }
+
+ cmd.Extensions = append(cmd.Extensions, &protocol.Extension{
+ Version: version,
+ Extensions: extensions,
+ })
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/s2cconnection/s2cconnection.go b/packet/command/s2cconnection/s2cconnection.go
new file mode 100644
index 0000000..840f27c
--- /dev/null
+++ b/packet/command/s2cconnection/s2cconnection.go
@@ -0,0 +1,17 @@
+package s2cconnection
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.S2CConnection}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/sellscreen/sellscreen.go b/packet/command/sellscreen/sellscreen.go
new file mode 100644
index 0000000..3089ec3
--- /dev/null
+++ b/packet/command/sellscreen/sellscreen.go
@@ -0,0 +1,17 @@
+package sellscreen
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCSellScreen}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/serverdata/serverdata.go b/packet/command/serverdata/serverdata.go
new file mode 100644
index 0000000..e5c6ab7
--- /dev/null
+++ b/packet/command/serverdata/serverdata.go
@@ -0,0 +1,285 @@
+package serverdata
+
+import (
+ "errors"
+
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+ "github.com/osm/quake/protocol/mvd"
+)
+
+var (
+ ErrUnknownProtocolVersion = errors.New("unknown protocol version")
+)
+
+type Command struct {
+ IsMVD bool
+
+ ProtocolVersion uint32
+ FTEProtocolExtension uint32
+ FTE2ProtocolExtension uint32
+ MVDProtocolExtension uint32
+
+ // NQ
+ MaxClients byte
+ GameType byte
+ SignOnMessage string
+ Models []string
+ Sounds []string
+
+ // QW
+ ServerCount int32
+ GameDirectory string
+ LastReceived float32
+ PlayerNumber byte
+ Spectator bool
+ LevelName string
+ Gravity float32
+ StopSpeed float32
+ MaxSpeed float32
+ SpectatorMaxSpeed float32
+ Accelerate float32
+ AirAccelerate float32
+ WaterAccelerate float32
+ Friction float32
+ WaterFriction float32
+ EntityGravity float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCServerData)
+
+ if cmd.FTEProtocolExtension > 0 {
+ buf.PutUint32(fte.ProtocolVersion)
+ buf.PutUint32(cmd.FTEProtocolExtension)
+ }
+
+ if cmd.FTE2ProtocolExtension > 0 {
+ buf.PutUint32(fte.ProtocolVersion2)
+ buf.PutUint32(cmd.FTE2ProtocolExtension)
+ }
+
+ if cmd.MVDProtocolExtension > 0 {
+ buf.PutUint32(mvd.ProtocolVersion)
+ buf.PutUint32(cmd.MVDProtocolExtension)
+ }
+
+ buf.PutUint32(uint32(cmd.ProtocolVersion))
+
+ if cmd.ProtocolVersion == protocol.VersionNQ {
+ buf.PutByte(cmd.MaxClients)
+ buf.PutByte(cmd.GameType)
+ buf.PutString(cmd.SignOnMessage)
+
+ for i := 0; i < len(cmd.Models); i++ {
+ buf.PutString(cmd.Models[i])
+ }
+ buf.PutByte(0x00)
+
+ for i := 0; i < len(cmd.Sounds); i++ {
+ buf.PutString(cmd.Sounds[i])
+ }
+ buf.PutByte(0x00)
+ } else {
+ buf.PutUint32(uint32(cmd.ServerCount))
+ buf.PutString(cmd.GameDirectory)
+
+ if cmd.IsMVD {
+ buf.PutFloat32(cmd.LastReceived)
+ } else {
+ buf.PutByte(cmd.PlayerNumber)
+ }
+
+ buf.PutString(cmd.LevelName)
+
+ if cmd.ProtocolVersion >= 25 {
+ buf.PutFloat32(cmd.Gravity)
+ buf.PutFloat32(cmd.StopSpeed)
+ buf.PutFloat32(cmd.MaxSpeed)
+ buf.PutFloat32(cmd.SpectatorMaxSpeed)
+ buf.PutFloat32(cmd.Accelerate)
+ buf.PutFloat32(cmd.AirAccelerate)
+ buf.PutFloat32(cmd.WaterAccelerate)
+ buf.PutFloat32(cmd.Friction)
+ buf.PutFloat32(cmd.WaterFriction)
+ buf.PutFloat32(cmd.EntityGravity)
+ }
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.IsMVD = ctx.GetIsMVD()
+
+ for {
+ pv, err := buf.GetUint32()
+ if err != nil {
+ return nil, err
+ }
+
+ if pv == fte.ProtocolVersion {
+ if cmd.FTEProtocolExtension, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+ ctx.SetFTEProtocolExtension(cmd.FTEProtocolExtension)
+ if cmd.FTEProtocolExtension&fte.ExtensionFloatCoords != 0 {
+ ctx.SetAngleSize(2)
+ ctx.SetCoordSize(4)
+ }
+ continue
+ }
+
+ if pv == fte.ProtocolVersion2 {
+ if cmd.FTE2ProtocolExtension, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+ ctx.SetFTE2ProtocolExtension(cmd.FTE2ProtocolExtension)
+ continue
+ }
+
+ if pv == mvd.ProtocolVersion {
+ if cmd.MVDProtocolExtension, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+ ctx.SetMVDProtocolExtension(cmd.MVDProtocolExtension)
+ continue
+ }
+
+ if pv == protocol.VersionNQ || pv == protocol.VersionQW ||
+ pv == protocol.VersionQW210 || pv == protocol.VersionQW221 {
+ cmd.ProtocolVersion = pv
+ ctx.SetProtocolVersion(cmd.ProtocolVersion)
+ break
+ }
+
+ return nil, ErrUnknownProtocolVersion
+ }
+
+ if cmd.ProtocolVersion == protocol.VersionNQ {
+ if err = parseCommandNQ(ctx, buf, &cmd); err != nil {
+ return nil, err
+ }
+ } else {
+ if err = parseCommandQW(ctx, buf, &cmd); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
+
+func parseCommandNQ(ctx *context.Context, buf *buffer.Buffer, cmd *Command) error {
+ var err error
+
+ if cmd.MaxClients, err = buf.ReadByte(); err != nil {
+ return err
+ }
+
+ if cmd.GameType, err = buf.ReadByte(); err != nil {
+ return err
+ }
+
+ if cmd.SignOnMessage, err = buf.GetString(); err != nil {
+ return err
+ }
+
+ for {
+ var model string
+ if model, err = buf.GetString(); err != nil {
+ return err
+ }
+
+ if model == "" {
+ break
+ }
+
+ cmd.Models = append(cmd.Models, model)
+ }
+
+ for {
+ var sound string
+ if sound, err = buf.GetString(); err != nil {
+ return err
+ }
+
+ if sound == "" {
+ break
+ }
+
+ cmd.Sounds = append(cmd.Sounds, sound)
+ }
+
+ return nil
+}
+
+func parseCommandQW(ctx *context.Context, buf *buffer.Buffer, cmd *Command) error {
+ var err error
+
+ if cmd.ServerCount, err = buf.GetInt32(); err != nil {
+ return err
+ }
+
+ if cmd.GameDirectory, err = buf.GetString(); err != nil {
+ return err
+ }
+
+ if cmd.IsMVD {
+ if cmd.LastReceived, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ } else {
+ if cmd.PlayerNumber, err = buf.ReadByte(); err != nil {
+ return err
+ }
+ if cmd.PlayerNumber&128 != 0 {
+ cmd.Spectator = true
+ }
+ }
+
+ if cmd.LevelName, err = buf.GetString(); err != nil {
+ return err
+ }
+
+ if cmd.ProtocolVersion >= 25 {
+ if cmd.Gravity, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.StopSpeed, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.MaxSpeed, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.SpectatorMaxSpeed, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.Accelerate, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.AirAccelerate, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.WaterAccelerate, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.Friction, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.WaterFriction, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ if cmd.EntityGravity, err = buf.GetFloat32(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/packet/command/serverinfo/serverinfo.go b/packet/command/serverinfo/serverinfo.go
new file mode 100644
index 0000000..f144d74
--- /dev/null
+++ b/packet/command/serverinfo/serverinfo.go
@@ -0,0 +1,37 @@
+package serverinfo
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Key string
+ Value string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCServerInfo)
+ buf.PutString(cmd.Key)
+ buf.PutString(cmd.Value)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Key, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Value, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/setangle/setangle.go b/packet/command/setangle/setangle.go
new file mode 100644
index 0000000..d9440da
--- /dev/null
+++ b/packet/command/setangle/setangle.go
@@ -0,0 +1,66 @@
+package setangle
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/mvd"
+)
+
+type Command struct {
+ AngleSize uint8
+ MVDProtocolExtension uint32
+ IsMVD bool
+
+ MVDAngleIndex byte
+ Angle [3]float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ writeAngle := buf.PutAngle8
+ if cmd.AngleSize == 2 {
+ writeAngle = buf.PutAngle16
+ }
+
+ buf.PutByte(protocol.SVCSetAngle)
+
+ if cmd.IsMVD || cmd.MVDProtocolExtension&mvd.ExtensionHighLagTeleport != 0 {
+ buf.PutByte(cmd.MVDAngleIndex)
+ }
+
+ for i := 0; i < 3; i++ {
+ writeAngle(cmd.Angle[i])
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.MVDProtocolExtension = ctx.GetMVDProtocolExtension()
+ cmd.IsMVD = ctx.GetIsMVD()
+
+ cmd.AngleSize = ctx.GetAngleSize()
+ readAngle := buf.GetAngle8
+ if cmd.AngleSize == 2 {
+ readAngle = buf.GetAngle16
+ }
+
+ if cmd.IsMVD || cmd.MVDProtocolExtension&mvd.ExtensionHighLagTeleport != 0 {
+ if cmd.MVDAngleIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Angle[i], err = readAngle(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/setinfo/setinfo.go b/packet/command/setinfo/setinfo.go
new file mode 100644
index 0000000..6262ab8
--- /dev/null
+++ b/packet/command/setinfo/setinfo.go
@@ -0,0 +1,43 @@
+package setinfo
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+ Key string
+ Value string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCSetInfo)
+ buf.PutByte(cmd.PlayerIndex)
+ buf.PutString(cmd.Key)
+ buf.PutString(cmd.Value)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Key, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Value, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/setpause/setpause.go b/packet/command/setpause/setpause.go
new file mode 100644
index 0000000..5bf1679
--- /dev/null
+++ b/packet/command/setpause/setpause.go
@@ -0,0 +1,26 @@
+package setpause
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Paused byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCSetPause, cmd.Paused}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Paused, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/setview/setview.go b/packet/command/setview/setview.go
new file mode 100644
index 0000000..3b3f75c
--- /dev/null
+++ b/packet/command/setview/setview.go
@@ -0,0 +1,31 @@
+package setview
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ ViewEntity uint16
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCSetView)
+ buf.PutUint16(cmd.ViewEntity)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.ViewEntity, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/signonnum/signonnum.go b/packet/command/signonnum/signonnum.go
new file mode 100644
index 0000000..95c487e
--- /dev/null
+++ b/packet/command/signonnum/signonnum.go
@@ -0,0 +1,26 @@
+package signonnum
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCSignOnNum, cmd.PlayerIndex}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/smallkick/smallkick.go b/packet/command/smallkick/smallkick.go
new file mode 100644
index 0000000..308d369
--- /dev/null
+++ b/packet/command/smallkick/smallkick.go
@@ -0,0 +1,17 @@
+package smallkick
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct{}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCSmallKick}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ return &Command{}, nil
+}
diff --git a/packet/command/sound/sound.go b/packet/command/sound/sound.go
new file mode 100644
index 0000000..09c0149
--- /dev/null
+++ b/packet/command/sound/sound.go
@@ -0,0 +1,142 @@
+package sound
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ CoordSize uint8
+ IsNQ bool
+
+ Bits byte
+ SoundIndex byte
+ Channel uint16
+ Volume byte
+ Attenuation byte
+ SoundNum byte
+ Coord [3]float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCSound)
+
+ if cmd.IsNQ {
+ buf.PutByte(cmd.Bits)
+
+ if cmd.Bits&protocol.NQSoundVolume != 0 {
+ buf.PutByte(cmd.Volume)
+ }
+
+ if cmd.Bits&protocol.NQSoundAttenuation != 0 {
+ buf.PutByte(cmd.Attenuation)
+ }
+
+ buf.PutUint16(cmd.Channel)
+ buf.PutByte(cmd.SoundIndex)
+
+ for i := 0; i < 3; i++ {
+ buf.PutCoord16(cmd.Coord[i])
+ }
+ } else {
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutUint16(cmd.Channel)
+
+ if cmd.Channel&protocol.SoundVolume != 0 {
+ buf.PutByte(cmd.Volume)
+ }
+
+ if cmd.Channel&protocol.SoundAttenuation != 0 {
+ buf.PutByte(cmd.Attenuation)
+ }
+
+ buf.PutByte(cmd.SoundNum)
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.IsNQ = ctx.GetIsNQ()
+
+ if cmd.IsNQ {
+ if cmd.Bits, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Bits&protocol.NQSoundVolume != 0 {
+ if cmd.Volume, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Bits&protocol.NQSoundAttenuation != 0 {
+ if cmd.Attenuation, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Channel, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.SoundIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = buf.GetCoord16(); err != nil {
+ return nil, err
+ }
+ }
+
+ } else {
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 {
+ readCoord = buf.GetCoord32
+ }
+
+ if cmd.Channel, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Channel&protocol.SoundVolume != 0 {
+ if cmd.Volume, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.Channel&protocol.SoundAttenuation != 0 {
+ if cmd.Attenuation, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.SoundNum, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/soundlist/soundlist.go b/packet/command/soundlist/soundlist.go
new file mode 100644
index 0000000..19ae09f
--- /dev/null
+++ b/packet/command/soundlist/soundlist.go
@@ -0,0 +1,83 @@
+package soundlist
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ ProtocolVersion uint32
+ NumSounds byte
+ Sounds []string
+ Index byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCSoundList)
+
+ if cmd.ProtocolVersion >= 26 {
+ buf.PutByte(cmd.NumSounds)
+
+ for i := 0; i < len(cmd.Sounds); i++ {
+ buf.PutString(cmd.Sounds[i])
+ }
+ buf.PutByte(0x00)
+
+ buf.PutByte(cmd.Index)
+ } else {
+ for i := 0; i < len(cmd.Sounds); i++ {
+ buf.PutString(cmd.Sounds[i])
+ }
+ buf.PutByte(0x00)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.ProtocolVersion = ctx.GetProtocolVersion()
+
+ if cmd.ProtocolVersion >= 26 {
+ if cmd.NumSounds, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ for {
+ var sound string
+ if sound, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ if sound == "" {
+ break
+ }
+
+ cmd.Sounds = append(cmd.Sounds, sound)
+ }
+
+ if cmd.Index, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ } else {
+ for {
+ var sound string
+ if sound, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ if sound == "" {
+ break
+ }
+
+ cmd.Sounds = append(cmd.Sounds, sound)
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/spawnbaseline/spawnbaseline.go b/packet/command/spawnbaseline/spawnbaseline.go
new file mode 100644
index 0000000..55199c4
--- /dev/null
+++ b/packet/command/spawnbaseline/spawnbaseline.go
@@ -0,0 +1,41 @@
+package spawnbaseline
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/baseline"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Index uint16
+ Baseline *baseline.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCSpawnBaseline)
+ buf.PutUint16(cmd.Index)
+
+ if cmd.Baseline != nil {
+ buf.PutBytes(cmd.Baseline.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Index, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Baseline, err = baseline.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/spawnstatic/spawnstatic.go b/packet/command/spawnstatic/spawnstatic.go
new file mode 100644
index 0000000..759d494
--- /dev/null
+++ b/packet/command/spawnstatic/spawnstatic.go
@@ -0,0 +1,35 @@
+package spawnstatic
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command/baseline"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Baseline *baseline.Command
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCSpawnStatic)
+
+ if cmd.Baseline != nil {
+ buf.PutBytes(cmd.Baseline.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Baseline, err = baseline.Parse(ctx, buf); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/spawnstaticsound/spawnstaticsound.go b/packet/command/spawnstaticsound/spawnstaticsound.go
new file mode 100644
index 0000000..435feab
--- /dev/null
+++ b/packet/command/spawnstaticsound/spawnstaticsound.go
@@ -0,0 +1,68 @@
+package spawnstaticsound
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ CoordSize uint8
+
+ Coord [3]float32
+ SoundIndex byte
+ Volume byte
+ Attenuation byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutByte(protocol.SVCSpawnStaticSound)
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+
+ buf.PutByte(cmd.SoundIndex)
+ buf.PutByte(cmd.Volume)
+ buf.PutByte(cmd.Attenuation)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 {
+ readCoord = buf.GetCoord32
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.SoundIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Volume, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Attenuation, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/stopsound/stopsound.go b/packet/command/stopsound/stopsound.go
new file mode 100644
index 0000000..96a4608
--- /dev/null
+++ b/packet/command/stopsound/stopsound.go
@@ -0,0 +1,31 @@
+package stopsound
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ SoundIndex uint16
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCStopSound)
+ buf.PutUint16(cmd.SoundIndex)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.SoundIndex, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/stringcmd/stringcmd.go b/packet/command/stringcmd/stringcmd.go
new file mode 100644
index 0000000..cc5f8dd
--- /dev/null
+++ b/packet/command/stringcmd/stringcmd.go
@@ -0,0 +1,31 @@
+package stringcmd
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ String string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.CLCStringCmd)
+ buf.PutString(cmd.String)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.String, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/stufftext/stufftext.go b/packet/command/stufftext/stufftext.go
new file mode 100644
index 0000000..0b323c1
--- /dev/null
+++ b/packet/command/stufftext/stufftext.go
@@ -0,0 +1,32 @@
+package stufftext
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ String string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCStuffText)
+ buf.PutString(cmd.String)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.String, err = buf.GetString()
+ if err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/tempentity/tempentity.go b/packet/command/tempentity/tempentity.go
new file mode 100644
index 0000000..ebaf8c6
--- /dev/null
+++ b/packet/command/tempentity/tempentity.go
@@ -0,0 +1,176 @@
+package tempentity
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ IsNQ bool
+ CoordSize uint8
+
+ Type byte
+
+ Coord [3]float32
+ EndCoord [3]float32
+ Entity uint16
+ Count byte
+ ColorStart byte
+ ColorLength byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ writeCoord := buf.PutCoord16
+ if cmd.CoordSize == 4 {
+ writeCoord = buf.PutCoord32
+ }
+
+ buf.PutByte(protocol.SVCTempEntity)
+ buf.PutByte(cmd.Type)
+
+ switch cmd.Type {
+ case protocol.TELightning1:
+ fallthrough
+ case protocol.TELightning2:
+ fallthrough
+ case protocol.TELightning3:
+ buf.PutUint16(cmd.Entity)
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.EndCoord[i])
+ }
+ case protocol.TEGunshot:
+ fallthrough
+ case protocol.TEBlood:
+ if !cmd.IsNQ {
+ buf.PutByte(cmd.Count)
+ }
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+
+ if cmd.IsNQ && cmd.Type != protocol.TEGunshot {
+ buf.PutByte(cmd.ColorStart)
+ buf.PutByte(cmd.ColorLength)
+ }
+ case protocol.TELightningBlood:
+ if cmd.IsNQ {
+ buf.PutUint16(cmd.Entity)
+ }
+
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+
+ if cmd.IsNQ {
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.EndCoord[i])
+ }
+ }
+ default:
+ for i := 0; i < 3; i++ {
+ writeCoord(cmd.Coord[i])
+ }
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.IsNQ = ctx.GetIsNQ()
+
+ cmd.CoordSize = ctx.GetCoordSize()
+ readCoord := buf.GetCoord16
+ if cmd.CoordSize == 4 {
+ readCoord = buf.GetCoord32
+ }
+
+ if cmd.Type, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ switch cmd.Type {
+ case protocol.TELightning1:
+ fallthrough
+ case protocol.TELightning2:
+ fallthrough
+ case protocol.TELightning3:
+ if cmd.Entity, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+ for i := 0; i < 3; i++ {
+ if cmd.EndCoord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+ case protocol.TEGunshot:
+ fallthrough
+ case protocol.TEBlood:
+ if !cmd.IsNQ {
+ if cmd.Count, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.IsNQ && cmd.Type != protocol.TEGunshot {
+ if cmd.ColorStart, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.ColorLength, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+ case protocol.TELightningBlood:
+ if cmd.IsNQ {
+ if cmd.Entity, err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+ }
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+
+ if cmd.IsNQ {
+ for i := 0; i < 3; i++ {
+ if cmd.EndCoord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+ }
+ default:
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = readCoord(); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/time/time.go b/packet/command/time/time.go
new file mode 100644
index 0000000..825d571
--- /dev/null
+++ b/packet/command/time/time.go
@@ -0,0 +1,31 @@
+package time
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Time float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCTime)
+ buf.PutFloat32(cmd.Time)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Time, err = buf.GetFloat32(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/tmove/tmove.go b/packet/command/tmove/tmove.go
new file mode 100644
index 0000000..4733f2c
--- /dev/null
+++ b/packet/command/tmove/tmove.go
@@ -0,0 +1,36 @@
+package tmove
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Coord [3]uint16
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.CLCTMove)
+
+ for i := 0; i < 3; i++ {
+ buf.PutUint16(cmd.Coord[i])
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ for i := 0; i < 3; i++ {
+ if cmd.Coord[i], err = buf.GetUint16(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updatecolors/updatecolors.go b/packet/command/updatecolors/updatecolors.go
new file mode 100644
index 0000000..491ff72
--- /dev/null
+++ b/packet/command/updatecolors/updatecolors.go
@@ -0,0 +1,31 @@
+package updatecolors
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+ Color byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCUpdateColors, cmd.PlayerIndex, cmd.Color}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Color, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updateentertime/updateentertime.go b/packet/command/updateentertime/updateentertime.go
new file mode 100644
index 0000000..da670e2
--- /dev/null
+++ b/packet/command/updateentertime/updateentertime.go
@@ -0,0 +1,37 @@
+package updateentertime
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+ EnterTime float32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCUpdateEnterTime)
+ buf.PutByte(cmd.PlayerIndex)
+ buf.PutFloat32(cmd.EnterTime)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.EnterTime, err = buf.GetFloat32(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updatefrags/updatefrags.go b/packet/command/updatefrags/updatefrags.go
new file mode 100644
index 0000000..5713bce
--- /dev/null
+++ b/packet/command/updatefrags/updatefrags.go
@@ -0,0 +1,37 @@
+package updatefrags
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+ Frags int16
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCUpdateFrags)
+ buf.PutByte(cmd.PlayerIndex)
+ buf.PutInt16(cmd.Frags)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Frags, err = buf.GetInt16(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updatename/updatename.go b/packet/command/updatename/updatename.go
new file mode 100644
index 0000000..06e1bfd
--- /dev/null
+++ b/packet/command/updatename/updatename.go
@@ -0,0 +1,37 @@
+package updatename
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+ Name string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCUpdateName)
+ buf.PutByte(cmd.PlayerIndex)
+ buf.PutString(cmd.Name)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Name, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updateping/updateping.go b/packet/command/updateping/updateping.go
new file mode 100644
index 0000000..412d9c5
--- /dev/null
+++ b/packet/command/updateping/updateping.go
@@ -0,0 +1,37 @@
+package updateping
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+ Ping int16
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCUpdatePing)
+ buf.PutByte(cmd.PlayerIndex)
+ buf.PutInt16(cmd.Ping)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Ping, err = buf.GetInt16(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updatepl/updatepl.go b/packet/command/updatepl/updatepl.go
new file mode 100644
index 0000000..782ed9a
--- /dev/null
+++ b/packet/command/updatepl/updatepl.go
@@ -0,0 +1,31 @@
+package updatepl
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+ PL byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ return []byte{protocol.SVCUpdatePL, cmd.PlayerIndex, cmd.PL}
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.PL, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updatestat/updatestat.go b/packet/command/updatestat/updatestat.go
new file mode 100644
index 0000000..0f4810a
--- /dev/null
+++ b/packet/command/updatestat/updatestat.go
@@ -0,0 +1,53 @@
+package updatestat
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ IsNQ bool
+
+ Stat byte
+ Value8 byte
+ Value32 uint32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCUpdateStat)
+ buf.PutByte(cmd.Stat)
+
+ if cmd.IsNQ {
+ buf.PutUint32(cmd.Value32)
+ } else {
+ buf.PutByte(cmd.Value8)
+ }
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ cmd.IsNQ = ctx.GetIsNQ()
+
+ if cmd.Stat, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.IsNQ {
+ if cmd.Value32, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+ } else {
+ if cmd.Value8, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updatestatlong/updatestatlong.go b/packet/command/updatestatlong/updatestatlong.go
new file mode 100644
index 0000000..f45a0d9
--- /dev/null
+++ b/packet/command/updatestatlong/updatestatlong.go
@@ -0,0 +1,37 @@
+package updatestatlong
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Stat byte
+ Value int32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCUpdateStatLong)
+ buf.PutByte(cmd.Stat)
+ buf.PutInt32(cmd.Value)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Stat, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Value, err = buf.GetInt32(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/updateuserinfo/updateuserinfo.go b/packet/command/updateuserinfo/updateuserinfo.go
new file mode 100644
index 0000000..ce08d4f
--- /dev/null
+++ b/packet/command/updateuserinfo/updateuserinfo.go
@@ -0,0 +1,43 @@
+package updateuserinfo
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ PlayerIndex byte
+ UserID uint32
+ UserInfo string
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCUpdateUserInfo)
+ buf.PutByte(cmd.PlayerIndex)
+ buf.PutUint32(cmd.UserID)
+ buf.PutString(cmd.UserInfo)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.PlayerIndex, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.UserID, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+
+ if cmd.UserInfo, err = buf.GetString(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/upload/upload.go b/packet/command/upload/upload.go
new file mode 100644
index 0000000..a55d47f
--- /dev/null
+++ b/packet/command/upload/upload.go
@@ -0,0 +1,43 @@
+package upload
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Size int16
+ Percent byte
+ Data []byte
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.CLCUpload)
+ buf.PutInt16(cmd.Size)
+ buf.PutByte(cmd.Percent)
+ buf.PutBytes(cmd.Data)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Size, err = buf.GetInt16(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Percent, err = buf.ReadByte(); err != nil {
+ return nil, err
+ }
+
+ if cmd.Data, err = buf.GetBytes(int(cmd.Size)); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/command/version/version.go b/packet/command/version/version.go
new file mode 100644
index 0000000..60ee1b9
--- /dev/null
+++ b/packet/command/version/version.go
@@ -0,0 +1,31 @@
+package version
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/protocol"
+)
+
+type Command struct {
+ Version uint32
+}
+
+func (cmd *Command) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutByte(protocol.SVCVersion)
+ buf.PutUint32(cmd.Version)
+
+ return buf.Bytes()
+}
+
+func Parse(ctx *context.Context, buf *buffer.Buffer) (*Command, error) {
+ var err error
+ var cmd Command
+
+ if cmd.Version, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+
+ return &cmd, nil
+}
diff --git a/packet/packet.go b/packet/packet.go
new file mode 100644
index 0000000..7b20c3f
--- /dev/null
+++ b/packet/packet.go
@@ -0,0 +1,5 @@
+package packet
+
+type Packet interface {
+ Bytes() []byte
+}
diff --git a/packet/svc/connectionless.go b/packet/svc/connectionless.go
new file mode 100644
index 0000000..6d42ae2
--- /dev/null
+++ b/packet/svc/connectionless.go
@@ -0,0 +1,67 @@
+package svc
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command"
+ "github.com/osm/quake/packet/command/a2aping"
+ "github.com/osm/quake/packet/command/a2cclientcommand"
+ "github.com/osm/quake/packet/command/a2cprint"
+ "github.com/osm/quake/packet/command/disconnect"
+ "github.com/osm/quake/packet/command/passthrough"
+ "github.com/osm/quake/packet/command/s2cchallenge"
+ "github.com/osm/quake/packet/command/s2cconnection"
+ "github.com/osm/quake/protocol"
+)
+
+type Connectionless struct {
+ Command command.Command
+}
+
+func (cmd *Connectionless) Bytes() []byte {
+ buf := buffer.New()
+
+ buf.PutInt32(-1)
+ buf.PutBytes(cmd.Command.Bytes())
+
+ return buf.Bytes()
+}
+
+func parseConnectionless(ctx *context.Context, buf *buffer.Buffer) (*Connectionless, error) {
+ var err error
+ var pkg Connectionless
+
+ if err := buf.Skip(4); err != nil {
+ return nil, err
+ }
+
+ typ, err := buf.ReadByte()
+ if err != nil {
+ return nil, err
+ }
+
+ var cmd command.Command
+ switch protocol.CommandType(typ) {
+ case protocol.S2CConnection:
+ cmd, err = s2cconnection.Parse(ctx, buf)
+ case protocol.A2CClientCommand:
+ cmd, err = a2cclientcommand.Parse(ctx, buf)
+ case protocol.A2CPrint:
+ cmd, err = a2cprint.Parse(ctx, buf)
+ case protocol.A2APing:
+ cmd, err = a2aping.Parse(ctx, buf)
+ case protocol.S2CChallenge:
+ cmd, err = s2cchallenge.Parse(ctx, buf)
+ case protocol.SVCDisconnect:
+ cmd, err = disconnect.Parse(ctx, buf)
+ default:
+ cmd, err = passthrough.Parse(ctx, buf, "")
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ pkg.Command = cmd
+
+ return &pkg, nil
+}
diff --git a/packet/svc/gamedata.go b/packet/svc/gamedata.go
new file mode 100644
index 0000000..8d46cb0
--- /dev/null
+++ b/packet/svc/gamedata.go
@@ -0,0 +1,265 @@
+package svc
+
+import (
+ "errors"
+
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet/command"
+ "github.com/osm/quake/packet/command/bad"
+ "github.com/osm/quake/packet/command/bigkick"
+ "github.com/osm/quake/packet/command/cdtrack"
+ "github.com/osm/quake/packet/command/centerprint"
+ "github.com/osm/quake/packet/command/chokecount"
+ "github.com/osm/quake/packet/command/clientdata"
+ "github.com/osm/quake/packet/command/damage"
+ "github.com/osm/quake/packet/command/deltapacketentities"
+ "github.com/osm/quake/packet/command/disconnect"
+ "github.com/osm/quake/packet/command/download"
+ "github.com/osm/quake/packet/command/entgravity"
+ "github.com/osm/quake/packet/command/fastupdate"
+ "github.com/osm/quake/packet/command/finale"
+ "github.com/osm/quake/packet/command/foundsecret"
+ "github.com/osm/quake/packet/command/ftemodellist"
+ "github.com/osm/quake/packet/command/ftespawnbaseline"
+ "github.com/osm/quake/packet/command/ftespawnstatic"
+ "github.com/osm/quake/packet/command/ftevoicechats"
+ "github.com/osm/quake/packet/command/intermission"
+ "github.com/osm/quake/packet/command/killedmonster"
+ "github.com/osm/quake/packet/command/lightstyle"
+ "github.com/osm/quake/packet/command/maxspeed"
+ "github.com/osm/quake/packet/command/modellist"
+ "github.com/osm/quake/packet/command/muzzleflash"
+ "github.com/osm/quake/packet/command/nails"
+ "github.com/osm/quake/packet/command/nails2"
+ "github.com/osm/quake/packet/command/nops"
+ "github.com/osm/quake/packet/command/packetentities"
+ "github.com/osm/quake/packet/command/particle"
+ "github.com/osm/quake/packet/command/playerinfo"
+ "github.com/osm/quake/packet/command/print"
+ "github.com/osm/quake/packet/command/qizmovoice"
+ "github.com/osm/quake/packet/command/sellscreen"
+ "github.com/osm/quake/packet/command/serverdata"
+ "github.com/osm/quake/packet/command/serverinfo"
+ "github.com/osm/quake/packet/command/setangle"
+ "github.com/osm/quake/packet/command/setinfo"
+ "github.com/osm/quake/packet/command/setpause"
+ "github.com/osm/quake/packet/command/setview"
+ "github.com/osm/quake/packet/command/signonnum"
+ "github.com/osm/quake/packet/command/smallkick"
+ "github.com/osm/quake/packet/command/sound"
+ "github.com/osm/quake/packet/command/soundlist"
+ "github.com/osm/quake/packet/command/spawnbaseline"
+ "github.com/osm/quake/packet/command/spawnstatic"
+ "github.com/osm/quake/packet/command/spawnstaticsound"
+ "github.com/osm/quake/packet/command/stopsound"
+ "github.com/osm/quake/packet/command/stufftext"
+ "github.com/osm/quake/packet/command/tempentity"
+ "github.com/osm/quake/packet/command/time"
+ "github.com/osm/quake/packet/command/updatecolors"
+ "github.com/osm/quake/packet/command/updateentertime"
+ "github.com/osm/quake/packet/command/updatefrags"
+ "github.com/osm/quake/packet/command/updatename"
+ "github.com/osm/quake/packet/command/updateping"
+ "github.com/osm/quake/packet/command/updatepl"
+ "github.com/osm/quake/packet/command/updatestat"
+ "github.com/osm/quake/packet/command/updatestatlong"
+ "github.com/osm/quake/packet/command/updateuserinfo"
+ "github.com/osm/quake/packet/command/version"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+)
+
+var ErrUnknownCommandType = errors.New("unknown command type")
+
+type GameData struct {
+ IsMVD bool
+ IsNQ bool
+
+ Seq uint32
+ Ack uint32
+ Commands []command.Command
+}
+
+func (gd *GameData) Bytes() []byte {
+ buf := buffer.New()
+
+ if gd.IsMVD || gd.IsNQ {
+ goto process
+ }
+
+ buf.PutUint32(gd.Seq)
+ buf.PutUint32(gd.Ack)
+
+process:
+ for _, c := range gd.Commands {
+ buf.PutBytes(c.Bytes())
+ }
+
+ return buf.Bytes()
+}
+
+func parseGameData(ctx *context.Context, buf *buffer.Buffer) (*GameData, error) {
+ var err error
+ var pkg GameData
+
+ pkg.IsMVD = ctx.GetIsMVD()
+ pkg.IsNQ = ctx.GetIsNQ()
+
+ if pkg.IsMVD || pkg.IsNQ {
+ goto process
+ }
+
+ if pkg.Seq, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+
+ if pkg.Ack, err = buf.GetUint32(); err != nil {
+ return nil, err
+ }
+
+process:
+ var cmd command.Command
+ for buf.Off() < buf.Len() {
+ typ, err := buf.ReadByte()
+ if err != nil {
+ return nil, err
+ }
+
+ if pkg.IsNQ && typ&128 != 0 {
+ cmd, err = fastupdate.Parse(ctx, buf, typ)
+ goto next
+ }
+
+ switch protocol.CommandType(typ) {
+ case protocol.SVCBad:
+ cmd, err = bad.Parse(ctx, buf, protocol.SVCBad)
+ case protocol.SVCNOP:
+ cmd, err = nops.Parse(ctx, buf)
+ case protocol.SVCDisconnect:
+ cmd, err = disconnect.Parse(ctx, buf)
+ case protocol.SVCUpdateStat:
+ cmd, err = updatestat.Parse(ctx, buf)
+ case protocol.SVCVersion:
+ cmd, err = version.Parse(ctx, buf)
+ case protocol.SVCSetView:
+ cmd, err = setview.Parse(ctx, buf)
+ case protocol.SVCSound:
+ cmd, err = sound.Parse(ctx, buf)
+ case protocol.SVCTime:
+ cmd, err = time.Parse(ctx, buf)
+ case protocol.SVCPrint:
+ cmd, err = print.Parse(ctx, buf)
+ case protocol.SVCStuffText:
+ cmd, err = stufftext.Parse(ctx, buf)
+ case protocol.SVCSetAngle:
+ cmd, err = setangle.Parse(ctx, buf)
+ case protocol.SVCServerData:
+ cmd, err = serverdata.Parse(ctx, buf)
+ case protocol.SVCLightStyle:
+ cmd, err = lightstyle.Parse(ctx, buf)
+ case protocol.SVCUpdateName:
+ cmd, err = updatename.Parse(ctx, buf)
+ case protocol.SVCUpdateFrags:
+ cmd, err = updatefrags.Parse(ctx, buf)
+ case protocol.SVCClientData:
+ cmd, err = clientdata.Parse(ctx, buf)
+ case protocol.SVCStopSound:
+ cmd, err = stopsound.Parse(ctx, buf)
+ case protocol.SVCUpdateColors:
+ cmd, err = updatecolors.Parse(ctx, buf)
+ case protocol.SVCParticle:
+ cmd, err = particle.Parse(ctx, buf)
+ case protocol.SVCDamage:
+ cmd, err = damage.Parse(ctx, buf)
+ case protocol.SVCSpawnStatic:
+ cmd, err = spawnstatic.Parse(ctx, buf)
+ case protocol.SVCSpawnBaseline:
+ cmd, err = spawnbaseline.Parse(ctx, buf)
+ case protocol.SVCTempEntity:
+ cmd, err = tempentity.Parse(ctx, buf)
+ case protocol.SVCSetPause:
+ cmd, err = setpause.Parse(ctx, buf)
+ case protocol.SVCSignOnNum:
+ cmd, err = signonnum.Parse(ctx, buf)
+ case protocol.SVCCenterPrint:
+ cmd, err = centerprint.Parse(ctx, buf)
+ case protocol.SVCKilledMonster:
+ cmd, err = killedmonster.Parse(ctx, buf)
+ case protocol.SVCFoundSecret:
+ cmd, err = foundsecret.Parse(ctx, buf)
+ case protocol.SVCSpawnStaticSound:
+ cmd, err = spawnstaticsound.Parse(ctx, buf)
+ case protocol.SVCIntermission:
+ cmd, err = intermission.Parse(ctx, buf)
+ case protocol.SVCFinale:
+ cmd, err = finale.Parse(ctx, buf)
+ case protocol.SVCCDTrack:
+ cmd, err = cdtrack.Parse(ctx, buf)
+ case protocol.SVCSellScreen:
+ cmd, err = sellscreen.Parse(ctx, buf)
+ case protocol.SVCSmallKick:
+ cmd, err = smallkick.Parse(ctx, buf)
+ case protocol.SVCBigKick:
+ cmd, err = bigkick.Parse(ctx, buf)
+ case protocol.SVCUpdatePing:
+ cmd, err = updateping.Parse(ctx, buf)
+ case protocol.SVCUpdateEnterTime:
+ cmd, err = updateentertime.Parse(ctx, buf)
+ case protocol.SVCUpdateStatLong:
+ cmd, err = updatestatlong.Parse(ctx, buf)
+ case protocol.SVCMuzzleFlash:
+ cmd, err = muzzleflash.Parse(ctx, buf)
+ case protocol.SVCUpdateUserInfo:
+ cmd, err = updateuserinfo.Parse(ctx, buf)
+ case protocol.SVCDownload:
+ cmd, err = download.Parse(ctx, buf)
+ case protocol.SVCPlayerInfo:
+ cmd, err = playerinfo.Parse(ctx, buf)
+ case protocol.SVCNails:
+ cmd, err = nails.Parse(ctx, buf)
+ case protocol.SVCChokeCount:
+ cmd, err = chokecount.Parse(ctx, buf)
+ case protocol.SVCModelList:
+ cmd, err = modellist.Parse(ctx, buf)
+ case protocol.SVCSoundList:
+ cmd, err = soundlist.Parse(ctx, buf)
+ case protocol.SVCPacketEntities:
+ cmd, err = packetentities.Parse(ctx, buf)
+ case protocol.SVCDeltaPacketEntities:
+ cmd, err = deltapacketentities.Parse(ctx, buf)
+ case protocol.SVCMaxSpeed:
+ cmd, err = maxspeed.Parse(ctx, buf)
+ case protocol.SVCEntGravity:
+ cmd, err = entgravity.Parse(ctx, buf)
+ case protocol.SVCSetInfo:
+ cmd, err = setinfo.Parse(ctx, buf)
+ case protocol.SVCServerInfo:
+ cmd, err = serverinfo.Parse(ctx, buf)
+ case protocol.SVCUpdatePL:
+ cmd, err = updatepl.Parse(ctx, buf)
+ case protocol.SVCNails2:
+ cmd, err = nails2.Parse(ctx, buf)
+ case protocol.SVCQizmoVoice:
+ cmd, err = qizmovoice.Parse(ctx, buf)
+ case fte.SVCSpawnStatic:
+ cmd, err = ftespawnstatic.Parse(ctx, buf)
+ case fte.SVCModelListShort:
+ cmd, err = ftemodellist.Parse(ctx, buf)
+ case fte.SVCSpawnBaseline:
+ cmd, err = ftespawnbaseline.Parse(ctx, buf)
+ case fte.SVCVoiceChat:
+ cmd, err = ftevoicechats.Parse(ctx, buf)
+ default:
+ return nil, ErrUnknownCommandType
+ }
+
+ next:
+ if err != nil {
+ return nil, err
+ }
+ pkg.Commands = append(pkg.Commands, cmd)
+ }
+
+ return &pkg, nil
+}
diff --git a/packet/svc/parse.go b/packet/svc/parse.go
new file mode 100644
index 0000000..a95a33a
--- /dev/null
+++ b/packet/svc/parse.go
@@ -0,0 +1,18 @@
+package svc
+
+import (
+ "github.com/osm/quake/common/buffer"
+ "github.com/osm/quake/common/context"
+ "github.com/osm/quake/packet"
+)
+
+func Parse(ctx *context.Context, data []byte) (packet.Packet, error) {
+ buf := buffer.New(buffer.WithData(data))
+
+ header, _ := buf.PeekInt32()
+ if header == -1 {
+ return parseConnectionless(ctx, buf)
+ }
+
+ return parseGameData(ctx, buf)
+}