mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-08-22 00:24:38 -05:00
Fix general compatibility with DS games
This commit is contained in:
@@ -29,7 +29,7 @@ func generateRandom(n int) []byte {
|
||||
var (
|
||||
authTokenKey = generateRandom(16)
|
||||
authTokenIV = generateRandom(16)
|
||||
authTokenMagic = generateRandom(15)
|
||||
authTokenMagic = generateRandom(14)
|
||||
|
||||
loginTicketKey = generateRandom(16)
|
||||
loginTicketIV = generateRandom(16)
|
||||
@@ -46,7 +46,7 @@ func appendString(blob []byte, value string, maxlen int) []byte {
|
||||
return blob
|
||||
}
|
||||
|
||||
func MarshalNASAuthToken(gamecd string, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string, isLocalhost bool) (string, string) {
|
||||
func MarshalNASAuthToken(gamecd string, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string, unitcd byte, isLocalhost bool) (string, string) {
|
||||
blob := binary.LittleEndian.AppendUint64([]byte{}, uint64(time.Now().Unix()))
|
||||
|
||||
blob = appendString(blob, gamecd, 4)
|
||||
@@ -65,6 +65,8 @@ func MarshalNASAuthToken(gamecd string, userid uint64, gsbrcd string, cfc uint64
|
||||
challenge := RandomString(8)
|
||||
blob = append(blob, []byte(challenge)...)
|
||||
|
||||
blob = append(blob, byte(unitcd))
|
||||
|
||||
if isLocalhost {
|
||||
blob = append(blob, 0x01)
|
||||
} else {
|
||||
@@ -82,7 +84,7 @@ func MarshalNASAuthToken(gamecd string, userid uint64, gsbrcd string, cfc uint64
|
||||
return "NDS" + Base64DwcEncoding.EncodeToString(blob), challenge
|
||||
}
|
||||
|
||||
func UnmarshalNASAuthToken(token string) (err error, gamecd string, issuetime time.Time, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string, challenge string, isLocalhost bool) {
|
||||
func UnmarshalNASAuthToken(token string) (err error, gamecd string, issuetime time.Time, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string, challenge string, unitcd byte, isLocalhost bool) {
|
||||
if !strings.HasPrefix(token, "NDS") {
|
||||
err = errors.New("invalid auth token prefix")
|
||||
return
|
||||
@@ -119,7 +121,8 @@ func UnmarshalNASAuthToken(token string) (err error, gamecd string, issuetime ti
|
||||
lang = blob[0x2B]
|
||||
ingamesn = string(blob[0x2D : 0x2D+min(blob[0x2C], 75)])
|
||||
challenge = string(blob[0x78:0x80])
|
||||
isLocalhost = blob[0x80] == 0x01
|
||||
unitcd = blob[0x80]
|
||||
isLocalhost = blob[0x81] == 0x01
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/csv"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -84,3 +85,20 @@ func ReadGameList() {
|
||||
gameListNameLookup[entry[1]] = index
|
||||
}
|
||||
}
|
||||
|
||||
func GetExpectedUnitCode(gameName string) byte {
|
||||
if strings.HasSuffix(gameName, "wii") || strings.HasSuffix(gameName, "wiiam") {
|
||||
return 1
|
||||
}
|
||||
|
||||
if gameName == "sneezieswiiw" || gameName == "wormswiiware" || gameName == "wormswiiwaream" {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Games with weird other regions
|
||||
if gameName == "jockracerna" || gameName == "jockracereu" || gameName == "sengo3wiijp" {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -45,6 +45,14 @@ func IPFormatToString(ip string) (string, string) {
|
||||
return strconv.FormatInt(int64(intIP), 10), strconv.FormatUint(uint64(intPort), 10)
|
||||
}
|
||||
|
||||
func IPFormatToStringLE(ip string) (string, string) {
|
||||
intIP, intPort := IPFormatToInt(ip)
|
||||
|
||||
// Convert to little endian and print as big endian int
|
||||
intIP = int32((uint32(intIP) >> 24) | ((uint32(intIP) & 0x00FF0000) >> 8) | ((uint32(intIP) & 0x0000FF00) << 8) | ((uint32(intIP) & 0x000000FF) << 24))
|
||||
return strconv.FormatInt(int64(intIP), 10), strconv.FormatUint(uint64(intPort), 10)
|
||||
}
|
||||
|
||||
func IPFormatBytes(ip string) []byte {
|
||||
if strings.Contains(ip, ":") {
|
||||
ip = strings.Split(ip, ":")[0]
|
||||
|
||||
@@ -49,6 +49,7 @@ type MatchCommandData struct {
|
||||
TellAddr *MatchCommandDataTellAddr
|
||||
ServerCloseClient *MatchCommandDataServerCloseClient
|
||||
SuspendMatch *MatchCommandDataSuspendMatch
|
||||
Other []byte
|
||||
}
|
||||
|
||||
type MatchCommandDataReservation struct {
|
||||
@@ -189,7 +190,7 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
||||
|
||||
switch command {
|
||||
case MatchReservation:
|
||||
if version == 3 && len(buffer) < 0x0C {
|
||||
if version == 3 && len(buffer) < 0x04 {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -473,6 +474,25 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
||||
},
|
||||
}, true
|
||||
|
||||
case MatchPollTimeout:
|
||||
if len(buffer) != 0x00 {
|
||||
break
|
||||
}
|
||||
return MatchCommandData{
|
||||
Version: version,
|
||||
Command: command,
|
||||
}, true
|
||||
|
||||
case MatchPollToAck:
|
||||
if len(buffer) != 0x04 {
|
||||
break
|
||||
}
|
||||
return MatchCommandData{
|
||||
Version: version,
|
||||
Command: command,
|
||||
Other: buffer,
|
||||
}, true
|
||||
|
||||
case MatchSuspendMatch:
|
||||
if len(buffer) == 0x08 {
|
||||
return MatchCommandData{
|
||||
@@ -497,6 +517,13 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
||||
},
|
||||
}, true
|
||||
}
|
||||
|
||||
default:
|
||||
return MatchCommandData{
|
||||
Version: version,
|
||||
Command: command,
|
||||
Other: buffer,
|
||||
}, true
|
||||
}
|
||||
|
||||
return MatchCommandData{}, false
|
||||
@@ -640,6 +667,12 @@ func EncodeMatchCommand(command byte, data MatchCommandData) ([]byte, bool) {
|
||||
}
|
||||
return message, true
|
||||
|
||||
case MatchPollTimeout:
|
||||
return []byte{}, true
|
||||
|
||||
case MatchPollToAck:
|
||||
return data.Other, true
|
||||
|
||||
case MatchSuspendMatch:
|
||||
message := binary.LittleEndian.AppendUint32([]byte{}, data.SuspendMatch.HostProfileID)
|
||||
message = binary.LittleEndian.AppendUint32(message, data.SuspendMatch.IsHostFlag)
|
||||
@@ -649,6 +682,10 @@ func EncodeMatchCommand(command byte, data MatchCommandData) ([]byte, bool) {
|
||||
message = binary.LittleEndian.AppendUint32(message, data.SuspendMatch.ClientAIDValue)
|
||||
}
|
||||
return message, true
|
||||
|
||||
default:
|
||||
logging.Info("Common", "Unknown match command:", aurora.Cyan(command), "data:", data.Other)
|
||||
return data.Other, true
|
||||
}
|
||||
|
||||
return []byte{}, false
|
||||
|
||||
@@ -660,7 +660,9 @@ Celtic Kings Demo celtickingsdemo 667 TCQMZI
|
||||
Celtic Lore: Sidhe Hills celticloresidhehil
|
||||
Ceville ceville
|
||||
Chainz 2: Relinked chainz2relinked
|
||||
Champion Jockey - G1 Jockey & Gallop Racer (Wii) jockracerna yNVo3W
|
||||
Champion Jockey - G1 Jockey & Gallop Racer (EU) (Wii) jockracereu eG1kq5
|
||||
Champion Jockey - G1 Jockey & Gallop Racer (JP) (Wii) jockracerwii haVAVM
|
||||
Champion Jockey - G1 Jockey & Gallop Racer (NA) (Wii) jockracerna yNVo3W
|
||||
Champions Online champon
|
||||
Championship Euchre cheuchre 317 Yw7fc9
|
||||
Championship Hearts chhearts 251 Yw7fc9
|
||||
|
||||
|
@@ -18,6 +18,11 @@ import (
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
UnitCodeDS = 0
|
||||
UnitCodeWii = 1
|
||||
)
|
||||
|
||||
func generateResponse(gpcmChallenge, nasChallenge, authToken, clientChallenge string) string {
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(nasChallenge))
|
||||
@@ -137,7 +142,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
||||
return
|
||||
}
|
||||
|
||||
err, gamecd, issueTime, userId, gsbrcd, cfc, region, lang, ingamesn, challenge, isLocalhost := common.UnmarshalNASAuthToken(authToken)
|
||||
err, gamecd, issueTime, userId, gsbrcd, cfc, region, lang, ingamesn, challenge, unitcd, isLocalhost := common.UnmarshalNASAuthToken(authToken)
|
||||
if err != nil {
|
||||
g.replyError(ErrLogin)
|
||||
return
|
||||
@@ -149,10 +154,6 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
||||
return
|
||||
}
|
||||
|
||||
_, payloadVerExists := command.OtherValues["payload_ver"]
|
||||
_, signatureExists := command.OtherValues["wwfc_sig"]
|
||||
deviceId := uint32(0)
|
||||
|
||||
g.GameName = command.OtherValues["gamename"]
|
||||
logging.Info(g.ModuleName, "Game name:", aurora.Cyan(g.GameName))
|
||||
g.GameCode = gamecd
|
||||
@@ -160,6 +161,11 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
||||
g.Language = lang
|
||||
g.ConsoleFriendCode = cfc
|
||||
g.InGameName = ingamesn
|
||||
g.UnitCode = unitcd
|
||||
|
||||
_, payloadVerExists := command.OtherValues["payload_ver"]
|
||||
_, signatureExists := command.OtherValues["wwfc_sig"]
|
||||
deviceId := uint32(0)
|
||||
|
||||
if hostPlatform, exists := command.OtherValues["wwfc_host"]; exists {
|
||||
g.HostPlatform = hostPlatform
|
||||
@@ -169,14 +175,22 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
||||
|
||||
g.LoginInfoSet = true
|
||||
|
||||
if isLocalhost && !payloadVerExists && !signatureExists {
|
||||
// Players using the DNS exploit, need patching using a QR2 exploit
|
||||
// TODO: Check that the game is compatible with the DNS
|
||||
g.NeedsExploit = true
|
||||
} else {
|
||||
deviceId = g.verifyExLoginInfo(command, authToken)
|
||||
if deviceId == 0 {
|
||||
return
|
||||
if g.GameName != "mahjongkcds" && common.GetExpectedUnitCode(g.GameName) != unitcd {
|
||||
logging.Error(g.ModuleName, "Incorrect unit code specified:", aurora.Cyan(unitcd))
|
||||
g.replyError(ErrLogin)
|
||||
return
|
||||
}
|
||||
|
||||
if g.UnitCode == UnitCodeWii {
|
||||
if isLocalhost && !payloadVerExists && !signatureExists {
|
||||
// Players using the DNS exploit, need patching using a QR2 exploit
|
||||
// TODO: Check that the game is compatible with the DNS
|
||||
g.NeedsExploit = true
|
||||
} else {
|
||||
deviceId = g.verifyExLoginInfo(command, authToken)
|
||||
if deviceId == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,13 +265,19 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
||||
// Notify QR2 of the login
|
||||
qr2.Login(g.User.ProfileId, gamecd, ingamesn, cfc, g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated, g.User.Restricted, KickPlayer)
|
||||
|
||||
replyUserId := g.User.UserId
|
||||
if g.UnitCode == UnitCodeDS {
|
||||
// Workaround for SDK bug
|
||||
replyUserId = 0
|
||||
}
|
||||
|
||||
payload := common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "lc",
|
||||
CommandValue: "2",
|
||||
OtherValues: map[string]string{
|
||||
"sesskey": strconv.FormatInt(int64(g.SessionKey), 10),
|
||||
"proof": proof,
|
||||
"userid": strconv.FormatUint(g.User.UserId, 10),
|
||||
"userid": strconv.FormatUint(replyUserId, 10),
|
||||
"profileid": strconv.FormatUint(uint64(g.User.ProfileId), 10),
|
||||
"uniquenick": g.User.UniqueNick,
|
||||
"lt": g.LoginTicket,
|
||||
|
||||
@@ -38,6 +38,7 @@ type GameSpySession struct {
|
||||
ConsoleFriendCode uint64
|
||||
DeviceId uint32
|
||||
HostPlatform string
|
||||
UnitCode byte
|
||||
|
||||
Status string
|
||||
LocString string
|
||||
|
||||
@@ -75,6 +75,9 @@ func handleRequest(conn net.Conn) {
|
||||
switch command.Command {
|
||||
default:
|
||||
logging.Error(moduleName, "Unknown command:", command.Command)
|
||||
logging.Error(moduleName, "Raw data:", string(buffer))
|
||||
replyError(moduleName, conn, gpcm.ErrParse)
|
||||
break
|
||||
|
||||
case "ka":
|
||||
conn.Write([]byte(`\ka\\final\`))
|
||||
@@ -83,6 +86,10 @@ func handleRequest(conn net.Conn) {
|
||||
case "otherslist":
|
||||
conn.Write([]byte(handleOthersList(command)))
|
||||
break
|
||||
|
||||
case "search":
|
||||
conn.Write([]byte(handleSearch(command)))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
gpsp/search.go
Normal file
33
gpsp/search.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package gpsp
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"wwfc/common"
|
||||
"wwfc/gpcm"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func handleSearch(command common.GameSpyCommand) string {
|
||||
moduleName := "GPSP"
|
||||
|
||||
strProfileId, ok := command.OtherValues["profileid"]
|
||||
if !ok {
|
||||
logging.Error(moduleName, "Missing profileid in search")
|
||||
return gpcm.ErrSearch.GetMessage()
|
||||
}
|
||||
|
||||
profileId, err := strconv.ParseUint(strProfileId, 10, 32)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Invalid profileid:", strProfileId)
|
||||
return gpcm.ErrSearch.GetMessage()
|
||||
}
|
||||
|
||||
moduleName = "GPSP:" + strconv.FormatUint(profileId, 10)
|
||||
logging.Info(moduleName, "Search for", aurora.Cyan(profileId))
|
||||
|
||||
return common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "bsrdone",
|
||||
})
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request
|
||||
replyHTTPError(w, 400, "400 Bad Request")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Need to know this here to determine UTF-16 endianness (LE for DS, BE for Wii)
|
||||
// unitcd 0 = DS, 1 = Wii
|
||||
unitcdValues, ok := r.PostForm["unitcd"]
|
||||
@@ -264,10 +264,10 @@ func login(moduleName string, fields map[string]string, isLocalhost bool) map[st
|
||||
// Only later DS games send this
|
||||
ingamesn, ok := fields["ingamesn"]
|
||||
if ok {
|
||||
authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, 0, 0, langByte[0], ingamesn, isLocalhost)
|
||||
authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, 0, 0, langByte[0], ingamesn, 0, isLocalhost)
|
||||
logging.Notice(moduleName, "Login (DS)", aurora.Cyan(strconv.FormatUint(userId, 10)), aurora.Cyan(gsbrcd), "devname:", aurora.Cyan(devname), "ingamesn:", aurora.Cyan(ingamesn))
|
||||
} else {
|
||||
authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, 0, 0, langByte[0], "", isLocalhost)
|
||||
authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, 0, 0, langByte[0], "", 0, isLocalhost)
|
||||
logging.Notice(moduleName, "Login (DS)", aurora.Cyan(strconv.FormatUint(userId, 10)), aurora.Cyan(gsbrcd), "devname:", aurora.Cyan(devname))
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ func login(moduleName string, fields map[string]string, isLocalhost bool) map[st
|
||||
return param
|
||||
}
|
||||
|
||||
authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, cfcInt, regionByte[0], langByte[0], fields["ingamesn"], isLocalhost)
|
||||
authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, cfcInt, regionByte[0], langByte[0], fields["ingamesn"], 1, isLocalhost)
|
||||
logging.Notice(moduleName, "Login (Wii)", aurora.Cyan(strconv.FormatUint(userId, 10)), aurora.Cyan(gsbrcd), "ingamesn:", aurora.Cyan(fields["ingamesn"]))
|
||||
}
|
||||
|
||||
|
||||
@@ -40,18 +40,30 @@ func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []b
|
||||
|
||||
realIP, realPort := common.IPFormatToString(addr.String())
|
||||
|
||||
noIP := false
|
||||
if ip, ok := payload["publicip"]; !ok || ip == "0" {
|
||||
// Set the public IP key to the real IP
|
||||
payload["publicip"] = realIP
|
||||
payload["publicport"] = realPort
|
||||
noIP = true
|
||||
}
|
||||
|
||||
// Client is mistaken about its public IP
|
||||
if payload["publicip"] != realIP || payload["publicport"] != realPort {
|
||||
logging.Error(moduleName, "Public IP mismatch")
|
||||
return
|
||||
clientEndianness := common.GetExpectedUnitCode(payload["gamename"])
|
||||
if !noIP && clientEndianness == ClientBigEndian {
|
||||
if payload["publicip"] != realIP || payload["publicport"] != realPort {
|
||||
// Client is mistaken about its public IP
|
||||
logging.Error(moduleName, "Public IP mismatch")
|
||||
return
|
||||
}
|
||||
} else if !noIP && clientEndianness == ClientLittleEndian {
|
||||
realIPLE, realPortLE := common.IPFormatToStringLE(addr.String())
|
||||
if payload["publicip"] != realIPLE || payload["publicport"] != realPortLE {
|
||||
// Client is mistaken about its public IP
|
||||
logging.Error(moduleName, "Public IP mismatch")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
payload["publicip"] = realIP
|
||||
payload["publicport"] = realPort
|
||||
|
||||
lookupAddr := makeLookupAddr(addr.String())
|
||||
|
||||
statechanged, ok := payload["statechanged"]
|
||||
@@ -100,7 +112,7 @@ func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []b
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
if !session.Authenticated {
|
||||
if !session.Authenticated || noIP {
|
||||
sendChallenge(conn, addr, session, lookupAddr)
|
||||
} else if !session.ExploitReceived && session.Login != nil && session.Login.NeedsExploit && statechanged == "1" {
|
||||
logging.Notice(moduleName, "Sending SBCM exploit to DNS patcher client")
|
||||
|
||||
@@ -241,7 +241,7 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
|
||||
}
|
||||
|
||||
s := sleep.Sleeper{}
|
||||
s.AddWaker(&receiver.MessageAckWaker)
|
||||
s.AddWaker(receiver.MessageAckWaker)
|
||||
timeWaker := sleep.Waker{}
|
||||
s.AddWaker(&timeWaker)
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ClientNoEndian = iota
|
||||
ClientBigEndian
|
||||
ClientLittleEndian
|
||||
ClientLittleEndian = 0
|
||||
ClientBigEndian = 1
|
||||
ClientNoEndian = 2
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
@@ -34,8 +34,8 @@ type Session struct {
|
||||
PacketCount uint32
|
||||
Reservation common.MatchCommandData
|
||||
ReservationID uint64
|
||||
MessageMutex deadlock.Mutex
|
||||
MessageAckWaker sleep.Waker
|
||||
MessageMutex *deadlock.Mutex
|
||||
MessageAckWaker *sleep.Waker
|
||||
GroupPointer *Group
|
||||
}
|
||||
|
||||
@@ -116,8 +116,8 @@ func setSessionData(moduleName string, addr net.Addr, sessionId uint32, payload
|
||||
PacketCount: 0,
|
||||
Reservation: common.MatchCommandData{},
|
||||
ReservationID: 0,
|
||||
MessageMutex: deadlock.Mutex{},
|
||||
MessageAckWaker: sleep.Waker{},
|
||||
MessageMutex: &deadlock.Mutex{},
|
||||
MessageAckWaker: &sleep.Waker{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user