client can join to channel
This commit is contained in:
6
go.mod
6
go.mod
@@ -6,4 +6,8 @@ toolchain go1.24.11
|
||||
|
||||
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
4
go.sum
@@ -1,4 +1,8 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
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/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user