2026-01-15 20:30:03 +01:00
|
|
|
package client
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"log"
|
|
|
|
|
|
|
|
|
|
"go-ts/pkg/protocol"
|
2026-01-15 21:35:23 +01:00
|
|
|
|
|
|
|
|
"gopkg.in/hraban/opus.v2"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// TeamSpeak Codecs
|
|
|
|
|
CodecOpusVoice = 4
|
|
|
|
|
CodecOpusMusic = 5
|
2026-01-15 20:30:03 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (c *Client) handleVoice(pkt *protocol.Packet) {
|
2026-01-15 21:35:23 +01:00
|
|
|
// Only process Opus packets
|
2026-01-15 20:30:03 +01:00
|
|
|
// Parse Voice Header (Server -> Client)
|
2026-01-15 21:35:23 +01:00
|
|
|
// VId(2) + CId(2) + Codec(1) + Data
|
|
|
|
|
|
|
|
|
|
var data []byte = pkt.Data
|
|
|
|
|
|
|
|
|
|
// Decrypt if Encrypted
|
|
|
|
|
if !pkt.Header.FlagUnencrypted() {
|
|
|
|
|
if c.Handshake == nil || len(c.Handshake.SharedIV) == 0 {
|
|
|
|
|
log.Println("Received encrypted voice packet but no SharedIV available. Dropping.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
crypto := &protocol.CryptoState{
|
|
|
|
|
SharedIV: c.Handshake.SharedIV,
|
|
|
|
|
SharedMac: c.Handshake.SharedMac,
|
|
|
|
|
GenerationID: 0,
|
|
|
|
|
}
|
|
|
|
|
// Server->Client = false
|
|
|
|
|
key, nonce := crypto.GenerateKeyNonce(&pkt.Header, false)
|
|
|
|
|
|
|
|
|
|
// Meta for Server->Client: PID(2) + PT(1) = 3 bytes
|
|
|
|
|
meta := make([]byte, 3)
|
|
|
|
|
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
|
|
|
|
meta[2] = pkt.Header.Type
|
|
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
data, err = protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Voice decryption failure is common if keys mismatch or packet loss affects counter?
|
|
|
|
|
// But EAX doesn't depend on counter state, only PID which is in header.
|
|
|
|
|
log.Printf("Voice decryption failed: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(data) < 6 {
|
|
|
|
|
// Ignore empty/too small packets (e.g. silence or end)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The first 2 bytes are the sequence ID, not the client ID for the decoder key.
|
|
|
|
|
// The client ID is at data[2:4]
|
|
|
|
|
// vid := binary.BigEndian.Uint16(data[0:2]) // Sequence ID
|
|
|
|
|
vid := binary.BigEndian.Uint16(data[2:4]) // Talking Client ID (CORRECT KEY)
|
|
|
|
|
codec := data[4]
|
|
|
|
|
voiceData := data[5:]
|
|
|
|
|
|
|
|
|
|
// Only process Opus packets
|
|
|
|
|
if codec != CodecOpusVoice && codec != CodecOpusMusic {
|
|
|
|
|
log.Printf("Received non-Opus voice packet (Codec=%d). Ignoring echo.", codec)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channels := 1
|
|
|
|
|
if codec == CodecOpusMusic {
|
|
|
|
|
channels = 2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 1. Get or Create Decoder for this VID
|
|
|
|
|
decoder, ok := c.VoiceDecoders[vid]
|
|
|
|
|
if !ok {
|
|
|
|
|
var err error
|
|
|
|
|
decoder, err = opus.NewDecoder(48000, channels)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Failed to create Opus decoder: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
c.VoiceDecoders[vid] = decoder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Decode Opus to PCM
|
|
|
|
|
// Max frame size for 120ms at 48kHz is 5760 samples
|
|
|
|
|
pcm := make([]int16, 5760*channels)
|
|
|
|
|
n, err := decoder.Decode(voiceData, pcm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Opus decode error: %v", err)
|
2026-01-15 20:30:03 +01:00
|
|
|
return
|
|
|
|
|
}
|
2026-01-15 21:35:23 +01:00
|
|
|
pcm = pcm[:n*channels]
|
|
|
|
|
|
|
|
|
|
if n != 960 {
|
|
|
|
|
log.Printf("WARNING: Unusual Opus frame size: %d samples (expected 960 for 20ms)", n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Process PCM: Reduce Volume (divide by 4)
|
|
|
|
|
for i := range pcm {
|
|
|
|
|
pcm[i] = pcm[i] / 4
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Get or Create Encoder
|
|
|
|
|
if c.VoiceEncoder == nil {
|
|
|
|
|
var err error
|
|
|
|
|
app := opus.AppVoIP
|
|
|
|
|
if channels == 2 {
|
|
|
|
|
app = opus.AppAudio
|
|
|
|
|
}
|
|
|
|
|
encoder, err := opus.NewEncoder(48000, channels, app)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Failed to create Opus encoder: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optimize Quality
|
|
|
|
|
encoder.SetBitrate(64000) // 64 kbps (High Quality for Voice)
|
|
|
|
|
encoder.SetComplexity(10) // Max Complexity (Best Quality)
|
|
|
|
|
|
|
|
|
|
c.VoiceEncoder = encoder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Encode PCM to Opus
|
|
|
|
|
encoded := make([]byte, 1024)
|
|
|
|
|
nEnc, err := c.VoiceEncoder.Encode(pcm, encoded)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Opus encode error: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
encoded = encoded[:nEnc]
|
|
|
|
|
|
|
|
|
|
// log.Printf("Voice Processed (CGO): VID=%d, In=%d bytes, PCM=%d samples, Out=%d bytes", vid, len(voiceData), n, nEnc)
|
2026-01-15 20:30:03 +01:00
|
|
|
|
2026-01-15 21:35:23 +01:00
|
|
|
// 6. Build echo packet (Client -> Server)
|
|
|
|
|
// Payload format: [VId(2)] [Codec(1)] [Data...]
|
|
|
|
|
echoData := make([]byte, 2+1+len(encoded))
|
2026-01-15 20:30:03 +01:00
|
|
|
|
2026-01-15 21:35:23 +01:00
|
|
|
c.VoicePacketID++ // Increment counter before using it
|
2026-01-15 20:30:03 +01:00
|
|
|
|
2026-01-15 21:35:23 +01:00
|
|
|
// Correctly set VId in Payload to be the Sequence Number (not ClientID)
|
|
|
|
|
binary.BigEndian.PutUint16(echoData[0:2], c.VoicePacketID)
|
2026-01-15 20:30:03 +01:00
|
|
|
echoData[2] = codec
|
2026-01-15 21:35:23 +01:00
|
|
|
copy(echoData[3:], encoded)
|
2026-01-15 20:30:03 +01:00
|
|
|
|
|
|
|
|
echoPkt := protocol.NewPacket(protocol.PacketTypeVoice, echoData)
|
2026-01-15 21:35:23 +01:00
|
|
|
echoPkt.Header.PacketID = c.VoicePacketID
|
2026-01-15 20:30:03 +01:00
|
|
|
echoPkt.Header.ClientID = c.ClientID
|
|
|
|
|
|
2026-01-15 21:35:23 +01:00
|
|
|
// Encrypt voice packet
|
2026-01-15 20:30:03 +01:00
|
|
|
if c.Handshake != nil && len(c.Handshake.SharedIV) > 0 {
|
|
|
|
|
crypto := &protocol.CryptoState{
|
|
|
|
|
SharedIV: c.Handshake.SharedIV,
|
|
|
|
|
SharedMac: c.Handshake.SharedMac,
|
|
|
|
|
GenerationID: 0,
|
|
|
|
|
}
|
|
|
|
|
key, nonce := crypto.GenerateKeyNonce(&echoPkt.Header, true)
|
|
|
|
|
|
|
|
|
|
meta := make([]byte, 5)
|
|
|
|
|
binary.BigEndian.PutUint16(meta[0:2], echoPkt.Header.PacketID)
|
|
|
|
|
binary.BigEndian.PutUint16(meta[2:4], echoPkt.Header.ClientID)
|
|
|
|
|
meta[4] = echoPkt.Header.Type
|
|
|
|
|
|
|
|
|
|
encData, mac, err := protocol.EncryptEAX(key, nonce, meta, echoPkt.Data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Voice encryption failed: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
echoPkt.Data = encData
|
|
|
|
|
copy(echoPkt.Header.MAC[:], mac)
|
|
|
|
|
} else {
|
|
|
|
|
if c.Handshake != nil && len(c.Handshake.SharedMac) > 0 {
|
|
|
|
|
copy(echoPkt.Header.MAC[:], c.Handshake.SharedMac)
|
|
|
|
|
} else {
|
|
|
|
|
echoPkt.Header.MAC = protocol.HandshakeMac
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Conn.SendPacket(echoPkt)
|
|
|
|
|
}
|