mirror of https://github.com/icedream/go-q3net.git
213 lines
4.4 KiB
Go
213 lines
4.4 KiB
Go
package quake
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
type Message struct {
|
|
Header *Header
|
|
Name string
|
|
FragmentOffset uint32
|
|
FragmentLength uint32
|
|
Data []byte
|
|
}
|
|
|
|
func UnmarshalMessage(data []byte) (*Message, error) {
|
|
// Is there even a full header?
|
|
if len(data) < 4 {
|
|
return nil, ErrCorruptedMessage
|
|
}
|
|
|
|
header, err := UnmarshalHeader(data[0:4])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// netchan UDP packets are structured differently
|
|
if !header.IsOOB() {
|
|
/*
|
|
Original commentary from https://github.com/id-Software/Quake-III-Arena/blob/master/code/qcommon/net_chan.c#L30-L33:
|
|
|
|
packet header
|
|
-------------
|
|
4 outgoing sequence. high bit will be set if this is a fragmented message
|
|
[2 qport (only for client to server)]
|
|
[2 fragment start byte]
|
|
[2 fragment length. if < FRAGMENT_SIZE, this is the last fragment]
|
|
|
|
if the sequence number is -1, the packet should be handled as an out-of-band
|
|
message instead of as part of a netcon.
|
|
|
|
All fragments will have the same sequence numbers.
|
|
|
|
The qport field is a workaround for bad address translating routers that
|
|
sometimes remap the client's source port on a packet during gameplay.
|
|
|
|
If the base part of the net address matches and the qport matches, then the
|
|
channel matches even if the IP port differs. The IP port should be updated
|
|
to the new value before sending out any replies.
|
|
|
|
*/
|
|
|
|
// TODO - if packet is from client to server then 16 bits client's port number here
|
|
|
|
if header.Fragmented {
|
|
// TODO
|
|
|
|
// 16 bits fragment offset
|
|
|
|
// 16 bits fragment length
|
|
}
|
|
}
|
|
|
|
splitPos := bytes.IndexAny(data[4:], " \n\r\t\x00\\")
|
|
if splitPos < 0 {
|
|
splitPos = len(data) - 4
|
|
}
|
|
commandName := string(data[4 : 4+splitPos])
|
|
|
|
//log.Printf("Got message with command name %q", commandName)
|
|
|
|
extra := []byte(nil)
|
|
if data[4+splitPos] == 92 {
|
|
splitPos--
|
|
}
|
|
if len(data) > 4+splitPos {
|
|
extra = data[4+splitPos+1:]
|
|
}
|
|
for len(extra) > 0 && extra[len(extra)-1] == 0 {
|
|
extra = extra[0 : len(extra)-1]
|
|
}
|
|
|
|
return &Message{
|
|
Name: commandName,
|
|
Data: extra,
|
|
Header: header,
|
|
}, nil
|
|
}
|
|
|
|
func (m *Message) SetArguments(argv []string) {
|
|
m.Data = make([]byte, 0)
|
|
|
|
for _, arg := range argv {
|
|
if strings.Contains(arg, " ") {
|
|
arg = "\"" + strings.Replace(arg, "\"", "\\\"", -1) + "\""
|
|
}
|
|
|
|
if len(m.Data) > 0 {
|
|
// separate with space
|
|
m.Data = append(m.Data, 0x20)
|
|
}
|
|
|
|
// add argument in binary form
|
|
m.Data = append(m.Data, []byte(arg)...)
|
|
}
|
|
|
|
//m.Data = append(m.Data, 0x0A)
|
|
}
|
|
|
|
func (m *Message) GetArguments() []string {
|
|
// All matches, extract arguments
|
|
buffer := m.Data
|
|
argv := []string{}
|
|
for len(buffer) > 0 {
|
|
c := rune(buffer[0])
|
|
|
|
switch c {
|
|
case '"', '\'':
|
|
// quoted string
|
|
searchPos := 1
|
|
for {
|
|
pos := bytes.IndexByte(buffer[searchPos:], byte(c))
|
|
if pos < 0 {
|
|
// found quote start without a matching end
|
|
return nil
|
|
}
|
|
|
|
pos += searchPos
|
|
|
|
if rune(buffer[pos-1]) != '\\' {
|
|
// found end of quoted string
|
|
searchPos = pos
|
|
break
|
|
}
|
|
|
|
// that's an escaped quote char, skip that
|
|
searchPos = pos + 1
|
|
}
|
|
|
|
// append what's inside the quotes to the arguments list
|
|
arg := string(buffer[1:searchPos])
|
|
arg = strings.Replace(arg, "\\"+string(c), string(c), -1)
|
|
argv = append(argv, arg)
|
|
|
|
if searchPos+1 < len(buffer) {
|
|
buffer = buffer[searchPos+1:]
|
|
} else {
|
|
buffer = []byte{}
|
|
}
|
|
case ' ', '\t', '\n', '\r':
|
|
buffer = buffer[1:]
|
|
default:
|
|
// search for next space
|
|
pos := bytes.IndexByte(buffer, 0x20)
|
|
if pos < 0 {
|
|
pos = bytes.IndexByte(buffer, 0x0A)
|
|
}
|
|
if pos < 0 {
|
|
pos = bytes.IndexByte(buffer, 0x00)
|
|
}
|
|
if pos < 0 {
|
|
pos = len(buffer)
|
|
}
|
|
arg := string(buffer[0:pos])
|
|
argv = append(argv, arg)
|
|
|
|
if pos+1 < len(buffer) {
|
|
buffer = buffer[pos+1:]
|
|
} else {
|
|
buffer = []byte{}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return argv
|
|
}
|
|
|
|
func (m *Message) Marshal(w io.Writer) error {
|
|
// Buffer
|
|
buf := new(bytes.Buffer)
|
|
|
|
// Header
|
|
if _, err := buf.Write(m.Header.Marshal()); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Command name
|
|
if _, err := buf.Write([]byte(m.Name)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Data
|
|
if m.Data != nil && len(m.Data) > 0 {
|
|
if _, err := buf.Write([]byte{0x20}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err := buf.Write(m.Data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Separator, IW code expects this to be there, however our
|
|
// implementation doesn't require it.
|
|
buf.Write([]byte{0x00})
|
|
|
|
// And now write as one message
|
|
w.Write(buf.Bytes())
|
|
|
|
return nil
|
|
}
|