mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-03-22 01:54:12 -05:00
254 lines
7.6 KiB
Go
254 lines
7.6 KiB
Go
package serverbrowser
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"github.com/logrusorgru/aurora/v3"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"wwfc/common"
|
|
"wwfc/logging"
|
|
"wwfc/qr2"
|
|
)
|
|
|
|
const (
|
|
// Server flags
|
|
UnsolicitedUDPFlag = 1 << 0 // 0x01 / 1
|
|
PrivateIPFlag = 1 << 1 // 0x02 / 2
|
|
ConnectNegotiateFlag = 1 << 2 // 0x04 / 4
|
|
ICMPIPFlag = 1 << 3 // 0x08 / 8
|
|
NonstandardPortFlag = 1 << 4 // 0x10 / 16
|
|
NonstandardPrivatePortFlag = 1 << 5 // 0x20 / 32
|
|
HasKeysFlag = 1 << 6 // 0x40 / 64
|
|
HasFullRulesFlag = 1 << 7 // 0x80 / 128
|
|
|
|
// Key Type list
|
|
KeyTypeString = 0x00
|
|
KeyTypeByte = 0x01
|
|
KeyTypeShort = 0x02
|
|
|
|
// Options for ServerListRequest
|
|
SendFieldsForAllOption = 1 << 0 // 0x01 / 1
|
|
NoServerListOption = 1 << 1 // 0x02 / 2
|
|
PushUpdatesOption = 1 << 2 // 0x04 / 4
|
|
AlternateSourceIPOption = 1 << 3 // 0x08 / 8
|
|
SendGroupsOption = 1 << 5 // 0x20 / 32
|
|
NoListCacheOption = 1 << 6 // 0x40 / 64
|
|
LimitResultCountOption = 1 << 7 // 0x80 / 128
|
|
)
|
|
|
|
func popString(buffer []byte, index int) (string, int) {
|
|
str := common.GetString(buffer[index:])
|
|
return str, index + len(str) + 1
|
|
}
|
|
|
|
func popBytes(buffer []byte, index int, size int) ([]byte, int) {
|
|
return buffer[index : index+size], index + size
|
|
}
|
|
|
|
func popUint32(buffer []byte, index int) (uint32, int) {
|
|
return binary.BigEndian.Uint32(buffer[index:]), index + 4
|
|
}
|
|
|
|
func handleServerListRequest(conn net.Conn, buffer []byte) {
|
|
index := 9
|
|
queryGame, index := popString(buffer, index)
|
|
gameName, index := popString(buffer, index)
|
|
challenge, index := popBytes(buffer, index, 8)
|
|
filter, index := popString(buffer, index)
|
|
fields, index := popString(buffer, index)
|
|
options, index := popUint32(buffer, index)
|
|
|
|
logging.Info(ModuleName, "queryGame:", aurora.Cyan(queryGame).String(), "- gameName:", aurora.Cyan(gameName).String(), "- filter:", aurora.Cyan(filter).String(), "- fields:", aurora.Cyan(fields).String())
|
|
|
|
gameInfo := common.GetGameInfoByName(gameName)
|
|
if gameInfo == nil {
|
|
// Game doesn't exist in the game list.
|
|
return
|
|
}
|
|
|
|
var output []byte
|
|
for _, s := range strings.Split(strings.Split(conn.RemoteAddr().String(), ":")[0], ".") {
|
|
val, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
output = append(output, byte(val))
|
|
}
|
|
|
|
var fieldList []string
|
|
for _, field := range strings.Split(fields, "\\") {
|
|
if len(field) == 0 || field == " " {
|
|
continue
|
|
}
|
|
|
|
fieldList = append(fieldList, field)
|
|
}
|
|
|
|
if options&NoServerListOption != 0 || len(fieldList) == 0 {
|
|
// The client requests its own public IP and game port
|
|
logging.Info(ModuleName, "Reply without server list", aurora.Cyan(conn.RemoteAddr()))
|
|
|
|
// The default game port 6500
|
|
output = binary.BigEndian.AppendUint16(output, 6500)
|
|
|
|
// Write the encrypted reply
|
|
// TODO: Key is currently hardcoded to Mario Kart Wii
|
|
conn.Write(common.EncryptTypeX([]byte(gameInfo.SecretKey), challenge, output))
|
|
return
|
|
}
|
|
|
|
logging.Info(ModuleName, "Reply with server list", aurora.Cyan(conn.RemoteAddr()))
|
|
|
|
// The client's port
|
|
port, err := strconv.Atoi(strings.Split(conn.RemoteAddr().String(), ":")[1])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
output = binary.BigEndian.AppendUint16(output, uint16(port))
|
|
|
|
output = append(output, byte(len(fieldList)))
|
|
for _, field := range fieldList {
|
|
output = append(output, 0x00) // Key type (0 = string, 1 = byte, 2 = short)
|
|
output = append(output, []byte(field)...) // String
|
|
output = append(output, 0x00) // String terminator
|
|
}
|
|
output = append(output, 0x00) // Zero length string to end the list
|
|
|
|
publicIP, _ := common.IPFormatToString(conn.RemoteAddr().String())
|
|
servers := filterServers(qr2.GetSessionServers(), queryGame, filter, publicIP)
|
|
|
|
for _, server := range servers {
|
|
var flags byte
|
|
var flagsBuffer []byte
|
|
|
|
// Server will always have keys
|
|
flags |= HasKeysFlag
|
|
|
|
var natneg string
|
|
var exists bool
|
|
if natneg, exists = server["natneg"]; exists && natneg != "0" {
|
|
flags |= ConnectNegotiateFlag
|
|
}
|
|
|
|
var publicip string
|
|
if publicip, exists = server["publicip"]; !exists {
|
|
logging.Error(ModuleName, "Server exists without public IP")
|
|
continue
|
|
}
|
|
|
|
ip, err := strconv.ParseInt(publicip, 10, 32)
|
|
if err != nil {
|
|
logging.Error(ModuleName, "Server has invalid public IP value:", aurora.Cyan(publicip))
|
|
}
|
|
|
|
flagsBuffer = binary.BigEndian.AppendUint32(flagsBuffer, uint32(ip))
|
|
|
|
var port string
|
|
port, exists = server["publicport"]
|
|
if !exists {
|
|
// Fall back to local port if public port doesn't exist
|
|
if port, exists = server["localport"]; !exists {
|
|
logging.Error(ModuleName, "Server exists without port (publicip =", aurora.Cyan(publicip).String()+")")
|
|
continue
|
|
}
|
|
}
|
|
|
|
portValue, err := strconv.ParseUint(port, 10, 16)
|
|
if err != nil {
|
|
logging.Error(ModuleName, "Server has invalid port value:", aurora.Cyan(port))
|
|
continue
|
|
}
|
|
|
|
flags |= NonstandardPortFlag
|
|
flagsBuffer = binary.BigEndian.AppendUint16(flagsBuffer, uint16(portValue))
|
|
|
|
// Use the first local IP if it exists, this is used to skip natneg if multiple players are on the same network
|
|
if localip0, exists := server["localip0"]; exists {
|
|
flags |= PrivateIPFlag
|
|
|
|
// localip is written like "192.168.255.255" for example, so it needs to be parsed
|
|
ipSplit := strings.Split(localip0, ".")
|
|
if len(ipSplit) != 4 {
|
|
logging.Error(ModuleName, "Server has invalid local IP:", aurora.Cyan(localip0))
|
|
continue
|
|
}
|
|
|
|
err = nil
|
|
for _, s := range ipSplit {
|
|
val, err := strconv.ParseUint(s, 10, 8)
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
flagsBuffer = append(flagsBuffer, byte(val))
|
|
}
|
|
|
|
if err != nil {
|
|
logging.Error(ModuleName, "Server has invalid local IP value:", aurora.Cyan(localip0))
|
|
continue
|
|
}
|
|
}
|
|
|
|
if localport, exists := server["localport"]; exists {
|
|
portValue, err = strconv.ParseUint(localport, 10, 16)
|
|
if err != nil {
|
|
logging.Error(ModuleName, "Server has invalid local port value:", aurora.Cyan(localport))
|
|
continue
|
|
}
|
|
|
|
flags |= NonstandardPrivatePortFlag
|
|
flagsBuffer = binary.BigEndian.AppendUint16(flagsBuffer, uint16(portValue))
|
|
}
|
|
|
|
// Just a dummy IP? This is taken from dwc_network_server_emulator
|
|
// TODO: Check if this is actually needed
|
|
flags |= ICMPIPFlag
|
|
flagsBuffer = append(flagsBuffer, []byte{0, 0, 0, 0}...)
|
|
|
|
// Finally, write the server buffer to the output
|
|
output = append(output, flags)
|
|
output = append(output, flagsBuffer...)
|
|
|
|
if (flags & HasKeysFlag) == 0 {
|
|
// Server does not have keys, so skip them
|
|
logging.Info(ModuleName, "Wrote server without keys")
|
|
continue
|
|
}
|
|
|
|
// Add the requested fields
|
|
for _, field := range fieldList {
|
|
output = append(output, 0xff)
|
|
|
|
if str, exists := server[field]; exists {
|
|
output = append(output, []byte(str)...)
|
|
}
|
|
|
|
// Add null terminator so the string will be empty if the field doesn't exist
|
|
output = append(output, 0x00)
|
|
}
|
|
|
|
logging.Info(ModuleName, "Wrote server with keys")
|
|
}
|
|
|
|
// Server with 0 flags and IP of 0xffffffff terminates the list
|
|
output = append(output, []byte{0x00, 0xff, 0xff, 0xff, 0xff}...)
|
|
|
|
// Write the encrypted reply
|
|
conn.Write(common.EncryptTypeX([]byte(gameInfo.SecretKey), challenge, output))
|
|
}
|
|
|
|
func handleSendMessageRequest(conn net.Conn, buffer []byte) {
|
|
// Read destination IP from buffer
|
|
destIP := fmt.Sprintf("%d.%d.%d.%d:%d", buffer[3], buffer[4], buffer[5], buffer[6], binary.BigEndian.Uint16(buffer[7:9]))
|
|
|
|
logging.Notice(ModuleName, "Send message from", aurora.BrightCyan(conn.RemoteAddr()), "to", aurora.BrightCyan(destIP).String())
|
|
|
|
// TODO: Perform basic packet verification
|
|
// TODO SECURITY: Check if the selected IP is actually online, or at least make sure it's not a local IP
|
|
|
|
qr2.SendClientMessage(destIP, buffer[9:])
|
|
}
|