client can join to channel

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-15 17:09:32 +01:00
parent 47b8173045
commit 8d98579faa
3 changed files with 133 additions and 17 deletions

View File

@@ -10,6 +10,8 @@ import (
"go-ts/pkg/protocol"
"go-ts/pkg/transport"
"github.com/dgryski/go-quicklz"
)
type Channel struct {
@@ -32,6 +34,12 @@ type Client struct {
// State
Connected bool
// Fragment reassembly
FragmentBuffer []byte
FragmentStartPktID uint16
FragmentCompressed bool
Fragmenting bool
// Server Data
Channels map[uint64]*Channel
}
@@ -284,8 +292,73 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
c.Connected = true
}
// Fragment reassembly logic:
// - First fragment: Fragmented=true, optionally Compressed=true -> start buffer
// - Middle fragments: Fragmented=false, Compressed=false -> append to buffer
// - Last fragment: Fragmented=true -> append and process
isFragmented := pkt.Header.FlagFragmented()
if isFragmented && !c.Fragmenting {
// First fragment - start collecting
c.Fragmenting = true
c.FragmentBuffer = make([]byte, 0, 4096)
c.FragmentBuffer = append(c.FragmentBuffer, data...)
c.FragmentStartPktID = pkt.Header.PacketID
c.FragmentCompressed = pkt.Header.FlagCompressed()
log.Printf("Fragment start (PID=%d, Compressed=%v, Len=%d)", pkt.Header.PacketID, c.FragmentCompressed, len(data))
return nil // Wait for more fragments
} else if c.Fragmenting && !isFragmented {
// Middle fragment - append
c.FragmentBuffer = append(c.FragmentBuffer, data...)
log.Printf("Fragment continue (PID=%d, TotalLen=%d)", pkt.Header.PacketID, len(c.FragmentBuffer))
return nil // Wait for more fragments
} else if c.Fragmenting && isFragmented {
// Last fragment - complete reassembly
c.FragmentBuffer = append(c.FragmentBuffer, data...)
log.Printf("Fragment end (PID=%d, TotalLen=%d)", pkt.Header.PacketID, len(c.FragmentBuffer))
data = c.FragmentBuffer
// Decompress if first fragment was compressed
if c.FragmentCompressed {
decompressed, err := quicklz.Decompress(data)
if err != nil {
log.Printf("QuickLZ decompression of fragmented data failed: %v", err)
// Fallback to raw data
} else {
log.Printf("Decompressed fragmented: %d -> %d bytes", len(data), len(decompressed))
data = decompressed
}
}
// Reset fragment state
c.Fragmenting = false
c.FragmentBuffer = nil
} else {
// Non-fragmented packet - decompress if needed
if pkt.Header.FlagCompressed() {
decompressed, err := quicklz.Decompress(data)
if err != nil {
log.Printf("QuickLZ decompression failed: %v (falling back to raw)", err)
// Fallback to raw data - might not be compressed despite flag
} else {
log.Printf("Decompressed: %d -> %d bytes", len(data), len(decompressed))
data = decompressed
}
}
}
cmdStr := string(data)
// Debug: Log packet flags and raw command preview
log.Printf("Debug Packet: Compressed=%v, Fragmented=%v, RawLen=%d, Preview=%q",
pkt.Header.FlagCompressed(), pkt.Header.FlagFragmented(), len(data),
func() string {
if len(cmdStr) > 100 {
return cmdStr[:100]
}
return cmdStr
}())
// Fix Garbage Headers (TS3 often sends binary garbage before command)
// Scan for first valid lower case [a-z] char (Most commands are lowercase)
validStart := strings.IndexFunc(cmdStr, func(r rune) bool {
@@ -345,6 +418,13 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
}
}
if targetChan == nil {
if ch, ok := c.Channels[2]; ok {
log.Printf("Name parsing failed. Defaulting to Channel 2 as 'Test'.")
targetChan = ch
}
}
if targetChan != nil {
log.Printf("Found target channel 'Test' (ID=%d). Joining...", targetChan.ID)
@@ -358,22 +438,17 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
pkt := protocol.NewPacket(protocol.PacketTypeCommand, []byte(cmd))
// Encrypt
// key, nonce, mac, _ := c.getCryptoState() // Unused
// Meta
meta := make([]byte, 5)
binary.BigEndian.PutUint16(meta[0:2], c.PacketIDCounterC2S+1) // Next ID
// Set NewProtocol flag (required for all commands) BEFORE computing meta
pkt.Header.Type |= protocol.PacketFlagNewProtocol
pkt.Header.PacketID = c.PacketIDCounterC2S + 1
pkt.Header.ClientID = c.ClientID
c.PacketIDCounterC2S++
binary.BigEndian.PutUint16(meta[2:4], c.ClientID)
meta[4] = pkt.Header.Type
// TODO: Use correct crypto keys (SharedSecret if available)
// My getCryptoState returns correct ones?
// Let's manually do it to match sendClientInit logic for now or refactor later.
// Actually, if we are in Full Session, we should use SharedSecret.
// Handshake Step 6 -> SharedSecret.
// 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 // Now includes NewProtocol flag
crypto := &protocol.CryptoState{
SharedIV: c.Handshake.SharedIV,
@@ -386,12 +461,25 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
pkt.Data = encData
copy(pkt.Header.MAC[:], mac)
log.Printf("Sending clientmove command: clid=%d cid=%d (PID=%d)", c.ClientID, targetChan.ID, pkt.Header.PacketID)
c.Conn.SendPacket(pkt)
}
case "notifycliententerview":
// A client entered the server
if nick, ok := args["client_nickname"]; ok {
log.Printf("Client entered: %s", protocol.Unescape(nick))
nick := ""
if n, ok := args["client_nickname"]; ok {
nick = protocol.Unescape(n)
log.Printf("Client entered: %s", nick)
// If this matches our nickname, store the ClientID (Fallback if initserver missed)
if nick == c.Nickname && c.ClientID == 0 {
if clidStr, ok := args["clid"]; ok {
var id uint64
fmt.Sscanf(clidStr, "%d", &id)
c.ClientID = uint16(id)
log.Printf("Identified Self via notifycliententerview! ClientID: %d", c.ClientID)
}
}
}
case "notifytextmessage":
if msg, ok := args["msg"]; ok {
@@ -436,8 +524,27 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
log.Println("Received Badges (Ignored)")
return nil
}
// Fuzzy match for corrupted notifycliententerview
if strings.HasPrefix(cmd, "notifyclient") {
// Attempt to process it anyway
nick := ""
if n, ok := args["client_nickname"]; ok {
nick = protocol.Unescape(n)
log.Printf("Fuzzy Notify Client Entered: %s", nick)
if nick == c.Nickname && c.ClientID == 0 {
if clidStr, ok := args["clid"]; ok {
var id uint64
fmt.Sscanf(clidStr, "%d", &id)
c.ClientID = uint16(id)
log.Printf("Identified Self via Fuzzy Notify! ClientID: %d", c.ClientID)
}
}
}
return nil
}
// Log unknown commands for debugging
log.Printf("Unhandled command: %s", cmd)
log.Printf("Unhandled command: %s Args: %v", cmd, args)
}
return nil
@@ -559,5 +666,6 @@ func (c *Client) sendClientInit() error {
copy(pkt.Header.MAC[:], mac)
log.Println("Sending clientinit (Packet 2) [Encrypted with SharedSecret]...")
c.PacketIDCounterC2S = 2 // Update counter after clientinit
return c.Conn.SendPacket(pkt)
}