147 lines
4.3 KiB
Go
147 lines
4.3 KiB
Go
|
|
package client
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/binary"
|
||
|
|
"fmt"
|
||
|
|
"log"
|
||
|
|
|
||
|
|
"go-ts/pkg/protocol"
|
||
|
|
|
||
|
|
"github.com/dgryski/go-quicklz"
|
||
|
|
)
|
||
|
|
|
||
|
|
// SendCommand sends a command, splitting it into fragments if it exceeds 500 bytes.
|
||
|
|
func (c *Client) SendCommand(cmd *protocol.Command) error {
|
||
|
|
return c.SendCommandString(cmd.Encode())
|
||
|
|
}
|
||
|
|
|
||
|
|
// SendCommandString sends a raw command string with fragmentation.
|
||
|
|
func (c *Client) SendCommandString(cmdStr string) error {
|
||
|
|
data := []byte(cmdStr)
|
||
|
|
maxPacketSize := 500
|
||
|
|
maxBody := maxPacketSize - 13 // Header is 13 bytes for C->S (MAC 8, PID 2, TYPE 1, CID 2)
|
||
|
|
|
||
|
|
pType := protocol.PacketTypeCommand
|
||
|
|
pFlags := uint8(0)
|
||
|
|
|
||
|
|
// ts3j logic: If too large, try compressing
|
||
|
|
if len(data)+13 > maxPacketSize {
|
||
|
|
compressed := quicklz.Compress(data, 1)
|
||
|
|
if len(compressed)+13 < len(data)+13 {
|
||
|
|
data = compressed
|
||
|
|
pFlags |= protocol.PacketFlagCompressed
|
||
|
|
log.Printf("Compressed large command: %d -> %d bytes", len([]byte(cmdStr)), len(data))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If still too large (or not compressible), fragment
|
||
|
|
if len(data)+13 > maxPacketSize {
|
||
|
|
log.Printf("Fragmenting large command (%d bytes) into %d packets", len(data), (len(data)/maxBody)+1)
|
||
|
|
|
||
|
|
for i := 0; i < len(data); i += maxBody {
|
||
|
|
end := i + maxBody
|
||
|
|
if end > len(data) {
|
||
|
|
end = len(data)
|
||
|
|
}
|
||
|
|
|
||
|
|
chunk := data[i:end]
|
||
|
|
chunkFlags := uint8(0)
|
||
|
|
|
||
|
|
// First packet keeps COMPRESSED flag (if set) and gets FRAGMENTED
|
||
|
|
if i == 0 {
|
||
|
|
chunkFlags = pFlags | protocol.PacketFlagFragmented
|
||
|
|
} else if end == len(data) {
|
||
|
|
// Last packet gets FRAGMENTED
|
||
|
|
chunkFlags = protocol.PacketFlagFragmented
|
||
|
|
} else {
|
||
|
|
// Intermediate packets have NO flags (other than NewProtocol added in sendPacketInternal)
|
||
|
|
chunkFlags = 0
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := c.sendPacketInternal(chunk, pType, chunkFlags); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Small enough to send in one go
|
||
|
|
return c.sendPacketInternal(data, pType, pFlags)
|
||
|
|
}
|
||
|
|
|
||
|
|
// sendPacketInternal handles encryption and low-level header construction for C->S packets.
|
||
|
|
func (c *Client) sendPacketInternal(data []byte, pType protocol.PacketType, flags uint8) error {
|
||
|
|
pkt := protocol.NewPacket(pType, data)
|
||
|
|
c.PacketIDCounterC2S++
|
||
|
|
pkt.Header.PacketID = c.PacketIDCounterC2S
|
||
|
|
pkt.Header.ClientID = c.ClientID
|
||
|
|
pkt.Header.Type |= protocol.PacketFlagNewProtocol | flags
|
||
|
|
|
||
|
|
// Encryption
|
||
|
|
// Use SharedSecret if Step >= 6, else fallback to HandshakeKey
|
||
|
|
key := protocol.HandshakeKey[:]
|
||
|
|
nonce := protocol.HandshakeNonce[:]
|
||
|
|
|
||
|
|
if c.Handshake != nil && c.Handshake.Step >= 6 && len(c.Handshake.SharedIV) > 0 {
|
||
|
|
crypto := &protocol.CryptoState{
|
||
|
|
SharedIV: c.Handshake.SharedIV,
|
||
|
|
SharedMac: c.Handshake.SharedMac,
|
||
|
|
GenerationID: 0,
|
||
|
|
}
|
||
|
|
keyArr, nonceArr := crypto.GenerateKeyNonce(&pkt.Header, true) // Client->Server=true
|
||
|
|
key = keyArr
|
||
|
|
nonce = nonceArr
|
||
|
|
}
|
||
|
|
|
||
|
|
// Meta for Client->Server: PID(2) + CID(2) + PT(1) = 5 bytes
|
||
|
|
meta := make([]byte, 5)
|
||
|
|
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||
|
|
binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID)
|
||
|
|
meta[4] = pkt.Header.Type
|
||
|
|
|
||
|
|
encData, mac, err := protocol.EncryptEAX(key, nonce, meta, pkt.Data)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("encryption failed: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
pkt.Data = encData
|
||
|
|
copy(pkt.Header.MAC[:], mac)
|
||
|
|
|
||
|
|
return c.Conn.SendPacket(pkt)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) sendClientInit() error {
|
||
|
|
// Specialized send for clientinit because it needs PID 2 and uses Map params
|
||
|
|
params := map[string]string{
|
||
|
|
"client_nickname": c.Nickname,
|
||
|
|
"client_version": "3.6.2 [Build: 1690976575]",
|
||
|
|
"client_platform": "Windows",
|
||
|
|
"client_input_muted": "0",
|
||
|
|
"client_output_muted": "0",
|
||
|
|
"client_outputonly_muted": "0",
|
||
|
|
"client_input_hardware": "1",
|
||
|
|
"client_output_hardware": "1",
|
||
|
|
"client_version_sign": "OyuLO/1bVJtBsXLRWzfGVhNaQd7B9D4QTolZm14DM1uCbSXVvqX3Ssym3sLi/PcvOl+SAUlX6NwBPOsQdwOGDw==",
|
||
|
|
"client_key_offset": fmt.Sprintf("%d", c.Handshake.IdentityOffset),
|
||
|
|
"hwid": "1234567890",
|
||
|
|
}
|
||
|
|
|
||
|
|
var buf bytes.Buffer
|
||
|
|
buf.WriteString("clientinit")
|
||
|
|
for k, v := range params {
|
||
|
|
buf.WriteString(" ")
|
||
|
|
buf.WriteString(k)
|
||
|
|
if v != "" {
|
||
|
|
buf.WriteString("=")
|
||
|
|
buf.WriteString(protocol.Escape(v))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Println("Sending clientinit (Packet 2) [Encrypted]...")
|
||
|
|
|
||
|
|
// Reset counter specifically for this sync point
|
||
|
|
c.PacketIDCounterC2S = 1 // Next will be 2
|
||
|
|
return c.SendCommandString(buf.String())
|
||
|
|
}
|