diff options
| author | 2026-02-16 16:31:54 -0500 | |
|---|---|---|
| committer | 2026-02-16 16:31:54 -0500 | |
| commit | ca90ebdfa8789654766c5d7969baa7afacd9ebd2 (patch) | |
| tree | 9693e0c7a5af6713f4c5e39372dcf22d05844ec3 /packet | |
Diffstat (limited to 'packet')
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) +} |
