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

6
go.mod
View File

@@ -6,4 +6,8 @@ toolchain go1.24.11
require filippo.io/edwards25519 v1.1.0 require filippo.io/edwards25519 v1.1.0
require golang.org/x/crypto v0.47.0 // indirect require (
github.com/Hiroko103/go-quicklz v0.0.0-20190115215310-59904abc50d0 // indirect
github.com/dgryski/go-quicklz v0.0.0-20151014073603-d7042a82d57e // indirect
golang.org/x/crypto v0.47.0 // indirect
)

4
go.sum
View File

@@ -1,4 +1,8 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Hiroko103/go-quicklz v0.0.0-20190115215310-59904abc50d0 h1:6+dYUy8Dg9WdPkXnA3MtdAZq1ry1+pLrc+ZMCMi0MPU=
github.com/Hiroko103/go-quicklz v0.0.0-20190115215310-59904abc50d0/go.mod h1:qBfAulKipfG2mar83JHqv6ykKbgDXHbmTWPY5gvr2zw=
github.com/dgryski/go-quicklz v0.0.0-20151014073603-d7042a82d57e h1:MhBotBstN1h/GeA7lx7xstbFB8avummjt+nzOi2cY7Y=
github.com/dgryski/go-quicklz v0.0.0-20151014073603-d7042a82d57e/go.mod h1:XLmYwGWgVzMPLlMmcNcWt3b5ixRabPLstWnPVEDRhzc=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=

View File

@@ -10,6 +10,8 @@ import (
"go-ts/pkg/protocol" "go-ts/pkg/protocol"
"go-ts/pkg/transport" "go-ts/pkg/transport"
"github.com/dgryski/go-quicklz"
) )
type Channel struct { type Channel struct {
@@ -32,6 +34,12 @@ type Client struct {
// State // State
Connected bool Connected bool
// Fragment reassembly
FragmentBuffer []byte
FragmentStartPktID uint16
FragmentCompressed bool
Fragmenting bool
// Server Data // Server Data
Channels map[uint64]*Channel Channels map[uint64]*Channel
} }
@@ -284,8 +292,73 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
c.Connected = true 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) 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) // Fix Garbage Headers (TS3 often sends binary garbage before command)
// Scan for first valid lower case [a-z] char (Most commands are lowercase) // Scan for first valid lower case [a-z] char (Most commands are lowercase)
validStart := strings.IndexFunc(cmdStr, func(r rune) bool { 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 { if targetChan != nil {
log.Printf("Found target channel 'Test' (ID=%d). Joining...", targetChan.ID) 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)) pkt := protocol.NewPacket(protocol.PacketTypeCommand, []byte(cmd))
// Encrypt // Set NewProtocol flag (required for all commands) BEFORE computing meta
// key, nonce, mac, _ := c.getCryptoState() // Unused pkt.Header.Type |= protocol.PacketFlagNewProtocol
// Meta
meta := make([]byte, 5)
binary.BigEndian.PutUint16(meta[0:2], c.PacketIDCounterC2S+1) // Next ID
pkt.Header.PacketID = c.PacketIDCounterC2S + 1 pkt.Header.PacketID = c.PacketIDCounterC2S + 1
pkt.Header.ClientID = c.ClientID
c.PacketIDCounterC2S++ c.PacketIDCounterC2S++
binary.BigEndian.PutUint16(meta[2:4], c.ClientID)
meta[4] = pkt.Header.Type
// TODO: Use correct crypto keys (SharedSecret if available) // Meta for Client->Server: PID(2) + CID(2) + PT(1) = 5 bytes
// My getCryptoState returns correct ones? meta := make([]byte, 5)
// Let's manually do it to match sendClientInit logic for now or refactor later. binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
// Actually, if we are in Full Session, we should use SharedSecret. binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID)
// Handshake Step 6 -> SharedSecret. meta[4] = pkt.Header.Type // Now includes NewProtocol flag
crypto := &protocol.CryptoState{ crypto := &protocol.CryptoState{
SharedIV: c.Handshake.SharedIV, SharedIV: c.Handshake.SharedIV,
@@ -386,12 +461,25 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
pkt.Data = encData pkt.Data = encData
copy(pkt.Header.MAC[:], mac) 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) c.Conn.SendPacket(pkt)
} }
case "notifycliententerview": case "notifycliententerview":
// A client entered the server // A client entered the server
if nick, ok := args["client_nickname"]; ok { nick := ""
log.Printf("Client entered: %s", protocol.Unescape(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": case "notifytextmessage":
if msg, ok := args["msg"]; ok { if msg, ok := args["msg"]; ok {
@@ -436,8 +524,27 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
log.Println("Received Badges (Ignored)") log.Println("Received Badges (Ignored)")
return nil 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 unknown commands for debugging
log.Printf("Unhandled command: %s", cmd) log.Printf("Unhandled command: %s Args: %v", cmd, args)
} }
return nil return nil
@@ -559,5 +666,6 @@ func (c *Client) sendClientInit() error {
copy(pkt.Header.MAC[:], mac) copy(pkt.Header.MAC[:], mac)
log.Println("Sending clientinit (Packet 2) [Encrypted with SharedSecret]...") log.Println("Sending clientinit (Packet 2) [Encrypted with SharedSecret]...")
c.PacketIDCounterC2S = 2 // Update counter after clientinit
return c.Conn.SendPacket(pkt) return c.Conn.SendPacket(pkt)
} }