mirror of
https://github.com/yuanyuanxiang/SimpleRemoter.git
synced 2026-01-21 23:13:08 +08:00
203 lines
6.0 KiB
Go
203 lines
6.0 KiB
Go
|
|
package protocol
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/binary"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||
|
|
"golang.org/x/text/transform"
|
||
|
|
)
|
||
|
|
|
||
|
|
// gbkToUTF8 converts GBK encoded bytes to UTF-8 string
|
||
|
|
func gbkToUTF8(data []byte) string {
|
||
|
|
// Find the first null byte and truncate there
|
||
|
|
if idx := bytes.IndexByte(data, 0); idx >= 0 {
|
||
|
|
data = data[:idx]
|
||
|
|
}
|
||
|
|
if len(data) == 0 {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
// Try to decode as GBK
|
||
|
|
reader := transform.NewReader(bytes.NewReader(data), simplifiedchinese.GBK.NewDecoder())
|
||
|
|
buf := new(bytes.Buffer)
|
||
|
|
_, err := buf.ReadFrom(reader)
|
||
|
|
if err != nil {
|
||
|
|
// If GBK decoding fails, try treating as UTF-8 or ASCII
|
||
|
|
return cleanString(string(data))
|
||
|
|
}
|
||
|
|
return cleanString(buf.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
// cleanString removes non-printable characters except common whitespace
|
||
|
|
func cleanString(s string) string {
|
||
|
|
var result strings.Builder
|
||
|
|
for _, r := range s {
|
||
|
|
if r >= 32 || r == '\t' || r == '\n' || r == '\r' {
|
||
|
|
result.WriteRune(r)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return strings.TrimSpace(result.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
// Command tokens - matching the C++ definitions
|
||
|
|
const (
|
||
|
|
// Server -> Client commands
|
||
|
|
CommandActived byte = 0 // COMMAND_ACTIVED
|
||
|
|
CommandBye byte = 204 // COMMAND_BYE - disconnect
|
||
|
|
CommandHeartbeat byte = 216 // CMD_HEARTBEAT_ACK
|
||
|
|
|
||
|
|
// Client -> Server tokens
|
||
|
|
TokenAuth byte = 100 // TOKEN_AUTH - authorization required
|
||
|
|
TokenHeartbeat byte = 101 // TOKEN_HEARTBEAT
|
||
|
|
TokenLogin byte = 102 // TOKEN_LOGIN - login packet
|
||
|
|
)
|
||
|
|
|
||
|
|
// LOGIN_INFOR structure size and offsets (matching C++ struct with default alignment)
|
||
|
|
// Note: C++ struct uses default alignment (4-byte for uint32/int)
|
||
|
|
const (
|
||
|
|
LoginInfoSize = 980 // Total size of LOGIN_INFOR struct (with alignment padding)
|
||
|
|
|
||
|
|
// Field offsets (with alignment padding)
|
||
|
|
OffsetToken = 0 // 1 byte (unsigned char)
|
||
|
|
OffsetOsVerInfoEx = 1 // 156 bytes (char[156])
|
||
|
|
// 3 bytes padding here to align dwCPUMHz to 4-byte boundary
|
||
|
|
OffsetCPUMHz = 160 // 4 bytes (unsigned int) - aligned to 4
|
||
|
|
OffsetModuleVersion = 164 // 24 bytes (char[24])
|
||
|
|
OffsetPCName = 188 // 240 bytes (char[240])
|
||
|
|
OffsetMasterID = 428 // 20 bytes (char[20])
|
||
|
|
OffsetWebCamExist = 448 // 4 bytes (int) - aligned to 4
|
||
|
|
OffsetSpeed = 452 // 4 bytes (unsigned int)
|
||
|
|
OffsetStartTime = 456 // 20 bytes (char[20])
|
||
|
|
OffsetReserved = 476 // 512 bytes (char[512])
|
||
|
|
)
|
||
|
|
|
||
|
|
// LoginInfo represents client login information
|
||
|
|
type LoginInfo struct {
|
||
|
|
Token byte
|
||
|
|
OsVerInfo string // OS version info
|
||
|
|
CPUMHz uint32
|
||
|
|
ModuleVersion string
|
||
|
|
PCName string // Computer name
|
||
|
|
MasterID string
|
||
|
|
WebCamExist bool
|
||
|
|
Speed uint32
|
||
|
|
StartTime string
|
||
|
|
Reserved string // Contains additional info separated by |
|
||
|
|
}
|
||
|
|
|
||
|
|
// ParseLoginInfo parses LOGIN_INFOR from data
|
||
|
|
func ParseLoginInfo(data []byte) (*LoginInfo, error) {
|
||
|
|
if len(data) < 100 { // Minimum size check
|
||
|
|
return nil, ErrInvalidData
|
||
|
|
}
|
||
|
|
|
||
|
|
info := &LoginInfo{
|
||
|
|
Token: data[0],
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse OS version info (offset 1, 156 bytes)
|
||
|
|
// The C++ client fills this with a readable string like "Windows 10" via getSystemName()
|
||
|
|
if len(data) >= OffsetOsVerInfoEx+156 {
|
||
|
|
info.OsVerInfo = parseOsVersionInfo(data[OffsetOsVerInfoEx : OffsetOsVerInfoEx+156])
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse CPU MHz (offset 160, 4 bytes)
|
||
|
|
if len(data) >= OffsetCPUMHz+4 {
|
||
|
|
info.CPUMHz = binary.LittleEndian.Uint32(data[OffsetCPUMHz:])
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse module version (offset 164, 24 bytes)
|
||
|
|
// This contains date string like "Dec 19 2025"
|
||
|
|
if len(data) >= OffsetModuleVersion+24 {
|
||
|
|
info.ModuleVersion = gbkToUTF8(data[OffsetModuleVersion : OffsetModuleVersion+24])
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse PC name (offset 188, 240 bytes)
|
||
|
|
if len(data) >= OffsetPCName+240 {
|
||
|
|
info.PCName = gbkToUTF8(data[OffsetPCName : OffsetPCName+240])
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse Master ID (offset 428, 20 bytes)
|
||
|
|
if len(data) >= OffsetMasterID+20 {
|
||
|
|
info.MasterID = gbkToUTF8(data[OffsetMasterID : OffsetMasterID+20])
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse WebCam exist (offset 448, 4 bytes)
|
||
|
|
if len(data) >= OffsetWebCamExist+4 {
|
||
|
|
info.WebCamExist = binary.LittleEndian.Uint32(data[OffsetWebCamExist:]) != 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse Speed (offset 452, 4 bytes)
|
||
|
|
if len(data) >= OffsetSpeed+4 {
|
||
|
|
info.Speed = binary.LittleEndian.Uint32(data[OffsetSpeed:])
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse Start time (offset 456, 20 bytes)
|
||
|
|
if len(data) >= OffsetStartTime+20 {
|
||
|
|
info.StartTime = gbkToUTF8(data[OffsetStartTime : OffsetStartTime+20])
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse Reserved (offset 476, 512 bytes) - contains additional info
|
||
|
|
if len(data) >= OffsetReserved+512 {
|
||
|
|
info.Reserved = gbkToUTF8(data[OffsetReserved : OffsetReserved+512])
|
||
|
|
} else if len(data) > OffsetReserved {
|
||
|
|
info.Reserved = gbkToUTF8(data[OffsetReserved:])
|
||
|
|
}
|
||
|
|
|
||
|
|
return info, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// parseOsVersionInfo parses the OS version info field
|
||
|
|
// The C++ client fills this with a readable string like "Windows 10" via getSystemName()
|
||
|
|
func parseOsVersionInfo(data []byte) string {
|
||
|
|
return gbkToUTF8(data)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ParseReserved parses the reserved field into a slice of strings
|
||
|
|
func (info *LoginInfo) ParseReserved() []string {
|
||
|
|
if info.Reserved == "" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return strings.Split(info.Reserved, "|")
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetReservedField returns a specific field from reserved data by index
|
||
|
|
// Fields: ClientType(0), SystemBits(1), CPU(2), Memory(3), FilePath(4),
|
||
|
|
// Reserved(5), InstallTime(6), InstallInfo(7), ProgramBits(8), ExpiredDate(9),
|
||
|
|
// ClientLoc(10), ClientPubIP(11), ExeVersion(12), Username(13), IsAdmin(14)
|
||
|
|
func (info *LoginInfo) GetReservedField(index int) string {
|
||
|
|
fields := info.ParseReserved()
|
||
|
|
if index >= 0 && index < len(fields) {
|
||
|
|
return fields[index]
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validation structure for TOKEN_AUTH
|
||
|
|
type Validation struct {
|
||
|
|
From string // Start date
|
||
|
|
To string // End date
|
||
|
|
Admin string // Admin address
|
||
|
|
Port int // Admin port
|
||
|
|
Checksum string // Reserved field
|
||
|
|
}
|
||
|
|
|
||
|
|
// BuildValidation creates a validation response
|
||
|
|
func BuildValidation(days float64, admin string, port int) []byte {
|
||
|
|
// This would build the validation structure
|
||
|
|
// For now, return a simple structure
|
||
|
|
data := make([]byte, 160) // Size of Validation struct
|
||
|
|
data[0] = TokenAuth
|
||
|
|
|
||
|
|
// Fill in fields...
|
||
|
|
// From: 20 bytes
|
||
|
|
// To: 20 bytes
|
||
|
|
// Admin: 100 bytes
|
||
|
|
// Port: 4 bytes
|
||
|
|
// Checksum: 16 bytes
|
||
|
|
|
||
|
|
return data
|
||
|
|
}
|