Support DWC match version 11

This commit is contained in:
mkwcat
2023-12-11 07:24:34 -05:00
parent 66a5d0a778
commit 4a7c6f40d8
3 changed files with 181 additions and 129 deletions

View File

@@ -74,8 +74,12 @@ type MatchCommandDataResvOK struct {
ClientCount uint32
ResvCheckValue uint32
// Only exists in version 3
// Only exists in version 3 and 11
ProfileIDs []uint32
// Version 11
IsFriend bool
UserData uint32
}
type MatchCommandDataResvDeny struct {
@@ -94,11 +98,11 @@ type MatchCommandDataServerCloseClient struct {
}
type MatchCommandDataSuspendMatch struct {
HostProfileID uint32
IsHost bool
SuspendValue *bool
ClientAID *uint32
ClientAIDUsageMask *uint32
HostProfileID uint32
IsHostFlag uint32
Short bool
SuspendValue uint32
ClientAIDValue uint32
}
func GetMatchCommandString(command byte) string {
@@ -170,7 +174,7 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
switch command {
case MatchReservation:
if (version == 3 && len(buffer) != 0xC) || (version == 90 && len(buffer) != 0x24) {
if (version == 3 && len(buffer) != 0xC) || (version == 11 && len(buffer) != 0x14) || (version == 90 && len(buffer) != 0x24) {
break
}
@@ -184,13 +188,30 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
break
}
if version == 3 {
switch version {
case 3:
return MatchCommandData{Reservation: &MatchCommandDataReservation{
MatchType: byte(matchType),
PublicIP: binary.BigEndian.Uint32(buffer[0x04:0x08]),
PublicPort: uint16(publicPort),
}}, true
} else if version == 90 {
case 11:
isFriendValue := binary.LittleEndian.Uint32(buffer[0x0C:0x10])
if isFriendValue > 1 {
break
}
isFriend := isFriendValue != 0
return MatchCommandData{Reservation: &MatchCommandDataReservation{
MatchType: byte(matchType),
PublicIP: binary.BigEndian.Uint32(buffer[0x04:0x08]),
PublicPort: uint16(publicPort),
IsFriend: isFriend,
LocalPlayerCount: binary.LittleEndian.Uint32(buffer[0x10:0x14]),
}}, true
case 90:
localPort := binary.LittleEndian.Uint32(buffer[0x10:0x14])
if localPort > 0xffff {
break
@@ -216,14 +237,16 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
}
case MatchResvOK:
switch version {
case 3:
if version == 3 || version == 11 {
if len(buffer) < 0xC {
break
}
clientCount := binary.LittleEndian.Uint32(buffer[0x00:0x04])
if clientCount > 29 || len(buffer) != int(0xC+clientCount*0x4) {
if version == 3 && (clientCount > 29 || len(buffer) != int(0xC+clientCount*0x4)) {
break
}
if version == 11 && (clientCount > 24 || len(buffer) != int(0x20+clientCount*0x4)) {
break
}
@@ -232,50 +255,73 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
profileIDs = append(profileIDs, binary.LittleEndian.Uint32(buffer[0x4+i*4:0x4+i*4+4]))
}
publicPort := binary.LittleEndian.Uint32(buffer[0x08:0x0C])
index := 0x4 + clientCount*4
publicPort := binary.LittleEndian.Uint32(buffer[index+0x4 : index+0x8])
if publicPort > 0xffff {
break
}
return MatchCommandData{ResvOK: &MatchCommandDataResvOK{
PublicIP: binary.BigEndian.Uint32(buffer[0x04:0x08]),
PublicPort: uint16(publicPort),
ClientCount: clientCount,
ProfileIDs: profileIDs,
}}, true
if version == 3 {
return MatchCommandData{ResvOK: &MatchCommandDataResvOK{
PublicIP: binary.BigEndian.Uint32(buffer[index : index+0x4]),
PublicPort: uint16(publicPort),
ClientCount: clientCount,
ProfileIDs: profileIDs,
}}, true
} else if version == 11 {
isFriendValue := binary.LittleEndian.Uint32(buffer[index+0x8 : index+0xC])
if isFriendValue > 1 {
break
}
isFriend := isFriendValue != 0
case 90:
if len(buffer) != 0x34 {
break
return MatchCommandData{ResvOK: &MatchCommandDataResvOK{
MaxPlayers: binary.LittleEndian.Uint32(buffer[0x14:0x18]),
SenderAID: binary.LittleEndian.Uint32(buffer[index+0xC : index+0x10]),
PublicIP: binary.BigEndian.Uint32(buffer[index : index+0x4]),
PublicPort: uint16(publicPort),
GroupID: binary.LittleEndian.Uint32(buffer[0x10:0x14]),
ClientCount: clientCount,
ProfileIDs: profileIDs,
IsFriend: isFriend,
UserData: binary.LittleEndian.Uint32(buffer[0x18:0x1C]),
}}, true
}
publicPort := binary.LittleEndian.Uint32(buffer[0x10:0x14])
if publicPort > 0xffff {
break
}
localPort := binary.LittleEndian.Uint32(buffer[0x18:0x1C])
if localPort > 0xffff {
break
}
return MatchCommandData{ResvOK: &MatchCommandDataResvOK{
MaxPlayers: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
SenderAID: binary.LittleEndian.Uint32(buffer[0x04:0x08]),
ProfileID: binary.LittleEndian.Uint32(buffer[0x08:0x0C]),
PublicIP: binary.BigEndian.Uint32(buffer[0x0C:0x10]),
PublicPort: uint16(publicPort),
LocalIP: binary.BigEndian.Uint32(buffer[0x14:0x18]),
LocalPort: uint16(localPort),
Unknown: binary.LittleEndian.Uint32(buffer[0x1C:0x20]),
LocalPlayerCount: binary.LittleEndian.Uint32(buffer[0x20:0x24]),
GroupID: binary.LittleEndian.Uint32(buffer[0x24:0x28]),
ReceiverNewAID: binary.LittleEndian.Uint32(buffer[0x28:0x2C]),
ClientCount: binary.LittleEndian.Uint32(buffer[0x2C:0x30]),
ResvCheckValue: binary.LittleEndian.Uint32(buffer[0x30:0x34]),
}}, true
break
}
// Version 90
if len(buffer) != 0x34 {
break
}
publicPort := binary.LittleEndian.Uint32(buffer[0x10:0x14])
if publicPort > 0xffff {
break
}
localPort := binary.LittleEndian.Uint32(buffer[0x18:0x1C])
if localPort > 0xffff {
break
}
return MatchCommandData{ResvOK: &MatchCommandDataResvOK{
MaxPlayers: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
SenderAID: binary.LittleEndian.Uint32(buffer[0x04:0x08]),
ProfileID: binary.LittleEndian.Uint32(buffer[0x08:0x0C]),
PublicIP: binary.BigEndian.Uint32(buffer[0x0C:0x10]),
PublicPort: uint16(publicPort),
LocalIP: binary.BigEndian.Uint32(buffer[0x14:0x18]),
LocalPort: uint16(localPort),
Unknown: binary.LittleEndian.Uint32(buffer[0x1C:0x20]),
LocalPlayerCount: binary.LittleEndian.Uint32(buffer[0x20:0x24]),
GroupID: binary.LittleEndian.Uint32(buffer[0x24:0x28]),
ReceiverNewAID: binary.LittleEndian.Uint32(buffer[0x28:0x2C]),
ClientCount: binary.LittleEndian.Uint32(buffer[0x2C:0x30]),
ResvCheckValue: binary.LittleEndian.Uint32(buffer[0x30:0x34]),
}}, true
case MatchResvDeny:
if len(buffer) != 0x04 {
break
@@ -349,51 +395,21 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
}}, true
case MatchSuspendMatch:
if len(buffer) != 0x08 && len(buffer) != 0x10 {
break
}
isHostValue := binary.LittleEndian.Uint32(buffer[0x04:0x08])
if isHostValue > 1 {
break
}
isHost := isHostValue != 0
if len(buffer) == 0x8 {
if !isHost {
// This is just the host acknowledging a client request
break
}
if len(buffer) == 0x08 {
return MatchCommandData{SuspendMatch: &MatchCommandDataSuspendMatch{
HostProfileID: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
IsHost: true,
IsHostFlag: binary.LittleEndian.Uint32(buffer[0x04:0x08]),
Short: true,
}}, true
}
suspendValueInt := binary.LittleEndian.Uint32(buffer[0x04:0x08])
if suspendValueInt > 1 {
break
}
suspendValue := suspendValueInt != 0
suspendArgument := binary.LittleEndian.Uint32(buffer[0x0C:0x10])
if isHost {
} else if len(buffer) == 0x10 {
return MatchCommandData{SuspendMatch: &MatchCommandDataSuspendMatch{
HostProfileID: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
IsHost: true,
SuspendValue: &suspendValue,
ClientAIDUsageMask: &suspendArgument,
HostProfileID: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
IsHostFlag: binary.LittleEndian.Uint32(buffer[0x04:0x08]),
Short: false,
SuspendValue: binary.LittleEndian.Uint32(buffer[0x08:0x0C]),
ClientAIDValue: binary.LittleEndian.Uint32(buffer[0x0C:0x10]),
}}, true
}
return MatchCommandData{SuspendMatch: &MatchCommandDataSuspendMatch{
HostProfileID: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
IsHost: false,
SuspendValue: &suspendValue,
ClientAID: &suspendArgument,
}}, true
}
return MatchCommandData{}, false
@@ -410,7 +426,15 @@ func EncodeMatchCommand(command byte, data MatchCommandData, version int) ([]byt
message = binary.BigEndian.AppendUint32(message, data.Reservation.PublicIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.Reservation.PublicPort))
if version != 3 {
if version == 11 {
isFriendInt := uint32(0)
if data.Reservation.IsFriend {
isFriendInt = 1
}
message = binary.LittleEndian.AppendUint32(message, isFriendInt)
message = binary.LittleEndian.AppendUint32(message, data.Reservation.LocalPlayerCount)
} else if version == 90 {
message = binary.BigEndian.AppendUint32(message, data.Reservation.LocalIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.Reservation.LocalPort))
message = binary.LittleEndian.AppendUint32(message, data.Reservation.Unknown)
@@ -427,37 +451,51 @@ func EncodeMatchCommand(command byte, data MatchCommandData, version int) ([]byt
return message, true
case MatchResvOK:
switch version {
case 3:
if version == 3 || version == 11 {
if int(data.ResvOK.ClientCount) != len(data.ResvOK.ProfileIDs) {
return []byte{}, false
}
message := binary.LittleEndian.AppendUint32([]byte{}, data.ResvOK.ClientCount)
for _, pid := range data.ResvOK.ProfileIDs {
message = binary.LittleEndian.AppendUint32(message, pid)
}
message = binary.BigEndian.AppendUint32(message, data.ResvOK.PublicIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.ResvOK.PublicPort))
return message, true
case 90:
message := binary.LittleEndian.AppendUint32([]byte{}, data.ResvOK.MaxPlayers)
if version == 3 {
return message, true
}
// Version 11
isFriendInt := uint32(0)
if data.ResvOK.IsFriend {
isFriendInt = 1
}
message = binary.LittleEndian.AppendUint32(message, isFriendInt)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.SenderAID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ProfileID)
message = binary.BigEndian.AppendUint32(message, data.ResvOK.PublicIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.ResvOK.PublicPort))
message = binary.BigEndian.AppendUint32(message, data.ResvOK.LocalIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.ResvOK.LocalPort))
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.Unknown)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.LocalPlayerCount)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.GroupID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ReceiverNewAID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ClientCount)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ResvCheckValue)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.MaxPlayers)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.UserData)
return message, true
}
// Version 90
message := binary.LittleEndian.AppendUint32([]byte{}, data.ResvOK.MaxPlayers)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.SenderAID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ProfileID)
message = binary.BigEndian.AppendUint32(message, data.ResvOK.PublicIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.ResvOK.PublicPort))
message = binary.BigEndian.AppendUint32(message, data.ResvOK.LocalIP)
message = binary.LittleEndian.AppendUint32(message, uint32(data.ResvOK.LocalPort))
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.Unknown)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.LocalPlayerCount)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.GroupID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ReceiverNewAID)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ClientCount)
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ResvCheckValue)
return message, true
case MatchResvDeny:
message := binary.LittleEndian.AppendUint32([]byte{}, data.ResvDeny.Reason)
return message, true
@@ -482,25 +520,11 @@ func EncodeMatchCommand(command byte, data MatchCommandData, version int) ([]byt
case MatchSuspendMatch:
message := binary.LittleEndian.AppendUint32([]byte{}, data.SuspendMatch.HostProfileID)
message = binary.LittleEndian.AppendUint32(message, data.SuspendMatch.IsHostFlag)
isHostInt := uint32(0)
if data.SuspendMatch.IsHost {
isHostInt = 1
}
message = binary.LittleEndian.AppendUint32(message, isHostInt)
if data.SuspendMatch.SuspendValue != nil {
suspendValueInt := uint32(0)
if *data.SuspendMatch.SuspendValue {
suspendValueInt = 1
}
message = binary.LittleEndian.AppendUint32(message, suspendValueInt)
if data.SuspendMatch.ClientAID != nil {
message = binary.LittleEndian.AppendUint32(message, *data.SuspendMatch.ClientAID)
} else if data.SuspendMatch.ClientAIDUsageMask != nil {
message = binary.LittleEndian.AppendUint32(message, *data.SuspendMatch.ClientAIDUsageMask)
}
if !data.SuspendMatch.Short {
message = binary.LittleEndian.AppendUint32(message, data.SuspendMatch.SuspendValue)
message = binary.LittleEndian.AppendUint32(message, data.SuspendMatch.ClientAIDValue)
}
return message, true
}

View File

@@ -2,6 +2,7 @@ package gpcm
import (
"encoding/binary"
"encoding/hex"
"github.com/logrusorgru/aurora/v3"
"strconv"
"strings"
@@ -200,7 +201,18 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) {
}
break
// TODO: Version 11 (Super Smash Bros. Brawl)
case 11:
for _, stringValue := range strings.Split(msg[msgDataIndex:], "/") {
byteValue, err := hex.DecodeString(stringValue)
if err != nil || len(byteValue) != 4 {
logging.Error(g.ModuleName, "Invalid message value; message:", msg)
g.replyError(ErrMessage)
return
}
msgData = append(msgData, byteValue...)
}
break
case 90:
msgData, err = common.Base64DwcEncoding.DecodeString(msg[msgDataIndex:])

View File

@@ -3,12 +3,25 @@ package qr2
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/logrusorgru/aurora/v3"
"strconv"
"wwfc/common"
"wwfc/logging"
)
func printHex(data []byte) string {
logMsg := ""
for i := 0; i < len(data); i++ {
if (i % 32) == 0 {
logMsg += "\n"
}
logMsg += fmt.Sprintf("%02x ", data[i])
}
return logMsg
}
func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
moduleName := "QR2/MSG"
@@ -47,7 +60,7 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
natnegID := binary.LittleEndian.Uint32(message[0x6:0xA])
moduleName = "QR2/MSG:s" + strconv.FormatUint(uint64(natnegID), 10)
} else if bytes.Equal(message[:4], []byte{0xbb, 0x49, 0xcc, 0x4d}) {
} else if bytes.Equal(message[:4], []byte{0xbb, 0x49, 0xcc, 0x4d}) || bytes.Equal(message[:4], []byte("SBCM")) {
// DWC match command
if len(message) < 0x14 {
logging.Error(moduleName, "Received invalid length match command packet")
@@ -59,6 +72,7 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
logging.Error(moduleName, "Received invalid match version")
return
}
logging.Info(moduleName, "Version:", version)
senderProfileID := binary.LittleEndian.Uint32(message[0x10:0x14])
moduleName = "QR2/MSG:p" + strconv.FormatUint(uint64(senderProfileID), 10)
@@ -89,7 +103,7 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
var ok bool
matchData, ok = common.DecodeMatchCommand(message[8], message[0x14:], version)
if !ok {
logging.Error(moduleName, "Received invalid match command:", aurora.Cyan(message[8]))
logging.Error(moduleName, "Received invalid match command:", aurora.Cyan(printHex(message)))
return
}
@@ -110,7 +124,7 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
return
}
if version != 3 {
if version == 90 {
if matchData.Reservation.LocalPort < 1024 {
logging.Error(moduleName, "RESERVATION: Local port is reserved")
return
@@ -131,12 +145,12 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
return
}
if qr2Port != matchData.Reservation.PublicPort {
if qr2Port != matchData.ResvOK.PublicPort {
logging.Error(moduleName, "RESERVATION: Public port mismatch in header and command")
return
}
if version != 3 {
if version == 90 {
if matchData.ResvOK.LocalPort < 1024 {
logging.Error(moduleName, "RESV_OK: Local port is reserved")
return
@@ -183,13 +197,15 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
var matchMessage []byte
matchMessage, ok = common.EncodeMatchCommand(message[8], matchData, version)
if !ok {
logging.Error(moduleName, "Failed to reencode match command:", aurora.Cyan(message[8]))
logging.Error(moduleName, "Failed to reencode match command:", aurora.Cyan(printHex(message)))
return
}
if len(matchMessage) != 0 {
message = append(message, matchMessage...)
}
} else {
logging.Error(moduleName, "Invalid message:", aurora.Cyan(printHex(message)))
}
mutex.Lock()